reptile7's JavaScript blog
Saturday, February 09, 2008
 
Shifting Light from Morning to Afternoon
Blog Entry #103

We return now to our deconstruction of the "World Clock" script of HTML Goodies' JavaScript Script Tips #87-90. We pick up the conversation at the checkDateTime( ) function's daylight-saving time (DST) block:

/* This next tidbit gets the last Saturday in the month of Oct, for daylight-saving time purposes */
var lastSat;
lastSat = date - (day + 1);
while (lastSat < 32) {
lastSat += 7; }
if (lastSat > 31) lastSat += -7;

// This bit grabs the first Saturday in April for the start of daylight time
var firstSat;
firstSat = date - (day + 1);
while (firstSat > 0) {
firstSat += -7; }
if (firstSat < 1) firstSat += 7;

// Adjust for Windows95 daylight-saving time changes
if ((((month == 4) && (date >= firstSat)) || month > 4) && (month < 11 || ((month == 10) && day <= lastSat))) { adjust += 60; }

yourOffset = (new Date( )).getTimezoneOffset( );
yourOffset = yourOffset + adjust;

References
(1) date is the new Date( ).getDate( ) return
(2) day is the new Date( ).getDay( ) return
(3) The while statement
(4) Comparison operators (<, >, ==, >=, <=)
(5) Assignment operators (=, +=)
(6) Logical operators (&&, ||)

Validation note: Scripts containing one or more < and/or & characters should be externalized for XHTML-compliance.

The Script Tips #87-90 Script was written in the 1987-2006 period during which DST in the United States began on the first Sunday of April and ended on the last Sunday of October. Today, however, DST in the U.S. runs from the second Sunday of March to the first Sunday of November. (I should mention at this point that Hawaii and most of Arizona do not observe DST.) Accordingly, we'll first go over the script's DST code and then I'll give you my own code that brings it up to date.

The above code's first "tidbit" is meant to determine the date of the last Saturday in October for the current year, and it would do that if the current month were October, but in practice this code block determines the date of the last Saturday of the current month regardless of what month it is, at least for those months that have 31 days; this date will be assigned to the variable lastSat. Here's what happens:

var lastSat;
lastSat = date - (day + 1);

We first declare lastSat. Subsequently, a date - (day + 1) expression determines the date of the Saturday prior to the current date; this Saturday date is assigned to lastSat. For example, I'm writing today on 3 February 2008, a Sunday whose day return is 0 and whose prior Saturday was 3 - (0 + 1) = 2 February (yesterday).
(It might not be intuitively obvious to you that the date - (day + 1) value would necessarily correspond to a Saturday - it wasn't to me initially - but if you substitute various date/day values into this expression, you'll see that it checks out.)

while (lastSat < 32) lastSat += 7;

A while loop adds multiples of 7 to lastSat until lastSat is greater than 31. If lastSat is initially 2:
(1-5) For the loop's first, second, third, fourth, and fifth iterations, the loop condition returns true, and lastSat is increased from 2 to 9 to 16 to 23 to 30 to 37;
(6) At the beginning of what would be the loop's sixth iteration, the loop condition returns false, so the browser moves on to...

if (lastSat > 31) lastSat += -7;

The if condition returns true and lastSat is decreased to 30, its final value. Relatedly, if this year's April, June, September, or November were to begin on a Thursday (none of them do), then the final lastSat value for these months would be 31. However, we'll see in a moment that these seemingly erroneous values don't cause any problems vis-à-vis determining whether the user (at least an American user, for whom the script is clearly intended) is or is not on DST.

The DST code's next sub-block similarly determines the date of the first Saturday of the current month regardless of what month it is, for all twelve months.

var firstSat; firstSat = date - (day + 1);

À la the previous sub-block, we first declare the variable firstSat and initially assign thereto the date of the Saturday prior to the current date.

while (firstSat > 0) firstSat += -7;
if (firstSat < 1) firstSat += 7;

