reptile7's JavaScript blog
Wednesday, June 19, 2019
 
Approaching Quizmo, Part 2
Blog Entry #400

Welcome back to our ongoing deconstruction of the Java/JavaScript Goodies Math Check. In the previous post we walked through the checker's addition functionality and we now turn our attention to its subtraction, multiplication, and division code.

Subtract, multiply, divide

Clicking the rekenen form's , , and buttons calls subtract( ), multiply( ), and divide( ) functions, respectively. The subtract( ) and multiply( ) functions are exactly like the add( ) function except they've got - and * operators in place of the + operator; the divide( ) function largely parallels the add( ) function although it does feature some noteworthy differences.

Recall that the Math Check metatext includes a This page is best viewed with Netscape 3.0 line. With Navigator 3.04 on my iMac, x / 0 for x 0 and 0 / 0 both return NaN; the former division returns Infinity with Netscape 4+ and all modern browsers. These divisions are legitimate but their usefulness leaves something to be desired vis-à-vis testing the user's division skills: the divide( ) function staves them off by adding one to the random( ) and ranom( ) % remainders.

numA = random(maxValue) + 1;
numB = ranom(maxValue) + 1;


The divide( ) function subsequently carries out a numA / numB division for which the numA dividend is greater than or equal to the numB divisor; if a first numA = random(maxValue) + 1; is less than a first numB = ranom(maxValue) + 1;, then divide( ) is re-called as many times as needed until numAnumB.

if (numA < numB) { divide( ); }
numC = numA / numB;


The numC quotient is round( )-ed to the nearest integer; a following alert( ) asks the user to follow suit.

numC = Math.round(numC);
window.alert("Please round your answer off ?\n.5 or higher one number up\n.4 or lower one number down");


Each divide( )/ans( ) run therefore displays four dialogs:

(1) window.alert("Please round your answer...");
(2) window.prompt(numA + "/" + numB + " = ", 0);
(3) window.confirm("Do you want your browser to check your answer...");
(4) window.alert(...check response (answer is correct|wrong, try again later)...);

If it takes three divide( ) calls to obtain a numAnumB, then the user will be subjected to twelve dialogs for the same, ultimate numA / numB division, and we don't want that; this situation is easily cleaned up by placing a return statement after the divide( ) re-call so as to end the current divide( ) run.

if (numA < numB) { divide( ); return; }

Alternatively and preferably, we can lose the divide( ) re-call and just exchange the numA and numB values via:

if (numA < numB) {
    var divisor = numA;
    numA = numB;
    numB = divisor; }


Function consolidation

Do we really need separate add( ), subtract( ), multiply( ), and divide( ) functions? Nah. Upon setting up a

var opArray = ["+", "-", "*", "/"];

operator string Array (N.B. a var opArray = [+, -, *, /]; Array of non-stringified operators throws an expected expression, got ',' SyntaxError) the four functions can be formulated as a single operate( ) function that is operator-differentiated by an opIndex parameter (0 for addition, 1 for subtraction, etc.) and whose operator characters are fetched by an opArray[opIndex] expression:

function operate(opIndex) {
    ...beginning maxValue, numA, and numB assignments...
    if (opIndex == 3) { numA++; numB++; if (numA < numB) { ...numAnumB exchange... } }
    numC = eval(numA + opArray[opIndex] + numB);
    if (opIndex == 3) { numC = Math.round(numC); window.alert("Please round your answer..."); }
    Answer = window.prompt(numA + " " + opArray[opIndex] + " " + numB + " = ", 0);
    if (window.confirm("Do you want your browser to check your answer to the problem " + numA + " " + opArray[opIndex] + " " + numB + "?")) ans( );
    else window.alert("Please try again later!"); }


Note that the numC = numA +|-|*|/ numB operations are merged via the somewhat-notorious eval( ) function; if the use of eval( ) here bothers you for whatever reason, you can write out the operations 'in longhand' instead per the original code:

if (opIndex == 0) numC = numA + numB;
if (opIndex == 1) numC = numA - numB;
if (opIndex == 2) numC = numA * numB;
if (opIndex == 3) numC = numA / numB;


One last function

Clicking the button calls a check( ) function that displays the user's correct and wrong tallies on an alert( ) box.

function check( ) {
    window.alert("YOUR SCORE\n : " + correct + " correct\n : " + wrong + " incorrect"); }


The user can problem-wise flit freely between
the add, subtract, multiply, and divide operations and
the A, B, and C difficulty levels
and check( ) will score-wise keep track of it all.
In the next entry we'll revisit the random generation of numA and numB and then wrap things up with a refurbished demo.

Saturday, May 25, 2019
 
Approaching Quizmo
Blog Entry #399

Let's move on now to the next Calculators, Calendars, and Clocks sector item, that being Math Check, which went live in October 1997 and comes to us courtesy of Binoculars V.O.F.

Math Check assembles and presents to the user arithmetic problems having difficulty levels A < B < C and whose integer operands are themselves randomly generated;
per its title, it can check the user's answers to these problems against its own answers
and keep a running tally of correct and wrong user answers.
The /JSBook/mathcheck.html page does not feature a Grab the Script link although the Math Check code can be accessed at the corresponding mathcheck.txt page. The mathcheck.html demo works OK for the most part: its division module is somewhat buggy for a reason we will detail below.

Initial display

Before we do any arithmetic, we initially see on the mathcheck.html page
some metatext

IMPROVE YOUR ARITHMETIC SKILLS
USEFUL FOR THE AGES 3 TO 103
This page is best viewed with Netscape 3.0


