Recipe 10.6 Calculating Elapsed Time or Intervals Between Dates
10.6.1 Problem
You want to calculate an elapsed time, elapsed date, or
relative time.
10.6.2 Solution
For simple elapsed time, add and subtract from the Epoch milliseconds
or the value returned by getTimer( ). For
more complex conversions, create custom
Date.doMath( ) and Date.elapsedTime(
)
methods.
10.6.3 Discussion
For simple conversions such as adding or subtracting an hour, day, or
week to or from a date, simply add or subtract from the
date's Epoch milliseconds value. For this purpose,
note that a second is 1,000 milliseconds, a minute is 60,000
milliseconds, an hour is 3,600,000 milliseconds, a week is
604,800,000 milliseconds, and so on. Unless you have a spectacular
gift for remembering these conversion values, storing them as
constants of the Date class is the easiest
option. You can add the following constants to your
Date.as file for convenience:
// There are 1000 milliseconds in a second, 60 seconds in a minute, 60 minutes in an
// hour, 24 hours in a day, and 7 days in a week.
Date.SEC = 1000;
Date.MIN = Date.SEC * 60;
Date.HOUR = Date.MIN * 60;
Date.DAY = Date.HOUR * 24;
Date.WEEK = Date.DAY * 7;
You can use the Date.getTime( ) method to
retrieve a date's current value in Epoch
milliseconds, and you can set the new value using the
Date.setTime( ) method. The following example
adds one day to a given Date object.
#include "Date.as"
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);
// Displays: Fri Oct 13 03:55:00 GMT-0700 1978
trace(myDate);
// Add one day to the previous date by setting the new date/time to the original
// date/time plus Date.DAY (the number of milliseconds in a day).
myDate.setTime(myDate.getTime( ) + Date.DAY);
// Displays: Sat Oct 14 03:55:00 GMT-0700 1978
trace(myDate);
Here are some more examples of simple conversions using
Date.getTime( ), Date.setTime(
), and the aforementioned constants:
#include "Date.as"
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);
// Subtract one week from the date.
// Displays : Fri Oct 6 03:55:00 GMT-0700 1978 (timezone offset may vary)
myDate.setTime(myDate.getTime( ) - Date.WEEK);
trace(myDate);
// Add one week and one day to the date.
// Displays: Sat Oct 14 03:55:00 GMT-0700 1978 (timezone offset may vary)
myDate.setTime(myDate.getTime( ) + Date.WEEK + Date.DAY);
trace(myDate);
// Subtract 3 hours and 55 minutes from the date.
// Displays: Sat Oct 14 00:00:00 GMT-0700 1978 (timezone offset may vary)
myDate.setTime(myDate.getTime( ) - (3 * Date.HOUR) - (55 * Date.MIN));
trace(myDate);
You'll often want to calculate an elapsed time to
create a timer for a game or other activity. Calculating the elapsed
time is simply a matter of recording the time during initialization
and then later comparing it to the current time at some later point
during execution. For example, to calculate how long a movie has been
running, you can use the following code (note that the new
Date( ) constructor always returns the current time):
// Record the starting time.
var startingTime = new Date( );
// Create a text field to display the current time.
this.createTextField("timer_txt", 1, 100, 100, 50, 20);
// Check the elapsed time during each tick of the frame rate.
this.onEnterFrame = function ( ) {
// Determine the elapsed time.
var elapsedTime = new Date().getTime() - startingTime.getTime( );
// Convert from milliseconds to seconds and round to the nearest second.
this.timer_txt.text = Math.round(elapsedTime / 1000);
};
The global getTimer( ) function, not to be
confused with the Date.getTime( ) method,
returns the number of milliseconds since the Player started running.
Therefore, by checking its value at successive times, it can also be
used to determine the elapsed time. This alternative to the preceding
example uses
the global getTimer( )
function in place of new Date( ):
var startingTime = getTimer( );
this.createTextField("timer_txt", 1, 100, 100, 50, 20);
this.onEnterFrame = function ( ) {
// Determine the elapsed time.
var elapsedTime = getTimer( ) - startingTime;
// Convert from milliseconds to seconds and round to the nearest second.
this.timer_txt.text = Math.round(elapsedTime / 1000);
};
Note that the following code is not necessarily an acceptable
alternative to the preceding example. The global getTimer(
) function returns the number of milliseconds since the
Player started running, not since the current movie started running.
In this example, the timer is not reset when a new movie loads:
this.createTextField("timer_txt", 1, 100, 100, 50, 20);
this.onEnterFrame = function ( ) {
// Convert from milliseconds to seconds and round to the nearest second.
this.timer_txt.text = Math.round(getTimer( )/ 1000);
};
We can adapt the earlier example to create a countdown timer. This
example jumps to the frame labeled
"OutOfTime" after 60 seconds:
// Record the starting time.
var startingTime = getTimer( );
// Create a text field to display the current time.
this.createTextField("timer_txt", 1, 100, 100, 50, 20);
// Count down from 60 seconds
var maxTime = 60
// Check the elapsed time during each tick of the frame rate.
this.onEnterFrame = function ( ) {
// Determine the elapsed time.
var elapsedTime = getTimer( ) - startingTime;
// Convert from milliseconds to seconds and round to the nearest second.
elapsedTime = Math.round(elapsedTime / 1000);
if (elapsedTime >= maxTime) {
this.timer_txt.text = "0";
gotoAndPlay("OutOfTime");
} else {
this.timer_txt.text = maxTime - elapsedTime;
}
};
Let's return to our earlier example in which we
calculated elapsed times using Date objects.
When it comes to adding and subtracting years and months from dates,
we cannot rely on constants. This is because the number of
milliseconds in a month varies with the number of days in the month,
and leap years have more milliseconds than other years. However, the
Date class handles wrap-around calculations
transparently when using the getter and setter methods. The most
effective way to handle date math is to create a
Date.doMath( ) method that performs the
calculations for you. Such a method should take up to seven numeric
parameters, each of which can be positive or negative:
- years
-
The number of years to add to the date
- months
-
The number of months to add to the date
- days
-
The number of days to add to the date
- hours
-
The number of hours to add to the date
- minutes
-
The number of minutes to add to the date
- seconds
-
The number of seconds to add to the date
- milliseconds
-
The number of milliseconds to add to the date
Here is the custom Date.doMath( ) method, which
you can add to your Date.as file for easy
inclusion in other projects:
Date.prototype.doMath = function (years, months, days, hours, minutes,
seconds, milliseconds) {
// Perform conversions on a copy so as not to alter the original date.
var d = new Date(this.getTime( ));
// Add the specified intervals to the original date.
d.setYear(d.getFullYear( ) + years);
d.setMonth(d.getMonth( ) + months);
d.setDate(d.getDate( ) + days);
d.setHours(d.getHours( ) + hours);
d.setMinutes(d.getMinutes( ) + minutes);
d.setSeconds(d.getSeconds( ) + seconds);
d.setMilliseconds(d.getMilliseconds( ) + milliseconds);
// Return the new date value.
return d;
};
// Example usage:
#include "Date.as"
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);
// Add 1 year and subtract 3 hours and 55 minutes.
// Displays: Sat Oct 13 00:00:00 GMT-0700 1979
trace(myDate.doMath(1, 0, 0, -3, -55));
// You can also add 365 days and subtract 235 minutes to arrive at the same date as
// the previous statement. Displays: Sat Oct 13 00:00:00 GMT-0700 1979
trace(myDate.doMath(0, 0, 365, 0, -235));
// Add 24 years. Displays: Sun Oct 13 03:55:00 GMT-0700 2002
trace(myDate.doMath(24));
The preceding example demonstrates how to create a new
Date object based on an elapsed time from an
existing Date object. However, you may want to
calculate the elapsed time between two existing
Date objects, which is not as trivial as you
might assume. You might try subtracting the return value of the
getTime( ) method of one
Date object from another, but this
doesn't offer a general solution for calculating the
elapsed time between two Date objects. Although
the operation yields the number of milliseconds between the two
dates, the result isn't convenient to manipulate
when the times are not within the same day. Manually converting the
number of milliseconds to a number of years, months, and days is
difficult due to the varying number of days per month, leap year,
etc. Furthermore, handling negative elapsed times can be cumbersome.
Therefore, it is easiest to create a custom
Date.elapsedTime( ) method that returns a
Date object representing an elapsed time. This
allows us to use the Date
class's built-in methods to calculate the number of
years, months, and days between two Date
objects. There are several caveats, however. Although ActionScript
internally stores dates relative to the Epoch time (midnight on
January 1, 1970), most Date class methods return
absolute values, not values relative to the Epoch. For example, the
year for a date in 1971 is returned as 1971, not 1. For our
elapsed-time object to be optimally useful, we must
define custom elapsedYears(
) and elapsedDays( ) methods to
return values that are relative to the Epoch time. (The month, hour,
minute, second, and millisecond of the Epoch time are all 0, so there
is no need for custom methods to return relative offsets for these
values.)
Our custom elapsedTime( ) method presented here
creates a new Date object that represents the
elapsed time between the Date object on which it
is invoked and another Date object. If no
secondary Date object is specified, the elapsed
time is calculated relative to the current time
("now"). The elapsed time is always
stored as a positive number, regardless of which
Date object represents a later time. You should
add the elapsedTime( ), elapsedYears(
), and elapsedDays( ) methods to your
Date.as file for easy inclusion in other
projects.
// Calculate the elapsed time (as an absolute value) between a Date object and a
// specified date.
Date.prototype.elapsedTime = function (t1) {
// Calculate the elapsed time relative to the specified Date object.
if (t1 == undefined) {
// Calculate the elapsed time from "now" if no object is specified.
t1 = new Date( );
} else {
// Make a copy so as not to alter the original.
t1 = new Date(t1.getTime( ));
}
// Use the original Date object's time as one endpoint. Make a copy so as not to
// alter the original.
var t2 = new Date(this.getTime( ));
// Ensure that the elapsed time is always calculated as a positive value.
if (t1 < t2) {
temp = t1;
t1 = t2;
t2 = temp;
}
// Return the elapsed time as a new Date object.
var t = new Date(t1.getTime() - t2.getTime( ));
return t;
};
The elapsedYears( ) and elapsedDays(
) methods return the year and day relative to the Epoch
time.
Date.prototype.elapsedYears = function ( ) {
return this.getUTCFullYear( ) - 1970;
};
Date.prototype.elapsedDays = function ( ) {
return this.getUTCDate( ) - 1;
};
Here are some examples of how to use the custom methods. Note that
the built-in Date class methods can be used to
retrieve the elapsed months, hours, minutes, seconds, and
milliseconds, but the custom elapsedYears( ) and
elapsedDays( ) methods must be used to retrieve
the elapsed years and days.
#include "Date.as"
// Calculate someone's age based on his birthday.
birthday = new Date(1966, 3, 17);
age = birthday.elapsedTime( );
trace("Elapsed Years " + age.elapsedYears( ));
trace("Elapsed Months " + age.getUTCMonth( ));
trace("Elapsed Days " + age.elapsedDays( ));
trace("Elapsed Hours " + age.getUTCHours( ));
trace("Elapsed Minutes " + age.getUTCMinutes( ));
trace("Elapsed Seconds " + age.getUTCSeconds( ));
trace("Elapsed Milliseconds " + age.getUTCMilliseconds( ));
// Calculate an interval between two dates.
firstDay = new Date(1901, 0, 1, 0, 0, 0, 0);
lastDay = new Date(2000, 11, 31, 23, 59, 59, 999)
century = firstDay .elapsedTime(lastDay);
trace("Elapsed Years " + century.elapsedYears( ));
trace("Elapsed Months " + century.getUTCMonth( ));
trace("Elapsed Days " + century.elapsedDays( ));
trace("Elapsed Hours " + century.getUTCHours( ));
trace("Elapsed Minutes " + century.getUTCMinutes( ));
trace("Elapsed Seconds " + century.getUTCSeconds( ));
trace("Elapsed Milliseconds " + century.getUTCMilliseconds( ));
Naturally, there are many other potential ways to implement the
elapsed date/time functionality. We have chosen the simplest for
illustration purposes. For a more elegant and time-consuming
solution, you could implement an elapsedDate
class as a subclass of the Date class. The
custom class could override the built-in getUTCFullYear(
), getUTCDate( ), getYear(
), setUTCDate( ),
setUTCFullYear( ), and setYear(
) to get and set values relative to the Epoch instead of
absolute values. Such an implementation is left as an exercise to the
reader.
10.6.4 See Also
Recipe 10.8
|