A while loop subtracts multiples of 7 from firstSat until the firstSat value falls below 1, at which point the loop stops and the subsequent if statement adds 7 to firstSat. If date - (day + 1) is 2 per the discussion above, then the while loop drops firstSat to -5 and the if statement brings firstSat back up to 2.

Next we have a conditional that tests if the user (again, an American user) is on DST:

// Adjust for Windows95 daylight-saving time changes
if ((((month == 4) && (date >= firstSat)) || month > 4) && (month < 11 || ((month == 10) && day <= lastSat))) { adjust += 60; }

If the if condition returns true, then the variable adjust, set to 0 at the beginning of the checkDateTime( ) function, is increased by 60. In plain English, the if conditional says,
"If the current month is May, June, July, August, September, or October, or
if it's April and the current date is or is later than the first Saturday thereof, or
if it's October and the current date is or is earlier than the last Saturday thereof, then
add 60 to adjust because it's currently DST."
The if condition would without incident return true for an April 31, June 31, or September 31, and false for a February 30 or November 31.

Even if the U.S. DST period had not changed in 2007, however, the preceding if condition would be problematic in several respects:
(1) The first Saturday of April and also the first two hours of the first Sunday of April, which formerly belonged to standard time, would be mistaken for DST.
(2) The month < 11 mini-condition should be month < 10.
(3) If October's lastSat is 31, then DST would actually end on 25 October (the prior Sunday) and almost an entire week's worth of standard time would be mistaken for DST.
(4) If October's lastSat is not 31, then the first two hours of the following Sunday, which formerly belonged and still currently belong to DST, would be mistaken for standard time.
We'll sort out these problems later, but let's address another matter for the time being: Why would we want to add 60 to adjust? We move to the DST block's last two lines:

yourOffset = (new Date( )).getTimezoneOffset( );
/* Instead of 'reconstructing' the Date object, you can use the today Date object here, i.e.: yourOffset = today.getTimezoneOffset( ); */
yourOffset = yourOffset + adjust;

The getTimezoneOffset( ) method of the Date object returns the number of minutes between the user's local time and GMT (Greenwich Mean Time, a.k.a. UTC - OK, GMT and UTC are not exactly the same, but what's a few leap seconds among friends?). The getTimezoneOffset( ) return is a positive number for the UTC-x time zones of the Western hemisphere and is a negative number for the UTC+x time zones that mostly lie in the Eastern hemisphere. For example, I live in New Orleans, which is located in the UTC-6 time zone, and thus new Date( ).getTimezoneOffset( ) returns 360 (60 minutes/hour × a 6-hour offset from UTC) on my computer during standard time.

But now things get a bit confusing. The getTimezoneOffset( ) return, assigned here to the variable yourOffset, is, according to Mozilla's documentation, supposed to take DST into account, which evidently requires the user's operating system to have some sort of 'set daylight-saving time automatically' capability. The // Adjust for Windows95 daylight-saving time changes comment preceding the above DST conditional suggests that the Windows 95 operating system does not have such a capability, i.e., the user must manually adjust the Windows 95 clock for DST - I don't have access to, nor do I plan to track down, a computer running Windows 95, and it is left to those interested to verify whether this is true or not - so for the benefit of Windows 95 users (for whom the script is also apparently intended), adjust (60) is added to yourOffset. However, for those users on DST, DST decreases by an hour the time difference between UTC and the local time - e.g., during DST in New Orleans, the time is UTC-5 and the getTimezoneOffset( ) return should be 300 - and thus adjust should really be subtracted from yourOffset if yourOffset is meant to represent the user's actual offset from UTC.

The script also adds adjust to the standard time UTC offsets* of the script's various display locations that are set globally at the top of the script element, even for those locations that do not currently observe DST, e.g., Tokyo:

var TK = -540; // Japan is located in the UTC+9 time zone.

function checkTK( ) {
window.clearTimeout(checkDateTime);
gmtOffset = TK + adjust;
zone = " Tokyo";
checkDateTime( ); }

