Visual InterDev Technical Articles |
Mike Pope, Visual InterDev User Education
Microsoft Corporation
June 1998
When it comes to computers, calendars are messy. You and I can easily work with dates such as "next Tuesday" or "an hour and half from now," or "the first of next month." Computers, however, with their relentlessly sequential style of thinking, don't take as well to the arbitrary-seeming algorithms of calculating schedule intervals.
But it can be done. In this article I'll show you how to use the JScript® Date object and related methods and functions to handle various common calendar and clock tasks. As you'll see, once you get the hang of it, it's not hard. Even better, the computer can handle the date calculations that are hard, such as leap years.
Basics of Using the Date Object
To simplify the tasks of formatting and manipulating dates, JScript provides a Date object along with some extra functions that help you work with dates. When you create an instance of the Date object, it takes a snapshot of a particular instant in time down to the millisecond. You can then read or change the value of this snapshot to do your work.
To start with, the following trivial example shows somewhat verbosely how you can get the current date and display it in the format mm/dd/yy:
function showDate(){ dt = new Date(); //Gets today's date right now (to the millisecond). month = dt.getMonth()+1; day = dt.getDate(); year = dt.getFullYear(); alert(month + '/' + day + '/' + year); }
Here you already see most of the important concepts: you create a Date object with the new operator and the Date() constructor, and thereafter you play with the date by calling your Date object's methods. In addition, you might have noticed that the getMonth() method uses a zero offset for the month number.
Note
A warning about getting the year: tread carefully. JScript/JavaScript provides a getYear() method, but it is known to have problems in some dialects, and you should not rely on this method. A more useful method, getFullYear(), is available in later dialects of JScript, but not in earlier ones. To ensure that your JScript applications work properly in all dialects, you can create a custom getFullYear() method, as described later in this article in the section "Extending the Date Object."
You can work with any arbitrary date in the same way by simply passing a date to the constructor:
dt = new Date('1/1/1998'); // Establishes a specific date for the object.
JScript is reasonably forgiving about the date format, allowing variants such as "1-1-98," "98-1-1," "January 1, 1998," "1 Jan 98," and a variety of others. If you pass only two digits for the year, the 20th century is assumed, allowing you to create your own Year 2000 problem—not recommended! If you pass a complete but ambiguous date (such as "1/12/1998"), JScript interprets the date string using U.S. format (month-day-year).
If you need to pinpoint a time, you can additionally pass in a time string in the format hours:minutes:seconds. As if all these date formatting options weren't enough, you can pass the year, month, day, and even hour, minute, and second into the Date() constructor as individual parameters:
dt = new Date(1998, 4, 1, 9, 30, 0); // May 1, 1998 at 9:30AM
You saw a moment ago that to return individual portions of the date, you call various get methods, such as getDate() and getMonth(). An equivalent group of set methods allows you to change the value represented by the Date object. The following example shows how you can set the Date object's value to yesterday by getting today's date, subtracting 1 from it, and then setting the Date object's value again:
function getYesterday(){ today = new Date(); // Get today's date. yesterdayDate = today.getDate() -1 ; today.setDate( yesterdayDate ); alert("Yesterday was " + today); }
From just the few examples above, you'll probably be able to deduce almost everything else you need. In the following sections I'll give you a few practical examples of using the Date object.
Finding Specific Dates
One class of time tasks involves locating a specific date on the calendar: tomorrow, next week, 2 months ago, first Tuesday of the month. The general strategy here is to create a Date object and then use a set method to place yourself on the date you want to be. JScript makes this type of process particularly painless because it accounts for changes in months, leap years, and other hiccups in the calendar that otherwise make date calculations complex.
The following sections illustrate various ways to move to specific dates in the calendar. From these, you can extrapolate to other, similar variations.
- Same Time, Next Week or Month or YearMoving to a specific date.
- Due on the First of Next MonthMoving to a date based on its ordinal value.
- Working with Days of the WeekDeriving the day of the week for specific date; moving to a specific day.
Same Time, Next Week or Month or Year
To move forward or backward from a specific date, you get the relevant portion of the Date object—date, month, or year—and increment or decrement it as needed. You can then set the Date object to the new value.
JScript is smart enough to roll over to the next month or year as required. For example, if today is the 28th of January and you add 7 days, it correctly puts you at February 4. Here's an example of moving forward to one week from today:
function showNextWeek(){ dtNextWeek = new Date(); dtNextWeek.setDate( dtNextWeek.getDate()+7 ); alert( dtNextWeek ); }
The following example is similar, but moves forward by months. To illustrate how the months roll over correctly to the next year, the script contains a loop that walks forward 12 months:
function showOneYear(){ currentDate = new Date(); document.write("Today = " + currentDate + "<p>"); for(i=1; i<12; i++){ currentDate.setMonth(currentDate.getMonth() + 1 ); document.write("Month + " + i + " = " + currentDate + "<br>"); } }
Naturally, you can do the same with years, as the following fragment shows:
oneYearAgo = new Date(); oneYearAgo.setYear( oneYearAgo.getFullYear() - 1 ) ;
Due on the First of Next Month
If you need to calculate a date such as "the first of next month," you can increment the month and set the date to 1:
function firstOfNextMonth(){ dtNextMonth = new Date(); dtNextMonth.setMonth(dtNextMonth.getMonth() + 1 ); dtNextMonth.setDate(1); alert(dtNextMonth); return(dtNextMonth); }
If you need to find the end of the month instead, one way is to go to the first of the following month and work backward one day. The following example finds the last day of the current month:
function showLastOfMonth(){ eomDate = new Date(); eomDate.setMonth(eomDate.getMonth() + 1 ); eomDate.setDate(1); eomDate.setDate( eomDate.getDate() - 1); alert("Last day of this month = " + eomDate); }
Working with Days of the Week
The Date object provides a method called getDay()—not to be confused with getDate()—that returns a value between 0 and 6 to indicate the day of the week. Sunday is zero, Monday is 1, and so on. The following shows how you can display the current day of the week:
function showDayOfWeek(){ dowArray = new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); today = new Date(); dow = today.getDay(); alert("Today is " + dowArray[dow] ); }
At times you might need to move to a specific date based on its day of the week. The following example shows a script that returns the date of "last Friday" from wherever you might be now:
function lastFriday(){ startDate = new Date(); if(startDate.getDay() == 5){ // It's Friday today, just subtract one week. startDate.setDate( startDate.getDate() - 7); } else { // Walk backwards a day at a time until we get to Friday. while(startDate.getDay() != 5){ startDate.setDate( startDate.getDate() - 1); } } return startDate; }
The following example combines days of the week with adding dates to find the date for the U.S. holiday Thanksgiving, which is defined as the third Thursday of November. The script starts on November 1st of the current year, finds the following Thursday, and then adds three weeks:
function showThanksgiving(){ dtThanksgiving = new Date(); dtThanksgiving.setMonth(10); // November dtThanksgiving.setDate(1); // Find first Thursday. while( dtThanksgiving.getDay() != 4){ dtThanksgiving.setDate( dtThanksgiving.getDate() + 1 ) ; } // Add 3 weeks. dtThanksgiving.setDate( dtThanksgiving.getDate() + 21 ); alert("This year, Thanksgiving is on " + dtThanksgiving); }
Calculating Elapsed Time
A second class of time tasks involves figuring out how long the period is between two points. You might be measuring anything from how many seconds a process took to how old a person is.
Calculating elapsed time requires a start time and an end time so you can subtract and find the difference. For some elapsed time calculations, you want to set your start and end times using some sort of absolute unit that will just keep incrementing indefinitely, no matter how long the interval is.
As it turns out, the Date object does support one—and only one—interval-oriented unit: milliseconds. That seems like an obvious choice for measuring small increments such as a few seconds. But even though it doesn't seem like the obvious choice for larger increments, such as days, it works for many of those, too: You just end up with large integers that you convert to the appropriate unit.
You can calculate elapsed time in milliseconds using the Date object's getTime() method. This method returns an absolute increment, namely the number of milliseconds that have elapsed since the zero date and time of midnight on January 1, 1970. If you are working with a date before that date, getTime() returns a negative number.
You frequently work with two Date objects when calculating elapsed times, one for the start time and another for the end time. In some instances, you can avoid creating a second Date object, and instead just get a snapshot of the current time using the Date() function. The disadvantage is that you can't then call methods to extract various portions of that date, although you can convert it to milliseconds with the Date.parse() function if needed.
Note
By the way, getTime() returns the number of milliseconds in "Universal Coordinated Time" (UTC), also known as Greenwich Mean Time (GMT). If your time math calculations are sensitive to time zones, you must be sure to adjust the resulting number accordingly. A special function called getTimezoneOffset() helps you calculate your difference from GMT, although rather perversely, it returns a value in minutes.
In many cases, after you've gotten your start and end points in milliseconds, it's pretty much all subtraction plus some manipulation to get the right units. Other elapsed time calculations require more complex workarounds, as I'll explain in the following sections:
- How Long Did That Take?Calculating the number of milliseconds a process takes.
- How Many Days?Converting elapsed time in milliseconds into larger units.
- How Old Are You?Calculating intervals based on the calendar rather than on milliseconds.
- Creating a Timer.Timing a process and creating timed events in a Web page.
How Long Did That Take?
Here's a simple example to show how you calculate elapsed time. This script times how quickly you can type in your name:
function typingTimer() sTime = new Date(); prompt("What is your name?",""); eTime = new Date(); elapsed = (eTime.getTime() - sTime.getTime() ) / 1000 ; alert("You took " + elapsed + " seconds to type in your name."); }
This works no matter how long the interval is—perhaps you started the script, went home, and came back this morning to enter your name. The milliseconds reported by getTime() are an absolute value from the zero date, so they just keep on ticking forward through changes in the minute, hour, and day.
How Many Days?
If you want to work with more manageable units, you divide the milliseconds provided by getTime() by a suitable number to arrive at days. For instance, to turn milliseconds into days, divide the number by 86,400,000 (1000 * 60 * 60 * 24).
The following example shows you how many days, hours, minutes, and seconds have elapsed since the first day of the year. It uses a succession of division operations to carve up the elapsed time into more useful units. Note that it doesn't account for daylight savings time, so between April and October it might be off by an hour.
function elapsedTime(){ today = new Date(); bofYear = new Date(); bofYear.setMonth(0); //Reset to midnight on January 1. bofYear.setDate(0); bofYear.setHours(0); bofYear.setMinutes(0); bofYear.setSeconds(0); interval = today.getTime() - bofYear.getTime(); // Difference in ms. // Establish larger units based on milliseconds. msecondsPerMinute = 1000 * 60; msecondsPerHour = msecondsPerMinute * 60; msecondsPerDay = msecondsPerHour * 24; // Calculate how many days the interval contains, then subtract that // many days from the interval to come up with a remainder. days = Math.floor( interval / msecondsPerDay ); interval = interval - (days * msecondsPerDay ); // Repeat the previous calculation on the remainder using hours, // then subtract the hours from the remainder. hours = Math.floor( interval / msecondsPerHour ); interval = interval - (hours * msecondsPerHour ); minutes = Math.floor( interval / msecondsPerMinute ); interval = interval - (minutes * msecondsPerMinute ); seconds = Math.floor( interval / 1000 ); msg = "Total = " + days + " days, " + hours + " hours, " + minutes + " minutes, and " + seconds + " seconds."; alert(msg); }
How Old Are You?
In practice, you don't calculate ages in months or years using elapsed milliseconds. Why not? Because our definition of age in these units isn't based on a strict interval—it's an approximation that ignores leap years and irregular months and such. For those types of calculations, you return to a more-or-less standard algorithm that simply subtracts years and adjusts for your birthday:
function yearsOld(){ birthday = new Date("12/1/1967"); today = new Date(); years = today.getFullYear() - birthday.getFullYear(); birthday.setYear( today.getFullYear() ); // If your birthday hasn't occurred yet this year, subtract 1. if(today < birthday){ years-- ; } alert("You are " + years + " years old."); }
Note
You need to be careful when comparing dates to be sure you're doing it correctly. You can find more about that in the section "Comparing Dates" later in this article.
How about your age in months? Again, as we understand it, that's not a number that is derived from elapsed milliseconds. The following example shows one possibility, a variant on the age algorithm above. This script includes a test to see if the birthday has occurred within the current month.
function ageInMonths(){ birthday = new Date("12/1/1967"); today = new Date(); months = (today.getFullYear() - birthday.getFullYear()) * 12; addmonths = today.getMonth() - birthday.getMonth(); // The previous calculation could result in a negative number. months = months + addmonths; // Adjust month total if the birthday hasn't occurred yet this month. if(today.getDate() < birthday.getDate()) { months--; } alert("You are " + months + " months old."); }
Creating a Timer
Now and then you might want to run a process for a specific interval. For that you need a timer. How you create the timer depends on what you want to do while the timer is counting. Imagine for just a moment that you simply want to loop for 10 seconds. Doing so is similar to testing how long a process took. However, you don't need to create a Date() object. Instead, you can simply use the Date() function to successively get the current time and convert it to milliseconds using the Date.parse() function. Here's a fragment that shows the idea. It doesn't do anything except wait 10 seconds.
function smallTimer(){ now = Date.parse( Date() ); // Not an object limit = now + 100000; // Add 10 seconds. while(now < limit){ // do something here ... now = Date.parse( Date() ) ; // Keep resetting now. } alert("Done now."); }
A limitation of this type of timer is that you can't use it to create timed interaction with elements on the Web page. For example, you can't use this type of timer to change the graphic on a page every second, or the text in a paragraph, or the color of a heading. The problem is that while the script is running, it queues messages for the browser, but the browser doesn't get them until the script has finished. The result is that nothing appears to happen while the script is running, but when it finishes, the queued-up messages are all processed at once.
However, there is a way you can accomplish this type of timed browser interaction, though it works somewhat differently than the timer above. The solution is to use the setTimeout() method of the HTML window object. The setTimeout() method allows you to specify an interval in milliseconds, plus the name of a function to be called when the interval expires—it's like a little countdown clock that calls your function when the alarm rings.
For example, the following statement tells the browser to call the showClock() function after one second. Note that the name of the function to be called is a string and not, as sometimes is true in JScript, a function pointer.
timerID = window.setTimeout("showClock()", 1000);
Running the statement causes the browser to call the showClock() function after one second. However, it only calls it once. To create a continuous timer, you call setTimeout() again, usually from inside the function specified in setTimeout(). In effect, you just keep repeating the countdown:
timerID = window.setTimeout("showClock()", 1000); // Initial call. function showClock(){ // Display a clock here, and then ... // ... repeat the call to setTimeout(). timerID = window.setTimeout("showClock()", 1000); }
This does not, as it might appear, result in a recursive call to showClock(). Instead, showClock() runs to completion. The browser then counts down the specified interval and calls the function again from scratch. When you want to quit this cycle, you can call a window method called clearTimeout(), to which you pass the identifier returned by the most recent setTimeout() method.
Because the specified function is called from scratch each time, if you need to preserve values between timer increments, you need to keep them in global variables. A likely candidate for a global variable, of course, is the identifier returned by setTimeout().
Here's a simple but complete example of how to create a timer using setTimeout(). It sets a timer increment of one second, and when called, displays the current time in a text box. The global variable timerID holds the identifier for the most recent call to setTimeout().
<HTML> <HEAD> <TITLE>Clock Page</TITLE> <SCRIPT LANGUAGE="JScript"> var timerID function startClock(){ // Initial call txtClock.value = "Starting ..."; timerID = window.setTimeout("showClock()", 1000); // 1-second interval } function stopClock(){ window.clearTimeout(timerID); txtClock.value = "Stopped!"; } function showClock(){ // This function actually displays the time. dt = new Date(); hours = dt.getHours(); minutes = dt.getMinutes(); seconds = dt.getSeconds(); txtClock.value = hours + ":" + minutes + ":" + seconds; timerID = window.setTimeout("showClock()", 1000); // repeat call } </SCRIPT> </HEAD> <BODY> <B>Press buttons to display a clock</B> <P><INPUT TYPE="Text" ID="txtClock" value="Clock"></P> <BUTTON ID="btnStart" onclick="startClock()">Start</BUTTON> <BUTTON ID="btnStop" onclick="stopClock()">Stop</BUTTON> </BODY> </HTML>
Comparing Dates
Date objects follow JScript rules that apply to any objects, which include rules about how comparisons are made between objects. If you're already familiar with comparison rules in JScript, you're not likely to run into any problems. However, if you're new to JScript objects, you will need to learn how comparison works for objects. Consider the script from earlier for determining how old you are:
function yearsOld(){ birthday = new Date("12/1/1967"); today = new Date(); years = today.getFullYear() - birthday.getFullYear(); birthday.setYear( today.getFullYear() ); // If your birthday hasn't occurred yet this year, subtract 1. if(today < birthday){ years-- ; } alert("You are " + years + " years old."); }
Toward the end, you compare two objects, today and birthday. The JScript rule is that if you are comparing objects using greater than or less than (<, >, <=, or >=), the object's value is evaluated before the comparison is performed. In the example above, it works. (What the values are in this case is not clear, although it appears to work as if they were numeric.)
However, this rule does not apply to equality comparisons. The rule for the == operator is that the comparison returns true only if both sides of the operator refer to the same object. The same is true for the != operator.
Therefore, if you want to know whether one date is equal to another (or not equal to it), you have to get some value representing the dates first. One possibility is getTime(), which allows you to compare millisecond representations of two dates:
firstDate = new Date('1 January 1998') secondDate = new Date('1/1/98'); if(firstDate.getTime() == secondDate.getTime() ){ // This will compare true. }
But wait, there's more. If you create a Date object for a specific date, JScript calculates the day in milliseconds assuming midnight at the beginning of that day. For example, suppose you create a Date object this way:
newYear = new Date("1/1/1998");
JScript assumes January 1, 1998 at midnight and not one millisecond beyond. In contrast, suppose you create a Date object for today:
today = new Date();
JScript creates the object as a snapshot of this instant, using whatever the current values are for hours and minutes and so on. So millisecond-based comparisons don't work perfectly if one of the Date objects is a snapshot of right now. One workaround is to create a second Date object using today's month, day, and year, as in the following:
function compareDates(){ now = new Date(); today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); aprilFirst = new Date("4/1/1998"); if(today.getTime() == aprilFirst.getTime()){ alert("Same");} else{ alert("Different");} }
Another solution is to work with a string representation of a date. Because string comparisons work left to right, you have to put the most significant digits first, so the format is yyyymmdd. In addition, you have to left-pad any 1-character digits—for example, February has to be 02 so it will compare properly against December as 12. The string for a date such as April 1, 1998 would be 19980401. Here's an example:
function getDateString(){ var todayStr; today = new Date(); todayStr = "" + today.getFullYear(); if (today.getMonth() < 9) todayStr += "0"; todayStr += (today.getMonth() + 1); if (today.getDate() < 10) todayStr += "0"; todayStr += today.getDate(); return todayStr; }
Naturally, you would have to create this date string for any date that you wanted to compare against today's date. For that, you can extend the Date object, as described next.
Extending the Date Object
A useful feature of recent versions of JScript is that it allows you to extend the definition of an object, including built-in objects, using prototypes. You can define a new method or property and assign it to the prototype of an object, and then all instances of the object automatically have access to that method.
For our purposes, this means we can add methods to the Date object. A good example is the getDateString() method described above that creates a string version of a date for comparisons.
But first it needs to be changed slightly to make it more general purpose. In its final form, the function is designed to be called as a method of an object, so it doesn't need to create its own instance of the Date object. Instead, it assumes it's running in the context of a specific object—the object on which it is invoked—which can be referenced using the "this" keyword. The rewritten version might look like this:
function getDateString(){ var dateStr; dateStr = "" + this.getFullYear(); if (this.getMonth() < 9) dateStr += "0"; dateStr += (this.getMonth() + 1); if (this.getDate() < 10) dateStr += "0"; dateStr += this.getDate(); return dateStr; }
To add it to the Date object, you invoke the prototype method, something like this:
Date.prototype.getDateString = getDateString;
That's it. After this statement has executed, you can invoke the getDateString() method as if it had been built into the object. (You need to be sure that you've added the method to the prototype before you try to use it, of course.) Here's an example that shows you can add the method to the Date object, then use it to compare two dates.
function compareDates(){ today = new Date(); aprilFirst = new Date("4/1/98"); if(today.getDateString() == aprilFirst.getDateString()){ alert("Same"); } else{ alert("Different"); } }
Creating Your Own Year Method
You might remember from the beginning of the article that the getYear() method has problems in various dialects of JScript/JavaScript. In more recent versions, the getFullYear() method is reliable, but it isn't available in all dialects. If you're sure that your code will run in (for example) only Internet Explorer 4.0, you can rely on getFullYear(). However, if you're writing broad-reach code, you probably want to provide your own year method that will work across all dialects.
Fortunately, this is easy. Here's code for a getFullYear() method that correctly returns a full four-digit year for all dates starting in the year 1000:
function getFullYear(){ var year = this.getYear(); if(year < 1000){ year += 1900;} return year }
To make this getFullYear() method available, you again invoke the Date object's prototype like this:
Date.prototype.getFullYear = getFullYear;
Remember to make sure that this statement has executed before you try to call the new getFullYear() method. Incidentally, it's safe to use this custom getFullDate() method, even if the method is already available in a particular dialect. When you add to the object with a prototype, your addition takes precedence over a built-in method of the same name.
Your Turn
That's it for this lesson. Given these fundamentals, you should be able to get started with calendar and clock arithmetic. I didn't cover all available methods and functions that pertain to dates; for more information about those, refer to your favorite JScript text or Web site. Be sure also to search the Web for JScript and JavaScript sites, which contain lots of additional information, sample code, and downloadable scripts. A good place to start is the Articles section of http://www.irt.org/.