Perl Cookbook

Perl CookbookSearch this book
Previous: 2.19. Program: Calculating Prime Factors Chapter 3 Next: 3.1. Finding Today's Date
 

3. Dates and Times

It is inappropriate to require that a time represented as seconds since the Epoch precisely represent the number of seconds between the referenced time and the Epoch.

- IEEE Std 1003.1b-1993 (POSIX) Section B.2.2.2

3.0. Introduction

Times and dates are important things to be able to manipulate. "How many users logged in last month?", "How many seconds should I sleep, if I want to wake up at midday?", and "Has this user's password expired yet?" are all common questions whose answers involve surprisingly non-obvious manipulations.

Perl represents points in time as intervals, measuring seconds past a point in time called the Epoch . On Unix and many other systems, the Epoch was 00:00 Jan 1, 1970, Greenwich Mean Time (GMT).[ 1 ] On a Mac, all dates and times are expressed in the local time zone. The gmtime function returns the correct GMT time, based on your Mac's time zone offset. Bear this in mind when considering the recipes in this chapter. The Macintosh's Epoch seconds value ranges from 00:00 Jan 1, 1904 to 06:28:15 Feb 6, 2040.

[1] These days GMT is increasingly referred to as UTC (Universal Coordinated Time).

When we talk about dates and times, we often interchange two different concepts: points in time (dates and times) and intervals between points in time (weeks, months, days, etc.). Epoch seconds represent intervals and points in the same units, so you can do basic arithmetic on them.

However, people are not used to working with Epoch seconds. We are more used to dealing with individual year, month, day, hour, minute, and second values. Furthermore, the month can be represented by its full name or its abbreviation. The day can precede or follow the month. Because of the difficulty of performing calculations with a variety of formats, we typically convert human-supplied strings or lists to Epoch seconds, calculate, and then convert back to strings or lists for output.

For convenience in calculation, Epoch seconds are always calculated in GMT. When converting to or from distinct values, we must always consider whether the time represented is GMT or local. Use different conversion functions depending on whether you need to convert from GMT to local time or vice versa.

Perl's time function returns the number of seconds that have passed since the Epoch  - more or less.[ 2 ] To convert Epoch seconds into distinct values for days, months, years, hours, minutes, and seconds, use the localtime and gmtime functions. In list context, these functions return a nine-element list with the following elements:

[2] Well, less actually. To be precise, 21 seconds less as of this writing. POSIX requires that time not include leap seconds, a peculiar practice of adjusting the world's clock by a second here and there to account for the slowing down of the Earth's rotation due to tidal angular-momentum dissipation. See the sci.astro FAQ, section 3, in http://sciastro.astronomy.net/sci.astro.3.FAQ .

Variable

Values

Range

$sec

seconds

0-60

$min

minutes

0-59

$hours

hours

0-23

$mday

day of month

1-31

$month

month of year

0-11, 0 == January

$year

years since 1900

1-138 (or more)

$wday

day of week

0-6, 0 == Sunday

$yday

day of year

1-366

$isdst

0 or 1

true if daylight savings is in effect

The values for second range from 0-60 to account for leap seconds; you never know when a spare second will leap into existence at the urging of various standards bodies.

From now on, we'll refer to a list of day, month, year, hour, minute, and seconds as DMYHMS, for no better reason than that writing and reading "distinct day, month, year, hour, minute, and seconds values" is wearisome. The abbreviation is not meant to suggest an order of return values.

Perl does not return a two-digit year value. It returns the year minus 1900, which just happens to be a two-digit number through 1999. Perl doesn't intrinsically have a Year 2000 problem, unless you make one yourself. (Your computer, and Perl, may have a 2038 problem, though, if we're still using 32 bits by that time.) Add 1900 to get the full year value instead of using the construct "19$year" , or soon your programs will refer to the year "19102" . We can't pin down the year value's range because it depends on how big an integer your operating system uses for Epoch seconds. Small integers mean a small range; big (64-bit) integers mean a very big range.

In scalar context, localtime and gmtime return the date and time formatted as an ASCII string:





Fri Apr 11 09:27:08 1997







The standard Time::tm module provides objects that give you a named interface to these values. The standard Time::localtime and Time::gmtime modules override the list-returning localtime and gmtime functions, replacing them with versions that return Time::tm objects. Compare these two pieces of code:

# using arrays
print "Today is day ", (localtime)[7], " of the current year.\n";




Today is day 117 of the current year.





# using Time::tm objects
use Time::localtime;
$tm = localtime;
print "Today is day ", $tm->yday, " of the current year.\n";




Today is day 117 of the current year.



To go from a list to Epoch seconds, use the standard Time::Local module. It provides the functions timelocal and timegm , both of which take a nine-element list and return an integer. The list's values have the same meaning and ranges as those returned by localtime and gmtime .

Epoch seconds values are limited by the size of an integer. If you have a 32-bit signed integer holding your Epoch seconds, you can only represent dates (in GMT) from Fri Dec 13 20:45:52 1901 to Tue Jan 19 03:14:07 2038 (inclusive). By 2038, it is assumed, computers will change to use larger integers for Epoch seconds. We hope. For operations on dates outside this range, you must use another representation or work from distinct year, month, and day values.

The Date::Calc and Date::Manip modules on CPAN both work from these distinct values, but be warned: years don't necessarily have 1900 subtracted from them the way the year value returned by localtime does, nor do months and weeks always start at 0. As always, consult the manpage of the appropriate module to make sure you're giving it what it expects and getting back from it what you expect. There's little more embarrassing than realizing you've calculated your company payroll based on a calendar that's 1,900 years in the past.