*This would seem to put the kibosh on my thought that maybe the Windows 95 getTimezoneOffset( ) return during DST is correct, and the script's author is using the yourOffset + adjust expression to give a standard time offset that can be properly compared with the gmtOffset = 360 standard time offset for the script's initial U.S. Central Time display.

Clearly, the script's DST code could use a good revamping, wouldn't you say?

DST to the millisecond

It occurred to me that DST 'goalposts' can be created more efficiently and more precisely via the
new Date(dateString); and
new Date(year, month, date [, hour, minute, second, millisecond ]);
syntaxes for the Date object, as follows:

var today, year, dstweek, secondSun, dstweek2, firstSun, dstbegins, dstends;

today = new Date( );
year = today.getFullYear( );

dstweek = new Array( );
for (i = 8; i < 15; i++) {
dstweek[i] = new Date("March " + i + ", " + year);
if (dstweek[i].getDay( ) == 0) secondSun = i; }

dstweek2 = new Array( );
for (i = 1; i < 8; i++) {
dstweek2[i] = new Date("November " + i + ", " + year);
if (dstweek2[i].getDay( ) == 0) firstSun = i; }

dstbegins = new Date(year, 2, secondSun, 2, 0, 0, 0);
dstends = new Date(year, 10, firstSun, 2, 0, 0, 0);

if (dstbegins < today && today < dstends) gmtOffset -= 60;

As noted earlier, the current U.S. DST period starts on the second Sunday of March, which is necessarily 8, 9, 10, 11, 12, 13, or 14 March. We begin by creating an array dstweek of these dates for the current year and then use the getDay( ) method to flag which of those dates is a Sunday; that Sunday date is assigned to the variable secondSun. The current U.S. DST period ends on the first Sunday of November, which is necessarily 1, 2, 3, 4, 5, 6, or 7 November. We next create an array dstweek2 of these dates for the current year and use the getDay( ) method to flag which of those dates is a Sunday; that Sunday date is assigned to the variable firstSun.

For the secondSun and firstSun dates, respectively, DST begins and ends at exactly 2AM local time, which can be set to the millisecond via:

dstbegins = new Date(year, 2, secondSun, 2, 0, 0, 0);
dstends = new Date(year, 10, firstSun, 2, 0, 0, 0);

Finally, because [a Date object] date is measured in milliseconds since midnight 01 January, 1970 UTC, we can use simple comparisons to determine if the current date/time, today, lies in the DST period:

if (dstbegins < today && today < dstends) gmtOffset -= 60;

The above approach is easily extended to the DST periods of other countries. For example, in New Zealand DST begins at 2AM local time on the last Sunday of September and ends at 3AM local time on the first Sunday of April, and thus the corresponding code is:

dstweek = new Array( );
for (i = 24; i < 31; i++) {
dstweek[i] = new Date("September " + i + ", " + year);
if (dstweek[i].getDay( ) == 0) lastSun = i; }

dstweek2 = new Array( );
for (i = 1; i < 8; i++) {
dstweek2[i] = new Date("April " + i + ", " + year);
if (dstweek2[i].getDay( ) == 0) firstSun = i; }

dstbegins = new Date(year, 8, lastSun, 2, 0, 0, 0);
dstends = new Date(year, 3, firstSun, 3, 0, 0, 0);

if (dstbegins < today || today < dstends) gmtOffset -= 60;
// Note the change in logical operators!

Regarding the script's other display locations:
In London (and in the rest of Europe except Iceland, for that matter), DST begins at 01:00 UTC on the last Sunday of March and ends at 01:00 UTC on the last Sunday of October.
Mexico's DST period is the same as that observed in the U.S. during 1987-2006, beginning at 2AM local time on the first Sunday of April and ending at 2AM local time on the last Sunday of October.
I trust y'all can write corresponding DST blocks for these locations.
• Tokyo, Hong Kong, Hawaii, and New Delhi do not observe DST.

In the following entry, we'll detail the generation of the U.S. Central Time time/date display that initially appears in the face text box.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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