reptile7's JavaScript blog
Sunday, March 09, 2008
The World Clock Script: If I Scripted It
Blog Entry #106

Today's post concludes our treatment of the "World Clock" script of HTML Goodies' JavaScript Script Tips #87-90. Below I'll outline some additional changes that I've made to the script in order to create a new-and-improved world clock - you can follow this link to see my clock in the post's "Demo" section.

In the "Let's go to Hong Kong" section of the previous post, we discussed the script's location-specific functions - checkPST( ), checkEST( ), etc. - that provide gmtOffset and zone values to the main checkDateTime( ) function. We've noted that these functions incorrectly adjust the UTC offsets of the script's display locations for daylight-saving time (DST). Moreover but less importantly, it's inefficient to have a separate checkX( ) function for each of the script's location buttons; a group of time zones with the same DST practices (as in the U.S. or in Europe) should be handled by one function if at all possible. Our first order of business, then, is to trade in the original checkX( ) functions for 'better models'.

We sorted out the script's DST problems for the most part in Blog Entry #103's "DST to the millisecond" section, which presented a functional template for correctly carrying out DST adjustments. As for the one-checkX( )-per-button issue, it occurred to me that
(a) the checkX( ) functions can be grouped as needed, and
(b) the checkX( ) gmtOffset and zone values can be set more economically
if we use the script's location buttons to directly feed new Date( ).getTimezoneOffset( ) UTC offsets and zone strings to the generalized checkX( ) functions.

Accordingly, we can for example replace the checkLD( ) function with a EuropeTime( ) function

function EuropeTime( ) {
/* DST runs from 01:00 UTC on the last Sunday in March to 01:00 UTC on the last Sunday in October. */

gmtOffset = arguments[0];
zone = arguments[1];
/* We previously discussed the JavaScript arguments[ ] object in the "Random heterocolor" section of Blog Entry #99. */

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

dstweek2 = new Array( );
for (i = 25; i < 32; i++) {
dstweek2[i] = new Date("October " + i + ", " + year);
if (dstweek2[i].getDay( ) == 0) lastSun2 = i; }

dstbegins = new Date(year, 2, lastSun);
dstends = new Date(year, 9, lastSun2);

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

that by itself handles gmtOffset and zone values for a row of European London, Berlin, Athens, and Moscow buttons:

<td><input type="button" name="reset" value=" London " onclick="EuropeTime(0, this.value);" /></td>
<td><input type="button" name="reset" value=" Berlin " onclick="EuropeTime(-60, this.value);" /></td>
<td><input type="button" name="reset" value=" Athens " onclick="EuropeTime(-120, this.value);" /></td>
<td><input type="button" name="reset" value=" Moscow " onclick="EuropeTime(-180, this.value);" /></td>
<!-- During standard time, London, Berlin, Athens, and Moscow observe UTC+0, UTC+1, UTC+2, and UTC+3, respectively. -->

(My EuropeTime( ) function sets its DST goalposts (dstbegins and dstends) not to the millisecond but to the day, because the local user times in Europe's time zones at which standard time is shifted to DST and vice versa are different; for example, in going from standard time to DST, London shifts at 1AM local time, Berlin and Moscow shift at 2AM local time, and Athens shifts at 3AM local time. You could write a separate function for each European time zone, I suppose, but that rather defeats the idea of merging the checkX( ) functions, doesn't it?)

Analogously, my clock replaces
(a) the checkPST( ) and checkEST( ) functions with a USTime( ) function that handles the Pacific, Mountain, Central, and Eastern Time Zones,
(b) the checkFJ( ) function with a NZTime( ) function that takes care of New Zealand and the Chatham Islands, and
(c) the checkTK( ), checkHW( ), checkHK( ), and checkND( ) functions with a NoDST( ) function that can handle any location that does not observe DST:

function NoDST( ) {
gmtOffset = arguments[0];
zone = arguments[1];
checkDateTime( ); }

As for the global UTC offset statements that the checkX( ) functions act on - var PST = 480, var EST = 300, etc. - we don't need them anymore; chuck them out.

Ditching the hour/date/month/year conditionals, or not

In Script Tip #85, Joe notes that a document.write(Date()) command writes to the page a Thu Mar 6 12:41:55 2008-type string. On my computer, the format of this string is browser-dependent: MSIE 5.1.6 outputs Thu Mar 6 12:41:55 2008 whereas Netscape 7.02 outputs Thu Mar 06 2008 12:41:55 GMT-0600. A document.write(new Date()) command produces an almost-identical or identical string: Thu Mar 6 12:49:05 CST 2008 with MSIE and Thu Mar 06 2008 12:41:55 GMT-0600 with Netscape.

In Blog Entry #103, we saw that the original Script Tips #87-90 Script can sometimes generate erroneous last-Saturday dates: '30 February', '31 April', and so on. Upon plugging such dates into document.write( ) commands, I serendipitously discovered that they are automatically 'corrected' by the browsers on my computer, e.g.:

document.write(new Date(2008, 3, 31));
// MSIE outputs Thu May 1 00:00:00 CST 2008

Further experimentation showed that Date objects with similarly invalid month, hour, and minute parameters are also corrected.

The document.write(new Date()) strings bear an obvious resemblance to the dateTime string that the script displays in the face text field. If the browser corrects Date objects as necessary, then we should be able to directly assign a suitable Point B Date object expression to the face value value and thus dispense with the script's various into-a-new-or-prior hour/date/month/year conditionals, and this proves to be the case:

yourOffset = today.getTimezoneOffset( );
ourDifference = gmtOffset - yourOffset;
dateTime = new Date(year, month, date, hour, minute-ourDifference, second);
document.forms[0].face.value = dateTime;

In the above Date object constructor statement, the user minute is adjusted by ourDifference, the user/Point B time difference in minutes; the browser takes it from there.

However, I prefer the original dateTime because it is a cross-browser/platform string, and consequently I am inclined to leave the conditionals in place. And if we were to keep the conditional code, well, it's a bit of a jumble, isn't it? Is there some way we can clean it up, organize it, make it more readable? Here's how I would formulate the checkDateTime( ) conditional section:

if (half != 0) minute -= half;

if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) var leap = true;
else leap = false;

