DekGenius.com
[ Team LiB ] Previous Section Next Section

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

    [ Team LiB ] Previous Section Next Section