reptile7's JavaScript blog
Wednesday, January 31, 2018
 
A JavaScript 1.2 Calendar and Clock, Part 6
Blog Entry #388

The right year

One last point regarding the compute( ) day-of-the-week algorithm discussed in the previous post:
Obviously, the algorithm will not give us what we want if the val3 year input is off in some way. The year field's "19" + t.getYear( ) value was 1997 back in 1997 but is 19118 in 2018, which for January leads to a week = Tuesday versus the Monday that 1 January 2018 actually fell on, not good: this problem can be quickly fixed by setting the value to t.getFullYear( ).

A new charge

After the compute( ) function has finished executing, a

window.open('js-calbot2.html', target='frame2');

command replaces the js-calbot1.html document in the frame2 frame with the js-calbot2.html document. Per Mozilla, the window.open( ) method's windowName parameter specifies the name of the browsing context, a <frame> in this case, into which to load the specified resource; the target= part is unnecessary and in fact shouldn't be there.

js-calbot2.html intro

The js-calbot2.html body comprises a single script element that single-handedly (without the aid of any non-script HTML) creates a tabular calendar for a given month.

<html>
<body bgcolor="black" text="red" link="white" vlink="white">
<script>
...calendar code...
</script>
</html>


Body style

As shown above, the body element start tag provides color settings for the page background, the foreground text, unvisited links, and visited links.
(t) The text="red" attribute reddens the calendar's Sunday to Saturday day headers, and that's it.
(l,v) There are no links on the js-calbot2.html page.

Interframe data transfer

The js-calbot2.html script calls on the values stored in the line form's result, month, and year fields to create the calendar.

window.onerror = null;
first = parent.frame1.document.line.result.value;
months = parent.frame1.document.line.month.value;
yr = parent.frame1.document.line.year.value;


The line form's raison d'être is to facilitate this transfer of data. However, we could alternatively store the line form's hidden control values in global variables

var val1, val2x, val3, week;
val1 = 1;
val2x = t.getMonth( ) + 1; // Or just: val2x = t.getMonth( );
val3 = t.getFullYear( );


in which case there would be no need for the line form at all.

var first = parent.frame1.week;
var months = parent.frame1.val2x;
var yr = parent.frame1.val3;


The window.onerror = null; statement means your users won't see JavaScript errors caused by your own code, quoting Netscape, or at least that's what it meant for users who were using Netscape 3.x-4.x back in the day. I suspect the statement's purpose is to suppress the "parent.frame1 is undefined" or equivalent error that would otherwise be promptly thrown by the above parent.frame1 expression(s) if we were to land on the isolated js-calbot2.html page outside of the javacalendar.html frameset; a much better approach to preempting that error is to route the user to javacalendar.html via an if (! parent.frame1) window.location = "javacalendar.html"; conditional.

What month is it?

To build the calendar we'll need to know how many days the months month has.

var a = "";
if (months == 1 || months == 3 || months == 5 || months == 7 || months == 8 || months == 10 || months == 12) { a = 31; }
else if (months == 4 || months == 6 || months == 9 || months == 11) { a = 30; }
else { a = 28; }


Minor bullet point:
• The number-storing a can be declared with a var a; statement; its initialization to an empty string is pointless.
Major bullet point:
No allowance is made for leap years, whose Februaries will have only a = 28 days per the concluding else clause. Here's what we want for February:

else {
    if ((! (yr % 4) && (yr % 100)) || ! (yr % 400)) a = 29;
    else a = 28; }


We will later put the name of the months month in the calendar table caption. Accordingly, the months month number is mapped onto a corresponding month string via:

if (months == 1) { months = "January "; }
else if (months == 2) { months = "February "; }
...
else if (months == 12) { months = "December "; }


We saw the same set of months mappings in the js-caltop1.html date string code: as in that case, I would alternatively use an Array to get the months string:

var t = new Date( );
var monthArray = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var months = monthArray[t.getMonth( )];


Re the pre-months display

The first day string is mapped onto a getDay( )-synced d index via:

if (first == "Sunday") { d = 0; }
else if (first == "Monday") { d = 1; }
...
else if (first == "Saturday") { d = 6; }


We could have gotten d back in the compute( ) function with:

var t = new Date(val3, val2x, val1);
var d = t.getDay( );


We will later use d to create calendar table cells that lead up to the start of the months month for months months that do not begin on a Sunday.

Calendar intro

We're ready to start writing out the calendar table.

document.write("<center><table border='1'>");

• The center element's closing </center> tag is missing in the source; of course, we should actually use a table { margin-left: auto; margin-right: auto; } styling to horizontally center the table.

The calendar's rows[0] row holds one cell that contains a months yr caption.

document.write("<tr>");
document.write("<th colspan='7'><center><font size='+3' color='#449977' face='Arial'><b>" + months + " " + yr + "</b></font></center></th>");
document.write("</tr>");