if (minute > 59) { minute -= 60; hour++; }
if (minute < 0) { minute += 60; hour--; }

if (hour > 23) { hour -= 24; date += 1; }

if ((date == 32 && month != 12)
|| (date == 31 && (month == 4 || month == 6 || month == 9 || month == 11))
|| (date == 29 && month == 2 && leap == false)
|| (date == 30 && month == 2))
{ date = 1; month += 1; }
if (date == 32 && month == 12) { month = 1; date = 1; year += 1; }

if (hour < 0) { hour += 24; date -= 1; }

if (date < 1) {
if (month == 1) { month = 12; date = 31; year -= 1; }
else { date = 31; month -= 1; }
if (month == 4 || month == 6 || month == 9 || month == 11) date = 30;
if (month == 2 && leap == false) date = 28;
if (month == 2 && leap == true) date = 29; }

Not a big difference, I admit.


World Clock

(N.B.: If you're using a browser that will return a standard time getTimezoneOffset( ) value until 6 April (the standard time-to-DST date if the 1987-2006 U.S. DST policy were still in effect), then all of the clock's times will be an hour ahead of the actual times until 6 April (presumably the clock's times will be an hour behind the actual times during 26 October to 2 November). Sorry, there's nothing I can do about this.)

My clock is very much a work in progress, but not a bad start, eh? Most of the world's time zones are covered; conspicuously absent are button rows for South American and African cities.

The clock initially displays the time/date for Reykjavik, Iceland - I wanted a UTC+0 location that does not observe DST.

Australian DST adjustments are handled by two functions: a NSWTime( ) function for cities in New South Wales, South Australia, Victoria, and Tasmania, which observe a common DST policy, and a WATime( ) function for cities in Western Australia, which observes a separate DST policy. (Additional buttons for cities in the Northern Territory and Queensland, which do not observe DST, could be handled by the NoDST( ) function given earlier.)

We'll move on in the next post to the Script Tip #91 Script, which uses the JavaScript event object and event capturing to produce a 'cat chases the mouse' effect.


Comments: Post a Comment

<< Home

Powered by Blogger

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