reptile7's JavaScript blog
Friday, January 05, 2007
 
Strange Days of our Lives
Blog Entry #62

The sky is falling! Set the wayback machine, Sherman, for the heady pre-Y2K period, as someone in the HTML Goodies readership requests a script that will calculate the number of days between a given day and January 1, 2000, a day that, with respect to computer systems everywhere, will bring on The Apocalypse and the end of the world as we know it. Or maybe not. Anyway, Script Tips #29 and #30 provide just such a script; it's posted here and it appears below:

<script name="javascript">
// Script by Jari Aarniala (foo@mbnet.fi)

today = new Date( );
y2k = new Date("January 1, 2000");
daysLeft = (y2k.getTime( ) - today.getTime( )) / (1000*60*60*24);
daysLeft = Math.round(daysLeft);
document.write("<center><i>~~ Only " + daysLeft +
" days until the year 2000. ~~</i></center>");
</script>

A script identifier?

Before getting into the meat of the script, we need to take a hard look at the script element start-tag: <script name="javascript">??

Neither the Strict nor the Transitional HTML 4.01 Document Type Definition (DTD) lists a name attribute in its <!ATTLIST SCRIPT> declaration (in the "Document Head" section, near the bottom); the script's author almost certainly meant to use a language attribute here.

This raises an interesting question: what does a browser do when it encounters invalid HTML? Or at least what should it do? Conveniently, Appendix B of the HTML 4.01 Specification has a "B.1 Notes on invalid documents" section that addresses this very topic; regarding the matter at hand, it says:
"If a user agent encounters an attribute it does not recognize, it should ignore the entire attribute specification (i.e., the attribute and its value)."
Of all the script element's attributes, only the type attribute has a #REQUIRED default value designation, i.e., must always be specified (in theory at least); unfortunately, the B.1 section doesn't say anything about a browser's optimal response to a missing #REQUIRED attribute. In any case, on my iMac both Netscape 7.02 and MSIE 5.1.6 execute the Script Tips #29-30 Script, as is, without incident.

It then occurred to me that if we wanted to name a script element for identification purposes, then we could do so with an id="scriptID" attribute; the HTMLScriptElement Interface of the DOM Level 2 HTML Specification lists five attributes that should then be manipulable by a document.getElementById("scriptID").property="suitable_value" statement, even as I've never seen anyone treat a script element as an object in this way.

The Y2K countdown

The Script Tips #29-30 Script begins straightforwardly enough by creating two Y2K 'goalposts' in the form of Date object instances, one for the current day and one for January 1, 2000:

today = new Date( );
y2k = new Date("January 1, 2000");

As far as I am aware, this is the first time we've encountered a parameterized Date object in any of the HTML Goodies JavaScript Primers or Script Tips. The "January 1, 2000" string can alternately be shortened to "Jan 1, 2000" or expressed numerically using a Date(yr_num,mo_num,day_num) format:

var y2k = new Date(2000,0,1);
// The mo_num parameter runs from 0 (January) to 11 (December).

(The Date object can also take numerical hour, minute, second, and millisecond parameters, and if we wanted to, we could set y2k to the very stroke of midnight, January 1, 2000, by supplying 0's for these parameters, but I think you'd agree that this would be overkill.)

The lion's share of the script's work is carried out by the next command line:

daysLeft = (y2k.getTime( ) - today.getTime( )) / (1000*60*60*24);

Lots going on here - let's expand this statement as follows:

var msLeft = y2k.getTime( ) - today.getTime( );
// or var msLeft = y2k - today;
var msPerDay = 1000*60*60*24;
var daysLeft = msLeft/msPerDay;

Here's what's happening:

(1) The value returned by the getTime( ) method is the number of milliseconds since 1 January 1970 00:00:00, Netscape informs us; consequently:
(a) y2k.getTime( ) returns the number of milliseconds spanning y2k and (midnight) January 1, 1970;
(b) today.getTime( ) returns the number of milliseconds spanning today and (midnight) January 1, 1970;
(c) var msLeft = y2k.getTime( ) - today.getTime( ) returns the number of milliseconds spanning y2k and today.
(Algebraically, this is a case of (a - x) - (b - x) = a - b.)

It is in fact not necessary to use the getTime( ) method here. In its "Description" of the Date object, Netscape states, The date [of a Date object instance] is measured in milliseconds since midnight 01 January, 1970 UTC. It follows that y2k - today also returns the number of milliseconds spanning y2k and today.

(2) In the "What a Difference a Day Makes" section of Script Tip #29, Joe says that the 1000*60*60*24 expression is how you write one day in JavaScript. We need to be a lot more specific here: the var msPerDay = 1000*60*60*24 product gives the number of milliseconds in one day.