• Th cell content is bolded and horizontally centered by default with Firefox, Google Chrome, Opera, and Safari on my computer.
• A +3 font element size can be effected by a font-size: xx-large; styling.
• A caption is a heading but not really a header, and the th element is not quite right for it; it would be better to put our caption in a bona fide caption element:

caption { color: #449977; font: bold xx-large Arial; }

var calendarCaption = document.createElement("caption");
calendarCaption.textContent = months + " " + yr;
document.getElementsByTagName("table")[0].appendChild(calendarCaption);


Caption content is placed at the top of the table and is horizontally centered but not bolded by default; it is not surrounded by the table border, which doesn't bother me, although you could give it, say, a border: double #888; styling if you like.

The calendar's rows[1] row holds seven cells that respectively contain Sunday to Saturday day headers.

document.write("<tr>");
document.write("<td width='75'><center><font size='-1' face='Arial'><b>Sunday</b></font></center></td>");
document.write("<td width='75'><center><font size='-1' face='Arial'><b>Monday</b></font></center></td>");
...
document.write("<td width='75'><center><font size='-1' face='Arial'><b>Saturday</b></font></center></td>");
document.write("</tr>");


• Changing the td elements to th elements would allow us to get rid of the center and b element formatting.
• A -1 font element size can be effected by a font-size: small; styling; that said, I myself would size-wise leave the day headers alone and not small-en them.

Here's what we've got so far:


The day header row is coded with:

var dayArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var dayTr = document.createElement("tr");
document.getElementsByTagName("table")[0].appendChild(dayTr);
for (var i = 0; i < dayArray.length; i++) {
    var dayTh = document.createElement("th");
    dayTh.style.width = "75px";
    dayTh.textContent = dayArray[i];
    dayTh.style.color = "red";
    dayTh.style.fontFamily = "Arial";
    dayTr.appendChild(dayTh); }

We'll go through the date part of the calendar in the following entry.

Sunday, January 14, 2018
 
A JavaScript 1.2 Calendar and Clock, Part 5
Blog Entry #387

We'll finish off the js-caltop1.html document's scripts[2] script in today's post. I've done some homework on that mysterious code in the compute( ) function and I've got it all pretty well sorted out at this point - here we go...

More on the line form

A proper discussion of the compute( ) function obliges us to flesh out the line form that code-wise surrounds it.

As detailed earlier in this series, the line form contains five hidden controls and the push button.

The hidden part of the form is written JavaScriptically via a document.write( ) command that appears
(a) after a t = new Date( ); constructor, whose t Date provides values for the form's month and year fields, and
(b) before the compute( ) function
in the scripts[2] script.

The button and the line form's closing </form> tag, and the cells[2] table cell that contains them, are written as normal HTML that appears outside and after the scripts[2] script.

We can code the line form more cleanly if we put all of it in the cells[2] cell and separate its HTML and JavaScript as follows:

<td id="rightTd">
<form name="line">
<input name="res" type="hidden">
<input name="day" type="hidden" value="1">
<input name="month" type="hidden">
<input name="year" type="hidden">
<input name="result" type="hidden">
<input name="butt" type="button" value=" Show Calendar " onclick="compute(this.form); window.open('js-calbot2.html', target='frame2');">
</form>
<script type="text/javascript">
var t = new Date( );
document.line.month.value = t.getMonth( ) + 1;
document.line.year.value = "19" + t.getYear( );
</script>
</td>


In the compute( ) function, we will use the values of the day, month, and year fields to find the starting day of the week for a given month; this determination will enable us to build a calendar for the month from scratch in the frame2 frame.

compute( ) it

References

(1) The compute( ) code is based on an algorithm developed by Kim Larsen in a 1995 "Computing the Day of the Week" article; the compute( ) code differs slightly from Larsen's algorithm in that the former is pegged to a 0 = Saturday to 6 = Friday week whereas the latter is pegged to a 0 = Monday to 6 = Sunday week.

N.B. A significant part of the third paragraph of Larsen's article ("More formally, ...") is not rendered due to an incorrectly coded <img> element (that points to an image of a ≤ character, which could have been coded with a &le; character reference) - you'll have to go to the page source to get the full text.

(2) Ask Dr. Math's "The Calendar and the Days of the Week" article is a good starting point if you are new to day-of-the-week algorithms (as I was); it notably links to
(a) a "Formula for the First Day of a Year" article that compares Larsen's algorithm with one version of* a more well-known day-of-the-week algorithm called Zeller's congruence (*note that Ask Dr. Math's Zeller formula is different from Wikipedia's Gregorian Zeller formula) and
(b) a "Deriving Zeller's Rule" article that is helpful in understanding the month part(s) of the Larsen and Zeller algorithms.

Deconstruction

The Larsen and Zeller algorithms calculate a day-of-the-week index for the day of the week for a specific date. In our case, the compute( ) function will determine the day of the week that the first day of the current month falls on - "Monday" for January 2018, e.g. - if we haven't touched the month and year controls in the cells[1] table cell; if desired, however, compute( ) and its inputs can be modified so as to determine the day of the week for any Gregorian date.

Upon clicking the button and calling the compute( ) function, a this.form reference to the line form is passed to compute( ) and given a form identifier. As noted at the end of the previous post, the compute( ) body begins by changing the Show Calendar button label to Update Calendar.

function compute(form) {
    document.line.butt.value = "Update Calendar";


After that, compute( ) gets down to business. A subsequent set of operations extracts date, month, and year inputs for the compute( ) day-of-the-week code from the line form; for the arithmetic to come, these inputs are stored in val1, val2x, and val3 variables, respectively.

var val1 = parseInt(form.day.value, 10);
if ((val1 < 0) || (val1 > 31)) { window.alert("Day is out of range."); }
var val2 = parseInt(form.month.value, 10);
if ((val2 < 0) || (val2 > 12)) { window.alert("Month is out of range."); }
var val2x = parseInt(form.month.value, 10);
var val3 = parseInt(form.year.value, 10);
if (val3 < 1900) { window.alert("You're that old!"); }


• The day, month, and year values have a string data type; we'll carry out some addition with the numbers they hold in a little bit so it is necessary to numberify them via either the parseInt( ) function or the Number( ) function.
• The day value is fixed at 1 - at no point in the code is it reset - and therefore the if ((val1 < 0) || (val1 > 31)) test can be thrown out.
• The if ((val2 < 0) || (val2 > 12)) test can also be thrown out: the month value can either stay at t.getMonth( ) + 1 or be reset by the isnlist selection list but at no point does it go outside the 1-12 range.
• It's actually not necessary to use two variables for the month input: the values of val2 and val2x will diverge for January and February (while remaining the same for all other months) but that divergence can be recoded with one variable.
• You can push the val3 value below 1900 if you click the button enough times but the You're that old! alert( ) response upon doing that seems a bit silly, doesn't it?

The compute( ) day-of-the-week code works with a year of months that run from the March of a given year to the February of the next year and that are numbered 3 to 14, respectively. The January and February of the given year are renumbered and shifted to the previous year by the following conditionals:

if (val2 == 1) {
    val2x = 13;
    val3 = val3 - 1; }
if (val2 == 2) {
    val2x = 14;
    val3 = val3 - 1; }


The compute( ) day-of-the-week code works with a week of days that run from Saturday to Friday and that are numbered 0 to 6, respectively. The val1 1 date is mapped onto its corresponding day number via:

var val4 = parseInt(((val2x + 1) * 3) / 5, 10);
var val5 = parseInt(val3 / 4, 10);
var val6 = parseInt(val3 / 100, 10);
var val7 = parseInt(val3 / 400, 10);
var val8 = val1 + (val2x * 2) + val4 + val3 + val5 - val6 + val7 + 2;
var val9 = parseInt(val8 / 7, 10);
var week = val8 - (val9 * 7);


The terms that go into the var val8 = val1 + (val2x * 2) + val4 + val3 + val5 - val6 + val7 + 2; line are not meaningful in an absolute sense; rather, they are part of a formula that tracks changes in the day of the week in response to various time markers.
(val2x * 2) + ((val2x + 1) * 3) / 5 moves the day of the week forward by three days or two days as we go from month to month in the March to February year.
• In going from year to year, val3 + val3 / 4 - val3 / 100 + val3 / 400 moves the day of the week forward by one day if val3 ends with a 28 February (365 % 7 = 1) or two days if val3 ends with a 29 February.
• The + 2 at the end syncs us with a 0 = Saturday to 6 = Friday week; Larsen's formula doesn't have it; a + 1 would sync us with the getDay( ) method's 0 = Sunday to 6 = Saturday week.
Math.floor( ) can be used in place of parseInt( ).
• The last two commands - var val9 = parseInt(val8 / 7, 10); var week = val8 - (val9 * 7); - are equivalent to var week = val8 % 7;.

The val1 date's day number, week, is stored in the line form's res field and then mapped onto a corresponding day string via a switch-like set of if statements; the resulting week day string is finally stored in the line form's result field.

form.res.value = week;
if (week == 1) { week = "Sunday"; }
else if (week == 2) { week = "Monday"; }
else if (week == 3) { week = "Tuesday"; }
else if (week == 4) { week = "Wednesday"; }
else if (week == 5) { week = "Thursday"; }
else if (week == 6) { week = "Friday"; }
else if (week == 0) { week = "Saturday"; }
form.result.value = week; } /* That's it for compute( ). */


A better getDayString( )

We earlier created
(1) a separate t Date just for the line form and
(2) a var dayArray = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; Array for the date string part of the code;
both t and dayArray have a global scope. With these features in hand, we can get the week day string with:

t.setDate(1);
var week = dayArray[t.getDay( )];


That's it - a lot simpler, huh? This is something the author could have done: the setDate( ) method of the Date object was implemented in JavaScript 1.0 whereas the core Array object was implemented in JavaScript 1.1.
We are at long last ready to load the js-calbot2.html document into the frame2 frame and take on its calendar code, and we'll do just that in our next episode.


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)