<h4><center>IMPROVE YOUR ARITHMETIC SKILLS</center></h4>
<h4><center>USEFUL FOR THE AGES 3 TO 103</center></h4>
<center><h4>This page is best viewed with Netscape 3.0</center></h4>


and an series of push buttons

<center><form name="rekenen">
<input type="button" value="add" onclick="add( );">
<input type="button" value="subtract" onclick="subtract( );">
<input type="button" value="multiply" onclick="multiply( );">
<input type="button" value="divide" onclick="divide( );">


and a
Level A Level B Level C
set of radio buttons

<input type="radio" name="arithmetic">Level A
<input type="radio" name="arithmetic" checked>Level B
<input type="radio" name="arithmetic">Level C


and a push button.

<input type="button" value="Check Score" onclick="check( );">

We should also see a push button to the right of the button but it doesn't show up because of a coding typo.

<inputtype="button" value="Reset Score" onclick="score( );">

HTML notes

• As detailed above, the metatext segments are structurally marked up as <h4> headings. Are those lines headings? They're not headings. Code them as a <br>-newlining <strong>, good enough.

• The authors used four separate <center>s to horizontally center the metatext and the line boxes of the rekenen form (FYI, the h# elements can't validly have block-level children); just one <center> would have sufficed.

<body onload="score( );" bgcolor="#ffffff" text="#000000">
<center> ...metatext + form code... </center>
</body>


Our baseline

A score( ) function sets correct and wrong variables to 0 when the mathcheck.html page has loaded.

function score( ) {
    correct = 0;
    wrong = 0; }


Add it

It's time to get the math checking under way and put our arithmetic skills to the test, yes? We click the button, thereby calling an add( ) function, which will create and display an addition problem having two operands. The add( ) function's first order of business is to set some maxValue upper boundaries for the operands:

function add( ) {
    if (document.rekenen.arithmetic[0].checked) maxValue = 10;
    else {
        if (document.rekenen.arithmetic[1].checked) maxValue = 30;
        else { maxValue = 60; } } ...


For a
Level A
problem, the operands will be less than 10;
for a
Level B
problem, they'll be less than 30;
for a
Level C
problem, they'll be less than 60.
In all three cases, the operand range runs up to but does not reach the maxValue value, per the following code.

The operands themselves are separately generated by external random( ) and ranom( ) functions.

numA = random(maxValue);
numB = ranom(maxValue);

function random(maxValue) {
    day = new Date( );
    hour = day.getHours( );
    min = day.getMinutes( );
    sec = day.getSeconds( );
    mili = day.getTime( );
    return(((hour * 3600) + (min * 60) + (sec) + mili) % maxValue); }

function ranom(maxValue) {
    day = new Date( );
    mil = day.getTime( );
    return((mil) % maxValue); }


Both functions use a new Date( )-data % maxValue division/remainder operation to give a random (pseudorandom?) number in the range 0 to maxValue - 1, inclusive; regarding these operations,
the random( ) dividend is the number of seconds that have elapsed since the beginning of the current day plus the getTime( ) number of milliseconds that have elapsed since 1970-01-01T00:00:00.000Z whereas
the ranom( ) dividend is just the getTime( ) millisecond count.
The % remainders are returned to the add( ) function and respectively assigned to numA and numB variables.
(We previously encountered a var num = new Date( ).getSeconds( ) % 10 creation of random numbers in HTML Goodies' JavaScript Primers #20, which was discussed in Blog Entry #36.)

Back at the add( ) function,
numA and numB are added
and the resulting sum is stored in a numC variable.

numC = numA + numB;

Next, add( ) concatenates numA, +, numB, and  =  and displays the resulting addition problem string on a prompt( ) box; the prompt( )'s default input value is initially set to 0.

Answer = window.prompt(numA + "+" + numB + " = ", 0);

The user does or does not enter a value into the box's input field and then clicks the box's or button; the prompt( ) output is assigned to an Answer variable.

Is the Answer correct? Up pops an if-conditioned confirm( ) box whose message asks
Do you want your browser
 to check the answer you gave to the problem numA + numB
?

if (window.confirm("Do you want your browser\n to check the answer you gave to the problem " + numA + " + " + numB))
    ans( );
else { window.alert("Please try again later!"); } } // End of add( ) function


Clicking the box's button displays a Please try again later! alert( ). Clicking the box's button calls an external ans( ) function that compares the Answer and numC values.

function ans( ) {
    if (Answer == numC) {
        correct == correct++;
        window.alert("Congratulations your answer is correct."); }
    else {
        wrong == wrong++;
        window.alert("The answer " + Answer + " that you gave is wrong. The correct answer = " + numC); } }


If Answer and numC are equal, then the correct count is increased by one and the user gets a Congratulations your answer is correct. alert( ); if they're not equal, then the wrong count is increased by one and the user gets a The answer  Answer  that you gave is wrong. The correct answer = numC alert( ).

The correct == correct++; and wrong == wrong++; lines are kind of weird - if I understand the postfix increment operator correctly, they initially compare correct and wrong with themselves (thereby returning true, although nothing is done with those trues) and then increment correct and wrong - clearly, correct++; and wrong++; are all we need here.
We'll get to the rest of the mathcheck.html JavaScript in the following entry.

Thursday, May 09, 2019
 
The Whole of a Circle
Blog Entry #398

We continue today our discussion of the Java/JavaScript Goodies Circle Calculator. Having taken stock of the calculator's HTML structure and metafunctionality in the previous post, we are ready to do some actual calculating - let's get to it, shall we?

Area, diameter, circumference

Suppose the user changes the area field's 0 value to 10. Subsequently blurring the field calls a circle( ) function and passes thereto a this.form reference to the field's containing form and a this.name reference to the field's name, which are respectively assigned to form and changed variables.

function circle(form, changed) { /* circle( ) function body */ }
...
<input type="text" name="area" value="0" onchange="circle(this.form, this.name);">


I trust y'all know that for a circle
A = π × r² and C = π × d
where A, r, C, and d are the circle's area, radius, circumference, and diameter, respectively.

The parts of a circle
(I previously used this image here in Blog Entry #361 and originally got it from this USF page.)

Spikeman put all of the circle( ) value-getting and -setting code in a
with (Math) { ... }
carrier so he could use the JavaScript PI property and sqrt( ) method without a calling Math. object reference. (Although Mozilla's current with statement page details a number of problems with with, Netscape raised no with red flags back in the day.)

The with block begins by reading the area, diameter, and circumfrence fields' values and assigning them respectively to area, diameter, and circumfrence variables.

var area = form.area.value;
var diameter = form.diameter.value;
var circumfrence = form.circumfrence.value;


(Is it necessary to scoop up all three values? Two of them are 0, after all. A basic
var area, diameter, circumfrence;
declaration would suffice if we were to pass this itself to circle( ) and give it, say, an inputObject identifier and then use an inputObject.value expression in place of area, diameter, and circumfrence in the calculations below.)

Moving on, the above area/diameter/circumfrence assignments are followed by an if statement that tests if the area field has been changed and if so maps the area to the corresponding diameter and circumfrence via the formulae given earlier.

if (changed == "area") {
    var radius = sqrt(area / PI);
    diameter = 2 * radius;
    circumfrence = PI * diameter; }


Changes to the diameter and circumfrence fields are processed by analogous conditionals.

if (changed == "diameter") {
    area = PI * (diameter / 2) * (diameter / 2);
    circumfrence = PI * diameter; }
if (changed == "circumfrence") {
    diameter = circumfrence / PI;
    area = PI * (diameter / 2) * (diameter / 2); }


That's it for the math. The area, diameter, and circumfrence are lastly loaded into their designated form fields.

form.area.value = area;
form.diameter.value = diameter;
form.circumfrence.value = circumfrence; } /* End of with block */ } /* End of circle( ) function */


As regards the area = 10 input
the final outputs are:

Area:
Diameter:
Circumfrence:


In the name of completeness

Immediately after the circle( ) function are

var toDegrees = 360 / (Math.PI * 2);
var toRadians = (Math.PI * 2) / 360;


statements of which no use is made: throw them out.

Demo + notes

We're just about ready to wrap this guy up, and as is our custom, we do so by addressing the age-old question: "How might we improve things?"

I noted early on that the calculator's onchange action rubs me the wrong way.
Relatedly, the use of the area, diameter, and circumfrence fields for both inputting and outputting data strikes me as poor design.
I gave some thought to what I would like the calculator to be and here's what I came up with:

Circle Math

Another parts of a circle image - I myself made this one
C = π × d
A = π × r²
Calculation of Area, Circumference, and Diameter
Choose a term



and then enter a value:


The user initially interacts with a set of three radio buttons vis-à-vis a somewhat-confusing 'menu' of text boxes.
Clicking a radio button

<label><input type="radio" name="termName" value="Diameter:" onclick="chooseTerm(this.form, this.value);" /> Diameter</label>

calls a chooseTerm( ) function that
writes a corresponding label to the rows[1].cells[0] cell and
sends focus to a name="input3" text box in the rows[1].cells[1] cell.

var inputLabels = circleTable.getElementsByTagName("label");
function chooseTerm(form, inputLabel) {
    inputLabels[3].textContent = inputLabel;
    form.input3.focus( ); }


The user enters a value into the input3 field
and then clicks a button

<button type="button" onclick="getValues(this.form, this.form.input3.value);">Calculate the Remaining Terms</button>

thereby calling a getValues( ) function that outputs labels and values for the remaining circle parts in the rows[3] and rows[4] rows.

function getValues(form, inputValue) {
    if (inputLabels[3].textContent == "Diameter:") {
        inputLabels[4].textContent = "Circumference:";
        inputLabels[5].textContent = "Area:";
        form.input4.value = Math.PI * inputValue;
        form.input5.value = Math.PI * Math.pow(inputValue / 2, 2); } ... }


The inputValue is validated: the empty string and other non-numeric strings

else if (isNaN(inputValue)) { /* The isNaN( ) function was implemented for "all platforms" in JavaScript 1.1. */
    window.alert("Your input must be a bona fide number."); form.input3.value = ""; form.input3.focus( ); }


and non-positive numbers are all flagged and turned away by tests that Spikeman could have made use of.

The form.input4.value and form.input5.value are truncated/rounded at the thousandths place.

form.input4.value = Number(form.input4.value).toFixed(3);
form.input5.value = Number(form.input5.value).toFixed(3);


(Spikeman didn't have access to the toFixed( ) method but he could've easily accomplished the same via some decimal-point shifting and Math.round( )ing, e.g.:
form.area.value = Math.round(area * 1000) / 1000;)

Check the page source for the full coding. BTW, most but not all of the stylings

for (var i = 3; i < inputLabels.length; i++) inputLabels[i].style.color = "silver";

are effected JavaScriptically.
Math Check is up next.

Friday, April 19, 2019
 
May the Circle Be Unbroken, Part 2
Blog Entry #397

Today in the Calculators, Calendars, and Clocks sector we will take up the Circle Calculator, which went live in October 1997 and was authored by S. Christopher "Spikeman" Brown. The Circle Calculator takes in the diameter, circumference, and area parts of a circle: upon inputting a value for one part it outputs the corresponding values for the other two parts - as such it is a bit more involved than the Circle Circumference script that we covered in Blog Entry #367.

The /JSBook/circle.html page sports a functioning demo whose
After entering a value, click on the other values possible
interface is not at all to my liking. (But at least we're not accosted by any prompt( ) pop-ups right off the bat.)
The Circle Calculator code may be accessed here.

Structural overview

The Circle Calculator table

Per the preceding image:
The user enters an area, diameter, or circumference into a designated <input type="text"> box.
The three text boxes are named area, diameter, and circumfrence (sic), respectively.
The value of each box is initialized to 0.
The boxes are the first three controls of an unnamed <form> having an unnecessary method="post" attribute. (Nope, we won't be submitting any name/value pairs to a processing agent.)
The boxes occupy cells[1] cells in the right-hand column of a four-row, two-cells-per-row <table>; the flanking cells[0] cells hold Area:, Diameter:, and Circumfrence: labels.

Other form controls in the table's last row:
The rows[3].cells[0] cell holds a push button that triggers a help( ) metafunction.
The rows[3].cells[1] cell holds an push button that triggers an about( ) metafunction
and a reset button.

Above the table we've got a big Circle Math <h1> heading (it's a Circle Calculator <h2> heading at the /JSBook/circle.html page); below the table we've got an <hr> horizontal rule and a Script By ~`~ Spikeman ~`~ authorship credit bearing a dead link to http://www.geocities.com/BourbonStreet/3843/.

Style notes:
The table cell content and the display as a whole are horizontally <center>ed; the text box labels are <b>olded; the authorship credit at the end is <i>talicized. All of the styling elements lack end tags.
(The authorship credit is unsemantically also marked up with an unclosed <h4>, which effectively bolds it and pushes it away from the <hr> a bit.)

The metafunctions

Clicking the button

<input type="button" value="Help" onclick="help( );">

calls a help( ) function

function help( ) { window.alert(" Help for Spikeman\'s JavaScript Circle Math For \n Area & Diameter & Circumfrence \n\n Put in a Number into One of the box\'s \n Then Click in one of the other box\'s \n And you will get the values of that number. \n "); }

that displays on an alert( ) box the following message:



help( ) message minutiae:
(i) There's no need to escape those apostrophes if the alert( ) argument is delimited with double quotes.
(ii) The message lines are separated by \n newlines.
(iii) Firefox, Google Chrome, Opera, and Safari render the prepended white space at the beginning of a line: consecutive space characters are not collapsed as they would be in HTML; if there's just one space there, you'll get that space. Conversely, the appended white space at the end of a line may or may not be rendered, depending on the browser.
(iv) The second line is indented with a string of 12 spaces so as to give a centering effect; in practice on the alert( ) box, the line seems to begin with 7-8 spaces because alert( ) text is rendered in a sans-serif font.

As for the "help" that the help( ) message provides:
(a) The message implies that the calculator action is mediated by onclick event handlers; we'll see below that onchange event handlers actually do the honors. (FYI: We couldn't use onclick with the client-side Text object back in the day anyway.)
(b) Inputting a (new, changed) value into the area, diameter, or circumfrence box
and then clicking one of the other boxes
will output values in both of the other two boxes
and not just in the box that was clicked on.

Moving on, the button

<input type="button" value="About" onclick="about( );">

similarly connects us to an about( ) function

function about( ) { window.alert(" About for Spikeman\'s JavaScript Circle Math For \n Area & Diameter & Circumfrence \n\n This Was Made to help people get the Area,Diameter or \n Circumfrence with one of values.. \n "); window.alert("\n Spikeman\'s Circle Math For \n Area & Diameter & Circumfrence \n\n � 1997 Spikeman \n spikeman@myself.com\nhttp://www.geocities.com/BourbonStreet/3843"); }

that displays two alert( ) messages in succession:



Regarding the second message:
• The replacement character replaces a © copyright symbol, which should be coded with a \u00a9 Unicode escape sequence.
• Don't know 'bout the spikeman@myself.com email address but, as noted above, the http://www.geocities.com/BourbonStreet/3843 resource is no longer available.

The metafunctionality is a quaint extra - is it worth holding onto? Maybe, although I myself would put
any explanatory and contact information on the page and
any authorship information in the source
rather than on one or more alert( ) boxes.

Turn and face the strange onchanges

I didn't give you the code for the area/diameter/circumfrence fields earlier so let me do so now:

<form method="post"> ...
<input type="text" name="area" value="0" onchange="circle(this.form, this.name);"> ...
<input type="text" name="diameter" value="0" onchange="circle(this.form, this.name);"> ...
<input type="text" name="circumfrence" value="0" onchange="circle(this.form, this.name);">


Each field has an onchange attribute that invokes a circle(this.form, this.name) function call when the field loses focus and its value="0" has been modified; in the present context, a field's focus can be lost by clicking one of the other fields per the help( ) message (vide supra) although it can also be lost by clicking at any other external point inside or outside the table or by bing away from the field via the keyboard.

The onchange interface is too unintuitive for my taste: I'd much rather interact with a
<button type="button" onclick="circle(this.form, this.name);">Calculate the Remaining Terms</button> button
and I'll provide such a button when I roll out my own Circle Calculator demo at the end of our discussion.

We'll go through the circle( ) function in detail in the following entry.

Friday, March 29, 2019
 
Times of Our World, Part 4
Blog Entry #396

Today's post will conclude our discussion of the Java/JavaScript Goodies World Clock -- Daylight Savings Time by bringing its "Daylight Savings Time" part up to speed, specifically, we will
write standard time ↔ daylight saving time functions for the dstclock locales that need them,
integrate those functions with the rest of the dstclock code, and of course
present a suitably updated dstclock demo.

Before we get rolling, let me note, in the name of completeness, that the toGMTString( ) and toUTCString( ) methods do in fact 'recognize' daylight saving time: if we're in San Francisco and it's a Sunday in July and it's 3 p.m., then the hour part of their returns will be 22. But again, these methods give a GMT/UTC time and not a local time (the Date object has other methods that give a local time, e.g., toLocaleString( )), and local times are what we want for the dstclock clock. To go from a GMT/UTC time to a corresponding local time, we need to know
(1) the number of hours that separates the GMT/UTC time and the local time - check - but also
(2) whether the local locale observes daylight saving time and if so
when the local locale's standard time ↔ daylight saving time transitions occur.
The dstclock code lets us down in the latter respect: it specifies a fixed 8-hour difference between GMT and Pacific Time, a fixed 7-hour difference between GMT and Mountain Time, etc. without taking the time of year into account.

At the time of writing - acknowledging that daylight saving time faces an uncertain future in Europe - the dstclock locales that observe daylight saving time are
San Francisco, Denver, Memphis, New York, London, Paris, and Sydney.
Our standard time ↔ daylight saving time functions for these locales will be largely based on the code samples appearing in the DST to the millisecond section of Blog Entry #103 and the Seize the day section of Blog Entry #314 and can be adapted to any other city that observes daylight saving time: Santiago, Tehran, Auckland, wherever.

American locales

California, Colorado, Tennessee, and New York all conform to the standard* U.S. daylight saving time policy whereby
standard time changes to daylight saving time at 2 a.m. local time on the second Sunday of March and
daylight saving time changes to standard time at 2 a.m. local time on the first Sunday of November
regardless of time zone. (*In this and the next two sections, I use the word "standard" in the sense of "used or accepted as normal or average".)
We can easily determine secondSun and firstSun date numbers for the transition days as follows:

var dstweek, secondSun, dstweek2, firstSun;
dstweek = new Array( );
for (var i = 8; i <= 14; i++) {
    dstweek[i] = new Date(year, 2, i);
    if (dstweek[i].getDay( ) == 0) secondSun = i; }
dstweek2 = new Array( );
for (var i = 1; i <= 7; i++) {
    dstweek2[i] = new Date(year, 10, i);
    if (dstweek2[i].getDay( ) == 0) firstSun = i; }


We next nail down the precise times at which our American locales change from standard time to daylight saving time and vice versa:

function adjust_for_us_dst(hour_offset) {
    /* dstweek, secondSun, dstweek2, firstSun determinations */
    if (secondSun < 10) "0" + secondSun;
    var HH, dstbegins, dstends;
    HH = 2 - hour_offset;
    if (HH < 10) "0" + HH;
    dstbegins = new Date(year + "-03-" + secondSun + "T" + HH + ":00:00.000Z");
    dstends = new Date(year + "-11-0" + firstSun + "T0" + (HH - 1) + ":00:00.000Z"); ...

var hour_offset;
for (var i = 0; i < localeTds.length; i++) {
    hour_offset = gmtoffsetsArray[i];
    if (0 <= i && i <= 3) hour_offset = adjust_for_us_dst(hour_offset);
    locale_hour = hour + hour_offset; ... }


For the collection of the localeTds, the creation of the gmtoffsetsArray, and the rest of the i < localeTds.length; loop, see the Code consolidation section of the previous post.

As noted earlier, American standard time ↔ daylight saving time transitions are pegged to a local time, and therefore occur at different moments in time depending on the time zone:
New York springs forward at 07:00 UTC and falls back at 06:00 UTC,
Memphis springs forward at 08:00 UTC and falls back at 07:00 UTC, etc.

We want to code the transitions in a locale-independent way so that all users everywhere (vis-à-vis users in the dstclock locales) can make use of them; creation of the dstbegins and dstends Dates is best carried out via the
new Date(dateString);
constructor syntax versus the
new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);
constructor syntax, which confines us to the user's local time.
(Use of the latter syntax would require us to separately cater to every time zone in the world - don't know 'bout you, but I would really rather not do that.)

The dstbegins and dstends dateStrings comply with the
YYYY-MM-DDTHH:mm:ss.sssZ
ECMAScript Date Time String Format.
Note that the MM month numbers - e.g., 03 for March - are one higher than the corresponding getMonth( ) indexes. The Zs at the end effectively place us in the UTC±0 time zone.

We lastly compare the date millisecond count with the dstbegins and dstends millisecond counts: for daylight saving time, the date count is greater than or equal to the dstbegins count AND is less than the dstends count, and if that's what we've got, then the locale hour_offset is duly pushed forward.

if (dstbegins <= date && date < dstends) hour_offset += 1;
return hour_offset; } // End of the adjust_for_us_dst( ) function


As a Date object represents a single moment in time, the date Date can be deployed as is in the preceding conditional for any user in any time zone, and does not need to be 'corrected' in any way.

FYI: The above code is also good to go for Anchorage or Fairbanks (hour_offset = -9), Alaska being the one part of the non-conterminous U.S. that observes daylight saving time.

European locales

The U.K. and France conform to the standard European daylight saving time policy whereby
standard time changes to daylight saving time at 01:00 UTC on the last Sunday of March and
daylight saving time changes to standard time at 01:00 UTC on the last Sunday of October
regardless of time zone. We can accordingly reset the hour_offsets for London and Paris during daylight saving time with:

function adjust_for_eu_dst(hour_offset) {
    var dstweek, lastSun, dstweek2, lastSun2, dstbegins, dstends;
    dstweek = new Array( );
    for (var i = 25; i <= 31; i++) {
        dstweek[i] = new Date(year, 2, i);
        if (dstweek[i].getDay( ) == 0) lastSun = i; }
    dstweek2 = new Array( );
    for (var i = 25; i <= 31; i++) {
        dstweek2[i] = new Date(year, 9, i);
        if (dstweek2[i].getDay( ) == 0) lastSun2 = i; }
    dstbegins = new Date(year + "-03-" + lastSun + "T01:00:00.000Z");
    dstends = new Date(year + "-10-" + lastSun2 + "T01:00:00.000Z");
    if (dstbegins <= date && date < dstends) hour_offset += 1;
    return hour_offset; }

// In the i < localeTds.length; loop:
if (i == 4 || i == 5) hour_offset = adjust_for_eu_dst(hour_offset);


The standard time ↔ daylight saving time transitions occur at the same moments in time for all European countries that observe daylight saving time and therefore it is unnecessary to parameterize the HH part of the dstbegins and dstends dateStrings - Lisbon, Berlin, Helsinki, we're all set.

Australian locales

New South Wales conforms to the standard Australian daylight saving time policy whereby
standard time changes to daylight saving time at 2 a.m. local time on the first Sunday of October and
daylight saving time changes to standard time at 3 a.m. local time on the first Sunday of April
regardless of time zone. We can accordingly reset the hour_offset for Sydney during daylight saving time with:

function adjust_for_oz_dst(hour_offset) {
    var dstweek, firstSun, dstweek2, firstSun2, dstbegins, dstends;
    dstweek = new Array( );
    for (var i = 1; i <= 7; i++) {
        dstweek[i] = new Date(year, 9, i);
        if (dstweek[i].getDay( ) == 0) firstSun = i; }
    dstweek2 = new Array( );
    for (var i = 1; i <= 7; i++) {
        dstweek2[i] = new Date(year, 3, i);
        if (dstweek2[i].getDay( ) == 0) firstSun2 = i; }
    dstbegins = new Date(year + "-10-0" + firstSun + "T02:00:00.000+10:00");
    dstends = new Date(year + "-04-0" + firstSun2 + "T02:00:00.000+10:00");
    if (dstbegins <= date || date < dstends) hour_offset += 1;
    return hour_offset; }

// In the i < localeTds.length; loop:
if (i == 9) hour_offset = adjust_for_oz_dst(hour_offset);


The +10:00 substring at the end of the dstbegins and dstends dateStrings effectively places us in the UTC+10 time zone; pegging dstbegins and dstends to UTC itself would in this case require us to parameterize the MM and DD parts of the dateStrings in order to accommodate years in which 1 October or 1 April falls on a Sunday.

The preceding function presumes that we are working with Sydney's hour + 10 standard-time GMT offset rather than the hour + 11 offset that appears in the original code;
we can keep the original offset
and have the function take us from daylight saving time to standard time
if we recast the function's hour_offset adjustment as:

if (dstends <= date && date < dstbegins) hour_offset -= 1;

"You titled this section Australian locales. What about Adelaide, mate?"

The standard time ↔ daylight saving time transitions for Adelaide (hour_offset = 9.5) occur 30 minutes after those for Sydney, Canberra, Melbourne, and Hobart: you can bring Adelaide into the mix via parameterizing either HH:mm part of the dstbegins and dstends dateStrings, e.g.,
year + "-10-0" + firstSun + "T02:00:00.000+" + HHmm;
where
var HHmm = hour_offset == 9.5 ? "09:30" : "10:00";.

Demo

It's time for that demo I promised you - check the page source for the full coding.

Your local time is Times listed in the following table are based on your computer's time. If your computer's clock is wrong, so is this table.
San Francisco
Denver
Memphis
New York
London
Paris
Moscow
Bangkok
Tokyo
Sydney

The next Calculators, Calendars, and Clocks item is Circle Calculator, which looks as though it's a bit of a retread
but I'll go through it and if I find anything unusual I'll report back to you.

Sunday, March 10, 2019
 
Times of Our World, Part 3
Blog Entry #395

With the World Clock -- Daylight Savings Time's San Francisco particulars now under our belt, here are some...

Brief notes on the rest of the locale code

One of these offsets is not like the others

The GMT offsets for the remaining locales are:
Denver: hour - 7			London: hour + 0			Bangkok: hour + 7
Memphis: hour - 6		Paris: hour + 1			Tokyo: hour + 9 
New York: hour - 5		Moscow: hour + 3		Sydney: hour + 11
All are OK for standard time except the one for Sydney, whose standard-time GMT offset is actually hour + 10. Not all of Australia observes daylight saving time but New South Wales does, and we can make use of the hour + 11 offset if we want to, depending on the details of the standard time ↔ daylight saving time function we use for Sydney (vide infra).

When I'm twenty-four

Suppose we're in San Francisco and it's a Sunday in January and it's 3 p.m. (sf_hour = 15), for which the par_hour in Paris

var par_hour = hour + 1;

is 24. The Paris day and time display should read

Monday
12:00 a.m.


but in practice it reads

Sunday
12:00 p.m.


A miscoded

if (par_hour > 24) {
    par_hour -= 24;
    par_week += 1; }


conditional employs a >24 comparison to flag the p.m./a.m. boundary when it should use a >23 comparison to do so;
because the par_hour is not greater than 24,
it is not initially dropped to 0 by the -= 24 subtraction and therefore the par_ampm is toggled to  p.m.

if (par_hour > 11) { par_ampm = " p.m."; }

and the par_week is not moved forward a day calendar-wise.
The London, Moscow, Bangkok, Tokyo, and Sydney code units all contain such a conditional although it doesn't cause a problem for those locales in this case because
the London lon_hour (23) is below the 24 threshold whereas
the Moscow mos_hour (26), Bangkok ban_hour (30), Tokyo tok_hour (32), and Sydney syd_hour (34) are all pushed above the 24 threshold by their GMT offsets.
The >24 comparison does cause a problem for those locales when their own *_hours are 24, however, so we of course need to change all those >24s to >23s.

Code consolidation

Upon arraying the dstclock clock's table cells

<table id="worldclockTable" border="1"> ...One row or three, the command below gets/arrays all 10 <td>s... </table>

var localeTds = document.getElementById("worldclockTable").getElementsByTagName("td");


and its GMT offsets

var gmtoffsetsArray = [-8, -7, -6, -5, 0, 1, 3, 7, 9, 10];

and upon generalizing the *_hour, *_week, and *_ampm variables

var locale_hour, locale_week, locale_ampm;

we can merge the San Francisco → Sydney code commands in both the dstclock head and body into a single statement block and automate their execution.

for (var i = 0; i < localeTds.length; i++) {
    locale_hour = hour + gmtoffsetsArray[i];
    locale_week = week;
    ...


You may have noticed that there are no functions in the dstclock code. I first consolidated the San Francisco → Sydney code units via a getLocaleDayTime(gmtoffset, td_index) { ... } function before recognizing that a top-level for loop could give us what we want in a simpler way.

If we recast the weekly day Array as

var weekly = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; // Per getDay( )'s indexing

then we can generalize the p.m./a.m. boundary code with

// For San Francisco, Denver, Memphis, and New York
if (locale_hour < 0) {
    locale_hour += 24;
    locale_week = ! locale_week ? 6 : locale_week - 1; }
// For London, Paris, Moscow, Bangkok, Tokyo, and Sydney
if (locale_hour > 23) {
    locale_hour -= 24;
    locale_week = locale_week == 6 ? 0 : locale_week + 1; }
// The ?: conditional operator is documented here.


With GMT-pegged locale_hours in hand we determine the locales' a.m./p.m. designations

locale_ampm = locale_hour > 11 ? " p.m." : " a.m.";

and then move the 13-23 and 0 locale_hours to the 12-hour scale

if (locale_hour > 12) locale_hour -= 12;
if (locale_hour == 0) locale_hour = 12;


and finally assemble the day and time displays and append them to the San FranciscoSydney locale identifiers.

localeTds[i].innerHTML += weekly[locale_week] + "<br>" + locale_hour + ":" + min + locale_ampm; } // End of loop

Speaking of those locale identifiers, could we array them and loop them into, say, font-weight:bold;-ed <span>s? Why, yes, we could do that too.

Stuart did not have a cross-browser/platform way to array the table cells at his disposal - without getting into the details, IE 4 for Windows users could have done so via the document.all mechanism but Netscape/Mac users would have been out of luck - although he could have
loaded the locale displays into text inputs (as was typically done with such scripts back in the day) and
automated the loading action via the Form object's elements property.
Separately writing the displays as #PCDATA/<br> content to the page did at least give him the option of styling them in various ways (e.g., color them with the font element, render them in a monospace font via the tt element) although he chose not to do that.
We've got one last issue to address before rolling out a demo, that being daylight saving time, and we'll hit it in detail in the following entry.

Saturday, February 23, 2019
 
Times of Our World, Part 2
Blog Entry #394

Let's get back now to our discussion of the Java/JavaScript Goodies World Clock -- Daylight Savings Time. In the previous entry we ticked through the dstclock clock's foundational JavaScript, preceding metatext, and HTML scaffolding; in today's post we'll unwind the clock's day and time displays. Three Clock origin values are relevant to those displays:
(w) the week string,
(h) the hour number, and
(m) the min string.

Day prep

The week string contains a three-letter day-of-the-week abbreviation plus a comma:
Sun, or Mon, or ... Sat,

The week day is the day in London. If it's Sunday in London, then it could be Monday in Sydney or Saturday in San Francisco, so we'll need to determine the week + 1 and week - 1 days in addition to the week day. Toward this end, Stuart initially maps the week value to an index number that's one higher than the corresponding getDay( ) index:

if (week == "Sun,") { week = 1; }
if (week == "Mon,") { week = 2; }
if (week == "Tue,") { week = 3; }
if (week == "Wed,") { week = 4; }
if (week == "Thu,") { week = 5; }
if (week == "Fri,") { week = 6; }
if (week == "Sat,") { week = 7; }


The week index is subsequently lowered by one for those users who are using a Macintosh:

// Fix Mac version Communicator bug
function checkOS( ) {
    if (navigator.appVersion.indexOf("Mac") > 0) return "Macintosh";
    else return "other"; }
var check = checkOS( );
if (check == "Macintosh") { week -= 1; }


• If there's a reason to use this code rather than just an
if (navigator.appVersion.indexOf("Mac") > 0) week -= 1;
statement, I don't see it.

• I myself would have reached for the navigator object's platform property to flag Macintosh users (not that we want to be 'platform sniffing' if we can help it, of course) although it is clear from JavaScript Kit's Navigator Object page - see its Additional browsers' Navigator Information subsection - that a navigator.appVersion probe will do the job.

There's no mention of any Macintosh support issues in the toGMTString( ) entry in the JavaScript 1.2 Reference. As Netscape's Communicator/Navigator 4.0-4.05 browsers have a JavaScript 1.2 engine, I downloaded Navigator 4.05 for the Macintosh from OldVersion.com and ran a
window.alert((new Date(2019, 2, 1)).toGMTString( ).split(" ")[0]);
command with it, and the alert( ) box that popped up had a Sat, on it even though 1 March 2019 is a Friday, so there you have it.

We're almost ready to start writing out the locale displays: the missing link between
the full day names in those displays and
the week index
is a weekly Array whose creation follows the checkOS( ) check.

weekly = new Array("Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday");

(Nine elements? Duplicate Saturday and Sunday values? We'll clean this up later.)

More yesteryear matters, in the name of completeness

In his authorship comment, Stuart states that the dstclock code only works with Communicator 4, but should be gracefully ignored by lesser browsers. Actually, there are no features in the dstclock code that are specific to JavaScript 1.2, and I find that
if the week string → index number conversions are formulated as an if ... else if ... else if ... chain
AND
if the script language="javascript1.2" attributes are converted to language="javascript" attributes
then Navigator 3.04, which has a JavaScript 1.1 engine, runs the code without incident.

A bit of sleuthing, with some help from this Netscape onError example, revealed that Navigator 3.04 can't handle the original code for an unexpected reason: once an index number is assigned to week, the subsequent (elseless)
if (week == "Three-letter-day-of-the-week-abbreviation,") comparison
throws a "[day] is not a number" type-conversion error that propagates through the rest of the <script>. If the date object's day is a Friday, then no errors are thrown because the final
if (week == "Sat,") week = 7;
conditional is operative, as it would be for Navigator 4.05 (vide supra).

As it happens, however, Navigator 4.05 and Navigator 3.04 have another toGMTString( ) problem besides the dayday + 1 problem: the time part of the toGMTString( ) return gives the local time and not the time in London, or at least that's what I see in the SheepShaver environment on my computer, for example, for my California locale (GMT - 8)
(new Date(2019, 2, 1)).toGMTString( )
returns
Sat, 02 Mar 2019 00:00:00 GMT
rather than
Fri, 01 Mar 2019 08:00:00 GMT.

Anyway, it's not worth my while (or your while, or anyone else's while) to determine whether the time discrepancy is a browser bug or a SheepShaver artifact in that we've spent enough time with these ancient browsers as it is and we really ought to be leaving them behind, yes? We assume the use of a current browser in the following sections.

If you're going to San Francisco

The day and time display show is ready to hit the road. Here's what we've got for San Francisco:

// In the dstclock head
var sf_hour = hour - 8;
var sf_week = week;
var sf_ampm = " a.m.";
if (sf_hour < 0) {
    sf_hour += 24;
    sf_week -= 1; }
if (sf_hour > 11) { sf_ampm = " p.m."; }
if (sf_hour > 12) { sf_hour -= 12; }
if (sf_hour == 0) { sf_hour = 12; }

<!-- In the dstclock body -->
<td align="center" valign="top"><script language="javascript1.2"><!-- Hide it
document.write("<b>San Francisco</b><br>");
document.write(weekly[sf_week] + "<br>");
document.write(sf_hour + ":" + min + sf_ampm + "<br>");
// --></script></td>


The var sf_hour = hour - 8; line subtracts 8 from the hour in London to give the sf_hour in San Francisco for standard time, but there's no - 7 subtraction for daylight saving time, which is a pretty serious omission given that DST lasts more than half the year.

The var sf_week = week; line gives San Francisco the same day index that London has. If the London hour is in the 0-7 range, then San Francisco will calendar-wise be a day behind London: in this situation
(h) the hour - 8 subtraction gives a negative sf_hour, which is converted to the corresponding 12-hour clock hour by + 24 and - 12 operations, and
(w) the sf_week index is pushed back by one.
The GMT-pegged sf_week is then plugged into the weekly[ ] Array to get the full day name of the display.

An appropriate a.m./p.m. indicator is derived from the sf_hour and assigned to an sf_ampm variable à la the metatext code; moreover, the sf_hour is set to 12 for the midnight to 1 a.m. hour this time.

The day and time, plus a <br> line break that separates them, are written to the San Francisco table cell via separate document.write( ) commands (the concluding <br> in the second write( ) command serves no purpose and can be removed); we'll replace those commands with a tdObject.innerHTML += localeString assignment in due course.

For the last eight hours of Saturday the weekly[sf_week] day is undefined for Mac users as
the initial week index is decreased from 1 for Sunday in London to 0 by the checkOS( ) check and
the initial sf_week index is decreased from 0 to -1 by the if (sf_hour < 0) { ... sf_week -= 1; } conditional;
commenting out the checkOS( ) check changes the undefined to the Saturday we want.
Heading east we've got some serious redundancy on our hands: the dstclock source contains
separate units of code for Denver, Memphis, and New York that
have different hour - x GMT offsets
and of course different variable names (den_hour, mem_hour, etc.)
but are otherwise identical to the above San Francisco code
followed by separate units of code for London, Paris, Moscow, Bangkok, Tokyo, and Sydney that are only slightly more different...
We'll consolidate the lot of it, and sort out the DST thing, in the next post.


Powered by Blogger

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