Recipe 14.8.
Calculating Elapsed Time or Intervals Between Dates
Problem
You wan
t to calculate an elapsed
time, elapsed date, or relative time.
Solution
For simple elapsed time, you can add and
subtract from the epoch milliseconds, or use the value returned
by getTimer( ). For
more complex conversions, use the methods of the custom DateUtilities class.
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 knack for
remembering these conversion values, storing them as constants is a
convenient option. The constants have already been defined in the
custom ascb.util.DateUtilities class as follows:
public static const MILLISECOND:Number = 1;
public static const SECOND:Number = MILLISECOND * 1000;
public static const MINUTE:Number = SECOND * 60;
public static const HOUR:Number = MINUTE * 60;
public static const DAY:Number = HOUR * 24;
public static const WEEK:Number = DAY * 7;
You can use the Date.time property to
retrieve a date's current value in epoch milliseconds, and then
assign a new value to the time property relative to the
current value. The following example adds one day to a given
Date object:
var example:Date = new Date(2010, 0, 5, 10, 25);
// Displays: Tue Jan 5 10:25:00 GMT-0800 2010
trace(example);
// Add one day to the previous date by setting the new date/time
// to the original date/time plus DateUtilities.DAY (the number
// of milliseconds in a day).
example.time += DateUtilities.DAY;
// Displays: Wed Jan 6 10:25:00 GMT-0800 2010
trace(example);
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 comparing it to the current time later
during execution. You can use a Date object to make those
calculations; however, it is much more efficient to use the
flash.util.getTimer( )
function. The getTimer( ) function returns the number of
milliseconds since the Player started running. By checking its
value at successive times, the getTimer( ) function can also
be used to determine the elapsed time. The following example uses
the getTimer( ) function to repeatedly update the displayed
text:
package {
import flash.display.Sprite;
import flash.display.TextField;
import flash.util.Timer;
import flash.events.TimerEvent;
import flash.util.getTimer;
public class Example extends Sprite {
private var _text:TextField;
private var _start:uint;
public function Example( ) {
_start = getTimer( );
_text = new TextField( );
addChild(_text);
var timer:Timer = new Timer(1000);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start( );
}
private function onTimer(event:TimerEvent):void {
_text.text = (getTimer( ) - _start) + " milliseconds";
}
}
}
Here, the previous example is tweaked to create
a countdown timer:
package {
import flash.display.Sprite;
import flash.display.TextField;
import flash.util.Timer;
import flash.events.TimerEvent;
import flash.util.getTimer;
public class Example extends Sprite {
private var _text:TextField;
private var _timer:Timer;
private var _start:uint;
private var _count:uint = 20;
public function Example( ) {
_start = getTimer( );
_text = new TextField( );
addChild(_text);
_timer = new Timer(500);
_timer.addEventListener(TimerEvent.TIMER, onTimer);
_timer.start( );
}
private function onTimer(event:TimerEvent):void {
var elapsed:int = Math.round(_count - (getTimer( ) - _start) / 1000);
_text.text = ellapsed + " seconds";
if(elapsed < 0) {
_text.text = "---------";
_timer.stop( );
}
}
}
}
The earlier example calculated elapsed times
using Date objects. When it comes to adding and subtracting
years and months from dates, you 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 wraparound
calculations transparently when using the getter and
setter methods. The most effective way to handle date math
is to use the custom DateUtilities.addTo( ) method and have it
perform the calculations for you. The method takes up to eight
parameters, seven of which are defined in Table 14-2. The first
is required, and it is the Date object to which you want to
add (or subtract as the case may be). The parameters defined in
Table 14-2 are optional
numeric parameters, each of which can be positive or negative.
Table 14-2. Parameters for the addTo( )
method
Parameter |
Description |
years
|
A number of years to add to the date. |
months
|
A number of months to add to the
date. |
days
|
A number of days to add to the date. |
hours
|
A number of hours to add to the date. |
minutes
|
A number of minutes to add to the
date. |
seconds
|
A number of seconds to add to the
date. |
milliseconds
|
A number of milliseconds to add to the
date. |
The following shows how you might use the
addTo( ) method to add (and subtract) years, months, and
days to a date:
var example:Date = new Date(2010, 0, 5, 10, 25);
trace(DateUtilities.addTo(example, 10));
trace(DateUtilities.addTo(example, -4));
trace(DateUtilities.addTo(example, 0, 1, 1));
trace(DateUtilities.addTo(example, 0, -1, -1));
/* Displays:
Sun Jan 5 10:25:00 GMT-0800 2020
Thu Jan 5 10:25:00 GMT-0800 2006
Sat Feb 6 10:25:00 GMT-0800 2010
Fri Dec 4 10:25:00 GMT-0800 2009
*/
This 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 think. You might try subtracting the return
value of the time property of one Date object from
another. However, 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 easy 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.
One convenient solution is to create a
Date object representing an elapsed time. This lets you use
the Date class's built-in methods to calculate the number of
years, months, and days between two Date objects. You can
use the UTC get methods to retrieve most of the offsets, as
illustrated in the following example:
var one:Date = new Date( );
var two:Date = DateUtilities.addTo(one, 4, 1, 3, 10);
var elapsed:Date = new Date(two.time - one.time);
trace(elapsed); // Displays: Sun Feb 3 16:00:00 GMT-0800 1974
trace(elapsed.hoursUTC); // Displays: 10
trace(elapsed.monthUTC); // Displays: 1
There are several caveats, however. Although
ActionScript internally stores dates relative to the epoch time,
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 the elapsed time object to be
useful, you need to subtract constants from the year and
day values. (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 values
for these values.) Subtract 1970 from the year, and then subtract 1
from the day:
trace(elapsed.dateUTC); // Displays: 4 (incorrect)
trace(elapsed.dateUTC - 1); // Displays: 3 (correct)
trace(elapsed.dateUTC); // Displays: 1974 (incorrect);
trace(elapsed.dateUTC - 1970); // Displays: 4 (correct);
To work with elapsed times more efficiently, you
might want to use some of the methods of the custom
DateUtilities class. The class has a static method called
elapsed( ) that
returns an object with the amount of elapsed time between two
dates, as illustrated in the following example:
var one:Date = new Date( );
var two:Date = DateUtilities.addTo(one, 4, 1, 3);
var elapsed:Object = DateUtilities.elapsed(two);
for(var item:String in elapsed) {
trace(item + ": " + elapsed[item]);
}
/* Displays:
milliseconds: 0
seconds: 0
minutes: 0
hours: 0
days: 3
months: 1
years: 4
*/
If you pass only one parameter to the
elapsed( ) method, it assumes that you want to find the
amount of time that has elapsed between the specified date and the
current date.
The DateUtilities class also has several
additional static methods that return elapsed times between two
dates in specific intervals (years, months,
days, etc.). By default, each of those methods calculates
the total number of specified intervals between the two dates, as
shown here:
var one:Date = new Date( );
var two:Date = DateUtilities.addTo(one, 4, 1, 3);
trace(DateUtilities.elapsedYears(two, one)); // Displays: 4
trace(DateUtilities.elapsedMonths(two, one)); // Displays: 49
trace(DateUtilities.elapsedDays(two, one)); // Displays: 1495
trace(DateUtilities.elapsedHours(two, one)); // Displays: 35880
trace(DateUtilities.elapsedMinutes(two, one)); // Displays: 2152800
trace(DateUtilities.elapsedSeconds(two, one)); // Displays: 129168000
trace(DateUtilities.elapsedMilliseconds(two, one)); // Displays: 129168000000
Optionally, you can pass a Boolean value as a
third parameter to any of the methods. A value of TRue
causes the methods to return the relative values instead. The
elapsedYears( ) method is the exception, since it returns
the same value either way, as shown in the following
example:
var one:Date = new Date( );
var two:Date = DateUtilities.addTo(one, 4, 1, 3);
trace(DateUtilities.elapsedMonths(two, one, true)); // Displays: 1
trace(DateUtilities.elapsedDays(two, one, true)); // Displays: 3
trace(DateUtilities.elapsedHours(two, one, true)); // Displays: 0
trace(DateUtilities.elapsedMinutes(two, one, true)); // Displays: 0
trace(DateUtilities.elapsedSeconds(two, one, true)); // Displays: 0
trace(DateUtilities.elapsedMilliseconds(two, one, true)); // Displays: 0
|