reptile7's JavaScript blog
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.


Powered by Blogger

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