$Header: /home/cvs/lpm/meetings/dates1.html,v 1.1.1.1 2001/02/12 03:23:29 rbowen Exp $
Date and calendar calculations with Perl, and other esoterica about calendars other than the one that you're probably used to using, the Gregorian calendar. And some interesting esoterica about that one, too, while we're at it.
You can always get the latest version of this document from http://dates.rcbowen.com/ or wherever that page tells you it has been moved to.
This will hopefully eventually develop into a comprehensive document about how to handle dates and calendar manipulation and calculation with Perl.
Originally titled ``How To Get a Date With Perl,'' this document will cover everything from the seemingly easy, but really rather tricky task of finding out when Thanksgiving is, to the seemingly tricky, but really very easy task of finding out what day of the week you were born on.
This document is a collaborative effort between Rich Bowen and Adam ``Ziggy'' Turoff. Other contributors are welcome.
Perl has a few built-in functions to tell you what time it is, and, used carefully, they can answer many of the most frequent time-related questions.
Before we get very far down this road, it will be necessary to understand what epoch time (aka Unix time, aka CTime) is, and why we are using this strange number to track time. And, of course, the main problems with using it at all.
Time is measured, in Perl, C, and a number of other places, as the number of seconds since January 1, 1970. How this came to be the case is a tale for another version of this document.
There are two main concerns with using such a strange number to track time. At least, for the purposes of this document I think there are just two. There are some others like ``wow, what a stupid way to keep track of time,'' but they are beyond the scope of this document.
First, there's the fact that you cannot easily talk about dates before 1970. For many in the programming community, this is not a particularly big deal. But, as you'll shortly see, it make certain seemingly simple calculations really quite difficult.
Second, the more pervasive one, is that we can't count past 2038. The reason for this is that the number in which the date is stored is a 32-bit integer. Unfortunately, this number will roll over in 2038. One hopes that we'll solve this problem before then. But then, that's what everyone said about the Year 2000 problem, and, lo, we did run about like decapitated chickens for much of the latter half of 1999.
So, as I write this, the time is 969739381. This number may seem rather meaningless right now, but Perl gives us some functions to make sense out of it.
Perl provides three functions for getting your system time and doing
useful things with it. These functions are time, localtime,
and gmtime.
time gives you the current time, in the above-described
epoch time.
% perl -e 'print time;'
969762954
localtime and gmtime both take a time value as an argument,
and return a list of values, breaking that time value down into
various useful components.
From the Perl documentation (perlfunc, to be precice):
# 0 1 2 3 4 5 6 7 8
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
The difference between localtime and gmtime is, as the
names suggest, that localtime gives this information in
your local time zone, while gmtime gives the information
in terms of GMT (Greenwich Mean Time) aka UTC (Coordinated
Universal Time), sometimes also called Zulu Time.
It is also important to know that some of these values are not what you might expect. Specifically, some of them have an offset.
The month value is the month of the year minus one. The reason for this is that arrays in Perl start numbering at 0, and since this value will frequently be used as an index into an array of month names, the numbering here starts at 0 also. Thus, January is 0, October is 9, and so on.
The year value is offset by 1900. That is, in 1987, $year
in the above statement would be set to 87. And in 2000,
$year would be set to 100.
The week day is also zero-based, starting on Sunday.
Finally, the value of $isdst is either 1, indicating that
the time specified is in daylight savings time, or 0,
indicating that it is not.
gmtime returns the same elements of information, except
for the last field.
Given the above functions, determining what day today is, is a simple matter.
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
$year += 1900;
$mon++;
print "Today is $mon/$mday/$year\n";
Or, perhaps we wanted to know what day of the week today is.
$wday = (localtime(time))[6];
$day_of_week = ('Sunday', 'Monday', 'Tuesday',
'Wednesday', 'Thursday', 'Friday', 'Saturday')[$wday];
print "Today is $day_of_week.\n";
To answer these questions, you need to know a magical number. 86400. That's the number of seconds in a day. So, to find out when tomorrow is, just add 86400 to the time value for now. Or subtract that number for yesterday.
$wday = (localtime(time-86400))[6];
$day_of_week = ('Sunday', 'Monday', 'Tuesday',
'Wednesday', 'Thursday', 'Friday', 'Saturday')[$wday];
print "Yesterday was $day_of_week.\n";
Occasionally, folks tell me that this method is unreliable. Due to leap seconds, this method is occasionally incorrect. If, for example, you were to run the above code actually during that extra second that they throw in at the end of the year, you'd get the wrong answer. What I usually tell these people is that they should be watching Dick Clark, not writing Perl code, in the last second of the year.
Date calculations would be really simple if it were not for leap years. Of course, as the ancient Egyptians discovered, without having a leap year, the Nile floods at a different time every year, and it's a little hard to play more than a few years in advance for your family reunion, because that date might be in the winter a few years from now.
Fortunately, there's a quick and easy way to determine whether it's a leap year. Leap years are every 4 years. Except when they're not. Here's the rule. Every forth year is a leap year, except for years that are divisible by 100, but not by 400. Thus, century marks are not leap years, unless they are also divisible by 400. 2000 is a leap year.
So, reduced to Perl code, this can be turned into the following function:
sub isleap {
my ($year) = @_;
$year += 1900;
return 1 if ( ($year % 4 == 0) &&
( ($year % 100) || ($year % 400 == 0) ) );
return 0;
}
print "It's a leap year\n" if isleap((localtime(time))[5]);
All of the above questions can be answered with built-in functions from Perl. Once we start asking slightly more difficult questions, it becomes necessary to start using some of the modules available on CPAN. You could figure these questions out based only on the Perl built-ins, but some people have gone to a lot of time and trouble to provide you with algorithms for making things easier, so you might as well exercise your laziness and install a few modules.
On December 7, 1941, the US fleet in Pearl Harbor, Hawaii, was bombed by the Japanese, precipitating the entry of the United States into the Second World War.
The question of how long ago that actually was is a slightly more difficult one that it might appear at first, for the simple reason that, as far as Perl is concerned, time began on January 1, 1970. Now, whether it makes any sense at all to have the beginning of time during the Nixon administration, what this means is that figuring out date information for events that happened before this takes a little trickery.
There are several ways to approach this question.
The hard way would be to count how many years ago the event
happened, and then count all the days between the event and
January 1 of this year, making sure to count leap days. Then,
just add to that number what day of the year today is, which
localtime() gives you.
The easy answer is to use Date::Calc, which has a function
specifically to answer this question:
use Date::Calc;
$days = Delta_Days(1945, 12, 7,
$year, $month, $day);
Of course, much of getting the right answer is in asking the right question. The above gives us the number of days since the event, rather than the number of years, months, or hours.
Most other techniques involve converting each date into whatever
the representation is in the particular module of your choice,
(Time::JulianDay, for example) and then calculating the difference
in the two values.
$Log: dates1.html,v $
Revision 1.1.1.1 2001/02/12 03:23:29 rbowen
Lexington.pm web site
Revision 1.3 2000/12/11 03:17:00 rbowen
Added "Perl Harbor" question.
Revision 1.2 2000/09/28 03:03:31 rbowen
More additions.
Revision 1.1.1.1 2000/09/24 02:03:34 rbowen
Initial import
Revision 1.1 2000/09/23 20:07:07 rbowen
Initial revision
Rich Bowen <rbowen@rcbowen.com> is the Head Monkey Wrangler and Chief Squirrel Tamer at The Creative Group ( http://www.cre8tivegroup.com ), a Full Service Media Solutions Provider, serving many Full Problem Providers.
Adam Turoff is a ... um ... Ziggy? You want to say something here?