You are here: irt.org | Articles | JavaScript | Date and Time | JavaScript Y2K Issues [ previous next ]
Published on: Monday 3rd January 2000 By: Martin Webb
Its been long known that there are potential Y2K issues with JavaScript dates.
Creating a date object is fairly straightforward:
<script language="JavaScript"><!-- // 1st January 1999: var date1 = new Date(1999,0,1); // months start at 0 // 1st January 2000: var date1 = new Date(2000,0,1); // months start at 0 //--></script>
For those browsers that support it, it is also possible to create really old dates:
<script language="JavaScript"><!-- // 1st January 99 AD: var date1 = new Date(99,0,1); // months start at 0 //--></script>
Retrieving the year from a date object is a slightly tricker problem. The method getYear() has been implemented differently on various browsers and versions of browsers. Starting with Netscape Navigator 2 and Internet Explorer 3, getYear() always returned a value equal to the number of years since 1900 for the date object, or put another way, the year minus 1900. This results in a one, two or three digit value being returned by getYear():
<script language="JavaScript"><!-- // 1st January 1999: var date1 = new Date(1999,0,1); // months start at 0 var year1 = date1.getYear(); alert(year1); // shows 99 in all browsers // 1st January 2000: var date2 = new Date(2000,0,1); // months start at 0 var year2 = date2.getYear(); alert(year2); // shows 100 in NN2 and IE3 //--></script>
Whereas with the introduction of Netscape Navigator 3 and Internet Explorer 4, getYear() started to return a full four digit number for any year from 2000 onwards:
<script language="JavaScript"><!-- // 1st January 1999: var date1 = new Date(1999,0,1); // months start at 0 var year1 = date1.getYear(); alert(year1); // shows 99 in all browsers // 1st January 2000: var date2 = new Date(2000,0,1); // months start at 0 var year2 = date2.getYear(); alert(year2); // shows 2000 in NN3+ and IE4+ //--></script>
With the introduction of the ECMA-262 ECMAScript specification, browser vendors were required to tidy up this inconsistency. The ECMAScript specification introduced the getFullYear() method, and deprecated the original getYear() method.
<script language="JavaScript1.2"><!-- // Note JavaScript1.2 required // 1st January 1999: var date1 = new Date(1999,0,1); // months start at 0 var year1 = date1.getFullYear(); alert(year1); // always shows 1999 in JavaScript1.2 enabled browsers // 1st January 2000: var date2 = new Date(2000,0,1); // months start at 0 var year2 = date2.getYear(); alert(year2); // always shows 2000 in JavaScript1.2 enabled browsers+ //--></script>
At the same time as adding support for getFullYear() and deprecating getYear(), Netscape reversed the getYear() implementation. They effectively undid the change they made in Netscape Navigator 2 (to return the full year), to the previous implementation which returned the number of years since 1900 - see Year 2000? Not a Problem!.
There are still differences between Netscape Navigator 4 and Internet Explorer 4 and 5. Whereas Internet Explorer 4 and 5 will return negative values for dates less than 1 AD 9, (e.g -1 for 1 BC, -2 for 2 BC etc), Netscape Navigator 4 will not.
So long as there are people using Netscape Navigator 2 and 3 and Internet Explorer 3, there will always be a need to use getYear(), as these browsers obviously do not support getFullYear().
The following JavaScript function has consistently worked to produce Y2K compliant 4 digit years in all browsers/versions:
<script language="JavaScript"><!-- function y2k(number) { return (number < 1000) ? number + 1900 : number; } //--></script>
The y2k() function makes an assumption: any value passed to it which is less than a 1000 is assumed to be a year which has had 1900 subtracted from it, and thus needs 1900 adding back to it. This means that you can't work with years less than 1000 AD - a small price to pay.
To make sure all your years are Y2K compliant invoke the y2k() method with the year passed as a parameter, e.g.:
<script language="JavaScript"><!-- function y2k(number) { return (number < 1000) ? number + 1900 : number; } // 1st January 1999: var date1 = new Date(1999,0,1); // months start at 0 var year1 = y2k(date1.getYear()); alert(year1); // always shows 1999 in all browsers // 1st January 2000: var date2 = new Date(2000,0,1); // months start at 0 var year2 = y2k(date2.getYear()); alert(year2); // always shows 2000 in all browsers //--></script>
It is worth noting that the y2k() function will return values as below:
input output 0 1900 1 1901 . . 999 2899 1000 1000 1001 1001 . . 1899 1899 1900 1900 1901 1901 . . 1999 1999 2000 2000 2001 2001
The setYear() date method suffers from the same problems that getYear() suffers from, albeit it depends on what your code passes as input into setYear(). setYear() accepts 0 to 99 for years 1900 to 1999, and then 2000 for year 2000. It also accepts 1900 to 1999 for years 1900 to 1999.
Again, with ECMA-262 and ECMAScript, the setFullYear() date method was introduced. This accepts an integer,where the integer specifies the year (e.g. 1 equates to 1 AD, 99 equates to 99 AD, 1900 equates to 1900 AD etc.)
As before, Internet Explorer 4 allows negative integers to be input, whereas Netscape Navigator accepts only positive integers.
So far we've been able to create full Y2K compliant years, but this is only possible where we know the correct context for the given date. For example, if we were handed a year (say by a user of our web site) of "99". We have to decide whether this is 1999 AD or 99 AD. Normally we would assume 9as in the y2k() function earlier) that this is 1999, and make adjustments to it accordingly.
There is another instance where we would be presented with a one or two digit year, and that's with the documents lastModified property.
The documents lastModified property is a date string derived from information supplied by the server which states when the document was last modified. The value held in the lastModified property is not a date object, but a string continuing the date and time. This property can be used to provide a visible date stamp on the document itself:
<script language="JavaScript"><!-- document.write('last updated on ' + document.lastModified); //--><script>
This lastModified property suffers from potential Y2K non compliance, and in later browsers than just Netscape Navigator 2 and Internet Explorer 3.
When using document.lastModified in Netscape Navigator from version 2 until 4.5 and with Internet Explorer 3 until 4.x, the date string returned by lastModified always returns a two digit year (e.g. 99 for 1999 and 00 for 2000.)
With Netscape Navigator 4.6 onwards and Internet Explorer 5.0 onwards the year portion of the date string returned by document.lastModified is always a four digit date (e.g. 1999 for 1999 and 2000 for 2000.)
Now this problem wouldn't normally bother us as displaying any one of the two following different date formats would still indicate to the user that the document was last update on the 1st January 2000:
last updated on Sat Jan 1 21:29:05 2000 last updated on Sat Jan 1 21:29:05 00
However, it is possible to use the date string supplied by document.lastModified to create a date object which could be further manipulated to show a more readable date format, e.g.:
Last updated on Saturday 1st January, 2000
To do this we would use JavaScript code similar to:
<script language = 'JavaScript'><!-- function makeArray0() { for (i = 0; i<makeArray0.arguments.length; i++) this[i] = makeArray0.arguments[i]; } var days = new makeArray0('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'); var months = new makeArray0('January','February','March','April','May','June','July','August','September','October','November','December'); function nths(day) { if (day == 1 || day == 21 || day == 31) return 'st'; if (day == 2 || day == 22) return 'nd'; if (day == 3 || day == 23) return 'rd'; return 'th'; } function y2k(number) { return (number < 1000) ? number + 1900 : number; } var date = new Date(document.lastModified); document.write('last updated on ' + days[date.getDay()] + ' ' + date.getDate() + nths(dateY2K.getDate()) + ' ' + months[date.getMonth()] + ', ' + y2k(date.getYear())); //--></script>
Works well? Not quite. The code does use the y2k() function from earlier, but if the document was last modified, say, on 1st January 2000, then on any browser prior to Netscape Navigator 4.6 and Internet Explorer 5.0, the above code would generate something like:
last updated on Monday 1st January, 1900
And on some browsers, it wouldn't even produce that - see Blind Date - 'Features' (AKA bugs) for further information.
The reason being the date string supplied by lastModified only contains a two digit year (00 for 2000), the y2k() function correctly converts this to 1900 for output, but even this does not explain the Monday (when it should have been Saturday.) What has happened is that the generation of the date object using new Date(document.lastModified) has identified the year part of the string as 00, the browser has correctly identified this as 1900, and generated a date object for the 1st of January 1900, a Monday. For browsers that are none to good at generating dates prior to 1970, this may well cause an error, and or cause the resultant date object to be corrupt.
The solution, roll our own code to trap this and adjust accordingly.
The following amended code makes use of the getCorrectedYear() function. It first create a new date object (dateError) using the lastModified property. It uses the getCorrectedYear() function to determine which side of a pivot date the year exists, any number below 70 it treats as a year in the 21st century (e.g. 0 becomes 2000, 69 becomes 2069), any number below 1900 it adds 1900 (e.g. 70 becomes 1970, 99 becomes 1999, 100 becomes 2000 etc), any number greater or equal to 1900 is left alone.
Why 70 as the pivot year? Two reasons, its far enough into the future for 2070 to err to 1970, that most people would no longer be using a non compliant browser, and 1970 is year zero on Unix systems, i.e. dates are calculated as the number of milliseconds since 1970.
Once we have corrected the year using getCorrectedYear(), we generate a further date object (date) so that the day of the month is valid for the corrected year, and not the uncorrected year.
<script language = 'JavaScript'><!-- function makeArray0() { for (i = 0; i<makeArray0.arguments.length; i++) this[i] = makeArray0.arguments[i]; } var days = new makeArray0('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'); var months = new makeArray0('January','February','March','April','May','June','July','August','September','October','November','December'); function nths(day) { if (day == 1 || day == 21 || day == 31) return 'st'; if (day == 2 || day == 22) return 'nd'; if (day == 3 || day == 23) return 'rd'; return 'th'; } function y2k(number) { return (number < 1000) ? number + 1900 : number; } function getCorrectedYear(year) { year = year - 0; if (year < 70) return (2000 + year); if (year < 1900) return (1900 + year); return year; } var dateError = new Date(document.lastModified); var date = new Date(getCorrectedYear(dateError.getYear()),dateError.getMonth(),dateError.getDate()); document.write('last updated on ' + days[date.getDay()] + ' ' + date.getDate() + nths(date.getDate()) + ' ' + months[date.getMonth()] + ', ' + y2k(date.getYear())); //--></script>
Just after releasing this article for publication, it was pointed out that the above version of the code suffers from a potential leap year bug. If a document is modified on 29th February 2000, and the last modified code is viewed on a browser that suffers from two digit years in the lastModified property, then the dateError date is calculated as 1st March 1900. Now we fixed the year fault, but we carried over the incorrect day and month values. Why? Because, 1900 was not a leap year. This bug will only occur for documents amended on the 29th February 2000, 2100, 2200, 2300 but not 2400. For other leap years it will work correctly.
And now...The Weekly Update Script
Extending "Born of the 4th of July"