Come, let us walk down Memory Lane and write it all out as you might have done in middle school science class:

milliseconds/day calculation

(3) msLeft ÷ msPerDay then gives daysLeft, the number of days spanning y2k and today. The unit conversion pathway is shown by the following equation:

daysLeft calculation

As to why the script uses a millisecond-based calculation, Joe remarks, Well, there is no JavaScript command called 'day' so we need to build a day [in terms of milliseconds]. Actually, the Date object has both a getDate( ) method and a getDay( ) method, but these methods have small return ranges - 1-31 and 0-6, respectively - and are thus not useful vis-à-vis the script's purpose. Consider the following code:

var today = new Date("December 31, 2006");
var y2k = new Date("January 1, 2000");
var daysLeft = today.getDate( ) - y2k.getDate( );

daysLeft returns 30 notwithstanding the seven years separating today and y2k.

Displaying the countdown

The following discussion assumes a pre-y2k today (as was the case when the script was written); we'll address a post-y2k today later.

The script then uses the round( ) method of the Math object to round the daysLeft return to the nearest integer, which is assigned to daysLeft:

daysLeft = Math.round(daysLeft);

The resulting daysLeft day count will include the current day if the user visits the page between 12 AM and 12 PM, but will not include the current day if the user visits the page between 12 PM and 12 AM.

In Script Tip #30, Joe alleges that a

daysLeft = daysLeft - 1;

statement after the Math.round(daysLeft) command will exclude the current day from the daysLeft day count, but this is only true for an AM visit; if a user visits the page between 12 PM and 12 AM, then both the current day and the following day will be excluded from the daysLeft day count. Suppose a user visits the page at 3 PM Monday; the Math.round(daysLeft) command effectively takes the user to 12 AM Tuesday, and then the daysLeft = daysLeft - 1 statement takes the user to 12 AM Wednesday. Both Monday and Tuesday are subtracted from daysLeft.

The current day can be more reliably included in or excluded from the daysLeft day count via the ceil( ) and floor( ) methods of the Math object:
var daysLeft = Math.ceil(daysLeft) will include the current day;
var daysLeft = Math.floor(daysLeft) will exclude the current day.

Finally, a document.write( ) command formats and writes to the page daysLeft plus some surrounding text:

document.write("<center><i>~~ Only " + daysLeft +
" days until the year 2000. ~~</i></center>");

We can replace the center and i elements with style declarations as follows:

document.write("<div style='text-align: center; font-style: italic;'>~~ Only " +
daysLeft + " days until the year 2000. ~~</div>");

(The CSS text-align property applies to block-level elements, and can thus be used with a div element but not a span element, which is an inline element.)

A post-y2k today

For a post-y2k today, the Script Tips #29-30 Script as written returns daysLeft as a negative number, which looks kind of weird on the page; there are at least three simple ways we can convert daysLeft to a positive number (i.e., to its additive inverse):

(1) Via the - unary negation arithmetic operator:

// After the Math.round(daysLeft) command, conclude the script with
daysLeft = -daysLeft;
document.write("<div style='text-align: center; font-style: italic;'>~~ " +
daysLeft + " days have passed since the Y2K catastrophe! ~~</div>");

(2) Via the abs(x) method of the Math object, which returns the absolute value of its argument x:

// As an alternative to daysLeft = -daysLeft, use
daysLeft = Math.abs(daysLeft);

(3) Perhaps most intuitively, we can simply commute the y2k and today subtraction operands in the initial daysLeft calculation:

var daysLeft = (today - y2k) / (1000*60*60*24);

It is left to the reader to verify that
(a) for the first two of these ways, replacing the Math.round(daysLeft) command with Math.floor(daysLeft) ensures that the current day is included in the daysLeft day count whereas replacing Math.round(daysLeft) with Math.ceil(daysLeft) excludes the current day from daysLeft, and
(b) vice versa for the third way.

Other date spans

In its Date object "Description," Netscape also says, The Date object range is -100,000,000 days to 100,000,000 days relative to 01 January, 1970 UTC; it follows that we can apply the Script Tips #29-30 Script to the calculation of the number of days spanning any two days within this ≅550,000-year period, for example:

var today = new Date( );
var liz1 = new Date("November 17, 1558");
var daySpan = (today - liz1) / (1000*60*60*24);
daySpan = Math.ceil(daySpan);
document.write("It was " + daySpan +
" days ago today that the Virgin Queen ascended the throne.");

We'll turn to the Date object and its methods once again, and also revisit the topic of arrays, in the next post as we take on the Script Tips #31-33 Script, which creates and writes to the page a text-based date.

reptile7

Labels:


Comments: Post a Comment

<< Home

Powered by Blogger

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