You are here: irt.org | Articles | JavaScript | Date and Time | Blind Date [ previous next ]
Published on: Sunday 5th October 1997 By: Martin Webb
This article demonstrates how to work with dates prior to 1970 and after 1999 without using the inbuilt Date object. It will describe how to pass this information to another frame or to another document.
Some of the older versions of Netscape Navigator and Internet Explorer have certain features or bugs around the date object.
For example the following script attempts to create and display a date object for 1st February 1960, but using Netscape 2 this will cause problems:
<SCRIPT LANGUAGE="JavaScript"><!-- var date = new Date(1960,1,1);document.write(date+'<BR>'); //--></SCRIPT>
The reason why this causes problems is that the inbuilt Date object in the first release of JavaScript stores dates internally as the number of milliseconds since January 1, 1970 00:00:00. The actual JavaScript documentation states that:
'Dates prior to 1970 are not allowed.'
Other features with dates, is that sometimes dates after 1999 also cause problems with older browsers. For example the following script attempts to create and display a date object for 1st February 2000, but using MSIE version 3, this may produce a negative value.
<SCRIPT LANGUAGE="JavaScript"><!-- var date = new Date(2000,1,1);document.write(date+'<BR>'); //--></SCRIPT>
The latest browsers do not have problems with dates earlier than 1970 or later than 1999.
So how do we perform all of the items described at the start of this article with only the following one-off use of the inbuilt Date object?
var today = new Date(); var year = today.getYear(); var month = today.getMonth(); var day = today.getDate();
Simple - we number crunch!
There are certain know facts about dates.
# | Month | Days |
1. | January | 31 |
2. | February | 28* |
3. | March | 31 |
4. | April | 30 |
5. | May | 31 |
6. | June | 30 |
7. | July | 31 |
8. | August | 31 |
9. | September | 30 |
10. | October | 31 |
11. | November | 30 |
12. | December | 31 |
The most useful function contained within this example is the following makeArray() function, which when passed an unspecified number of arguments creates an array, with the first array element, i.e. [0], containing the number of items within the array:
function makeArray() { this[0] = makeArray.arguments.length; for (i = 0; i<makeArray.arguments.length; i++) this[i+1] = makeArray.arguments[i]; }
The makeArray() function is described in detail in Arguments Array.
One simple use of the makeArray() function is to create an array containing the months of the year, monthsofyear[]:
var monthsofyear = new makeArray('January','February','March', 'April','May','June', 'July','August','September', 'October','November','December');
Once we've used the makeArray() function once, we can use it again and again and again:
var daysofmonth = new makeArray( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); var daysofmonthLY = new makeArray( 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
The daysofmonth[] array contains the number of days for each month from January through to December. Whereas the daysofmonthLY[] array contains the number of days in a Leap Year for each month from January through to December.
To retrieve the information stored within these arrays is straight forward:
for (var i=1; i<=12; i++) document.write(monthsofyear[i] + ' ' + daysofmonth[i] + ' ' + daysofmonthLY[i] + '</BR>');
Which when run produces:
How do we allow the user to enter valid dates, for example 28/1/1900 or 4/01/1965? Well one way would be to allow the user to type in a date in a text box along the lines of:
But, what if the user typed in the date in one of the following formats: mm/dd/yyyy, m/d/yy or yyyy-mm-dd.
It would be extremely difficult, and highly error prone, to vet and validate all possible date combinations that the user could enter.
I prefer the following method which restricts the input of a date to a well defined structure:
So, how did we produce this form, how did the current date get selected by default, and how do we retrieve the date selected?
The inputDateForm form was simply displayed using the following code:
<FORM NAME="inputDateForm" onSubmit="return ChosenDate(document.inputDateForm);"> Day: <SELECT NAME="day"> <SCRIPT LANGUAGE="JavaScript"><!-- document.write(dayOutput); //--></SCRIPT> </SELECT> Month: <SELECT NAME="month"> <SCRIPT LANGUAGE="JavaScript"><!-- document.write(monthOutput); //--></SCRIPT> </SELECT> Year: <SCRIPT LANGUAGE="JavaScript"><!-- document.write(yearOutput); //--></SCRIPT> <INPUT TYPE="SUBMIT" VALUE="Show"> </FORM>
The three JavaScript variables dayOutput, monthOutput and yearOutput were prepared slightly earlier using the following code.
function y2k(number) { return (number < 1000) ? number + 1900 : number; } function padout(number) { return (number < 10) ? '0' + number : number; } var today = new Date(); var thisYear = y2k(today.getYear()); var thisMonth = today.getMonth()+1; var thisDay = today.getDate(); var dayOutput = ''; for (var days=1; days <= 31; days++) { if (days == thisDay) dayOutput += '<OPTION VALUE="' + padout(days) + '" SELECTED>' + days; else dayOutput += '<OPTION VALUE="' + padout(days) + '">' + days; } var monthOutput = ''; for (var months=1; months <=12; months++) { if (months == thisMonth) monthOutput += '<OPTION VALUE="' + padout(months) + '" SELECTED>' + monthsofyear[months]; else monthOutput += '<OPTION VALUE="' + padout(months) + '">' + monthsofyear[months]; } var yearOutput = '<INPUT TYPE="TEXT" NAME="year" SIZE="4" VALUE="' + thisYear + '">';
The date today is obtained using the inbuilt Date object. The year is obtained using getYear() and is then converted to CCYY using the y2k() function.
The dayOutput variable contains the HTML required to display 31 options, one for each day of a calendar month. The option that corresponds with the current thisDay has the addition of a SELECTED property.
The monthOutput variable contains the HTML required to display 12 options, one for each month of the year. It utilises the monthsofyear[] array defined earlier in this article. Again the option that corresponds with the current thisMonth has the addition of a SELECTED property.
The padout() function is used to pad out values less than 10 with an extra leading zero, i.e. 5 becomes 05. This is helpful, when we later pass the data between documents using the locations search property.
The yearOutput variable, works slightly differently in that in contains the HTML required to display a form text field with the current year as its value. To retrieve the date selected we use the onSubmit event handler of the inputDateForm forms <FORM> tag to invoke the the following ChosenDate() function with a reference to the documents inputDateForm form:
function ChosenDate(object) { year = object.year.value; month = object.month.options[object.month.selectedIndex].value; day = object.day.options[object.day.selectedIndex].value; alert('Date: '+day+'/'+month+'/'+year); return false; }
The reference to the documents inputDateForm is received as object, which is then used to retrieve the value of the year text box and the month and day options using the selectedIndex property.
Finally the ChosenDate() function returns false to the forms onSubmit event handler to halt the completion of the form submission.
"But.." I hear you say, "But, not every month has 31 days. The user could still select an invalid date!"
As the code stands now, yes, they could. However, if we change the ChosenDate() function as follows, then all is not lost.
function LeapYear(year) { if ((year/4) != Math.floor(year/4)) return false; if ((year/100) != Math.floor(year/100)) return true; if ((year/400) != Math.floor(year/400)) return false; return true; } function ValidDate(day,month,year) { if ( (LeapYear(year) && (day > daysofmonthLY[month])) || (!LeapYear(year) && (day > daysofmonth[month])) ) return false; else return true; } function ChosenDate(object) { year = object.year.value - 0; month = object.month.options[object.month.selectedIndex].value - 0; day = object.day.options[object.day.selectedIndex].value - 0; if (!ValidDate(day-0,month-0,year-0)) { alert('You must enter a valid date'); return false; } alert('Valid Date: '+day+'/'+month+'/'+year); return false; }
We must first convert the day, month and year values from strings to numbers, by subtracting zero from them, before passing the date to the ValidDate() function. The option values within forms are always string values.
The ValidDate() function checks the number of days in the month selected by comparing it against the relevant entry in the daysofmonth[] or in the daysofmonthLY[] array if its a Leap Year by passing the data to the ValidDate() function.
To determine whether or not its a leap year, we pass the year to the LeapYear() function.
If the date is not valid (i.e. ! reverses the value returned from the ValidDate() function), then we display an error message.
Its possible, once we have validated the date, to pass it up to the parent document, for use by another frame.
For example, the parent document could define the three variables year, month and day, along with the required frameset:
<HTML><HEAD> <SCRIPT LANGUAGE="JavaScript"><!-- function y2k(number) { return (number < 1000) ? number + 1900 : number; } var today = new Date(); var year = y2k(today.getYear()); var month = today.getMonth()+1; var day = today.getDate(); var thisYear = year; var thisMonth = month; var thisDay = day; //--></SCRIPT> <FRAMESET ROWS="130,*"> <FRAME SCROLLING=NO FRAMEBORDER=0 BORDER=0 NORESIZE SRC="input.htm"> <FRAME SCROLLING=YES FRAMEBORDER=0 BORDER=0 NORESIZE SRC="blank.htm" NAME="outputFrame"> </FRAMESET> </HEAD></HTML>
The blank.htm document just holds the following HTML:
<BODY></BODY>
The input.htm document could then hold all the JavaScript code described in this article so far, but with an amended ChosenDate() function as follows:
function ChosenDate(object) { year = object.year.options[object.year.selectedIndex].text - 0; month = object.month.options[object.month.selectedIndex].value - 0; day = object.day.options[object.day.selectedIndex].value - 0; if (!ValidDate(day,month,year)) { alert('You must enter a valid date'); return false; } parent.year = year; parent.month = month; parent.day = day; parent.outputFrame.location.href = 'output.htm'; return false; }
This amended ChosenDate() function changes the value of the parent variables, before changing the location of the outputFrame from blank.htm to output.htm.
The output.htm can simple retrieve the parent variable values with the following code, which displays the date retrieved from the parent document along with the current date:
<HTML> <HEAD> <SCRIPT LANGUAGE="JavaScript"><!-- function y2k(number) { return (number < 1000) ? number + 1900 : number; } var year = parent.year - 0; var month = parent.month - 0; var day = parent.day - 0; var today = new Date(); var thisYear = y2k(today.getYear()); var thisMonth = today.getMonth()+1; var thisDay = today.getDate(); //--></SCRIPT> </HEAD> <BODY> <SCRIPT LANGUAGE="JavaScript"><!-- document.write('Valid Date: '+day+'/'+month+'/'+year+'<BR>'); document.write('Todays Date: '+thisDay+'/'+thisMonth+'/'+thisYear+'<BR>'); //--></SCRIPT> </BODY> </HTML>
Its also possible, once we have validated the date, to pass it on to another document. If we include an ACTION property to the existing inputDataForm, so that if the form is submitted it loads the output.htm document:
<FORM NAME="inputDateForm" ACTION="output.htm" onSubmit="return ChosenDate(document.inputDateForm);">
Up till now we have always returned false from the ChosenDate()function. To enable the ACTION property on the inputDataForm to load the next document we need to return true from the ChosenDate() function:
function ChosenDate(object) { year = object.year.value - 0; month = object.month.options[object.month.selectedIndex].value - 0; day = object.day.options[object.day.selectedIndex].value - 0; if (!ValidDate(day,month,year)) { alert('You must enter a valid date'); return false; } if (parent.location.href != window.location.href) { parent.year = year; parent.month = month; parent.day = day; parent.outputFrame.location.href = 'output.htm'; } else { return true; } return false; }
The above amended ChosenDate() function, tests whether the current document is loaded within a Frameset by comparing the location of the parent document with the location of the current window.
If they are not equal (i.e. !=) then, as before, the variables in the parent document are updated and the location of the outputFrame is changed to load the output.htm document.
Otherwise the ChosenDate() function returns true.
The output.htm document can now be invoked as a frame within a Frameset, or as a document on its own:
<HTML> <HEAD> <SCRIPT LANGUAGE="JavaScript"><!-- function y2k(number) { return (number < 1000) ? number + 1900 : number; } if (location.search.length == 0) { var year = parent.year - 0; var month = parent.month - 0; var day = parent.day - 0; } else { var day = location.search.substring(5,7) - 0; var month = location.search.substring(14,16) - 0; var year = location.search.substring(22) - 0; } var today = new Date(); var thisYear = y2k(today.getYear()); var thisMonth = today.getMonth()+1; var thisDay = today.getDate(); //--></SCRIPT> </HEAD> <BODY> <SCRIPT LANGUAGE="JavaScript"><!-- document.write('Valid Date: '+day+'/'+month+'/'+year+'<BR>'); document.write('Todays Date: '+thisDay+'/'+thisMonth+'/'+thisYear+'<BR>'); //--></SCRIPT> </BODY> </HTML>
The above amended output.htm now checks the length of any possible search string that may have been passed to it. For example, if the date chosen on the previous input.htm document had been the 4th of January 1965, then the search string would hold:
?day=04&month=01&year=1965
The text items day, month and year are the named form elements from the inputDataForm on the previous document.
Using the substring() method we are able to extract the values of there form fields, 04, 01 and 1965.
If the current location's search string is empty (i.e. length is zero) then we assume the
variable values are to be retrieved from the
Why not try out the frame version?
You can view the source code of the four components:
Extending "Born of the 4th of July"
Monday's child is full of grace