Copyright Notice
This text is copyright by CMP Media, LLC, and is used with their permission. Further distribution or use is not permitted.This text has appeared in an edited form in SysAdmin/PerformanceComputing/UnixReview magazine. However, the version you are reading here is as the author originally submitted the article for publication, not after their editors applied their creativity.
Please read all the information in the table of contents before using this article.
Unix Review Column 25 (Apr 1999)
Both the computer literature and the popular press are abuzz in full swing with ``end of the millenium'' and ``Y2K'' stories, some positive, some frightening. Many people have been asking ``is Perl Y2K compliant?'' Well, here's what the Perl documentation itself has to say about that:
Does Perl have a year 2000 problem? Is Perl Y2K compliant?
Short answer: No, Perl does not have a Year 2000 problem. Yes, Perl is Y2K compliant. The programmers you're hired to use it, however, probably are not.
Long answer: Perl is just as Y2K compliant as your pencil--no more, and no less. The date and time functions supplied with perl (gmtime and localtime) supply adequate information to determine the year well beyond 2000 (2038 is when trouble strikes for 32-bit machines). The year returned by these functions when used in an array context is the year minus 1900. For years between 1910 and 1999 this happens to be a 2-digit decimal number. To avoid the year 2000 problem simply do not treat the year as a 2-digit number. It isn't.
When gmtime()
and localtime()
are used in scalar context they
return a timestamp string that contains a fully- expanded year. For
example: $timestamp = gmtime(1005613200)
sets $timestamp
to
"Tue Nov 13 01:00:00 2001"
. There's no year 2000 problem here.
That doesn't mean that Perl can't be used to create non- Y2K compliant
programs. It can. But so can your pencil. It's the fault of the
user, not the language. At the risk of inflaming the NRA: ``Perl
doesn't break Y2K, people do.'' See
http://language.perl.com/news/y2k.html
for a longer exposition.
So there you have it. But as long as we're on the subject of dates, let's look at a few things that Perl knows how to do with dates.
First, there's the built-in time
operator. No, not the one that
gives you a subscription to Time magazine -- just the number of
seconds since the epoch (1 January 1970, 0000 GMT) as an unsigned
32-bit number. It's 900-million-something as I write this, and will
cross one billion in a while. When exactly? Well, we can use
another of Perl's built-in functions to translate this time value
to something we humans can understand:
my $when = gmtime 1000000000; print "timestamp turns a billion at $when\n";
Running this, I get:
timestamp turns a billion at Sun Sep 9 01:46:40 2001
Ahh... so somewhere out in Arthur C. Clarke's idea of the the
new millenium, we'll get a new digit for the return value of time
.
I wonder what programs will break when that happens.
That string that came back from gmtime
is the time in GMT. Unless
you live in London or West Africa, you'll probably be happier to see
something in your local timezone. Easy enough done: just use localtime
:
my $now = localtime time; print "It is now $now\n";
Both the gmtime
and localtime
operators return a UNIX
date-like string when used in a scalar context as I've done here.
You can also pick apart the pieces using these two time functions in a
list context:
my ($sec,$min,$hour,$mday,$mon,$year, $wday,$yday,$isdst) = localtime time;
And that'll give you access to the pieces. Note that the $mon
value is 0-based (you'll probably want to add 1), and the $year
value is offset by 1900. Yes, it'll be 100 in the year 2000, so
merely fixing it with $year += 1900
will suffice for now and
forever. For more details, read perldoc -f localtime
.
To go back the other way, Perl comes with a standard module called
Time::Local
. (Documentation is online at perldoc Time::Local
unless someone botched your installation.) For example, to get
the epoch value for the beginning of the year 2000 in my local timezone,
I can use:
use Time::Local; my $time = timelocal(0,0,0,1,0,100); my $string = localtime $time; print "the big ball falls at $time => $string\n";
Here I'm printing out the reconverted string just to make sure I'm doing it right. We can use this to get a countdown counter to the rollover:
use Time::Local; my $time = timelocal(0,0,0,1,0,100); my $diff = $time - time; $diff /= 86400; # seconds to days if ($diff > 0) { printf "we've got %.2f days to go\n", $diff; } else { print "we made it!\n"; }
Of course, the value of $time
won't change between invocations, so
it'd be best to compute that once and hardwire it into the program.
But this is good for a quick demonstration. For a more elaborate
version of a countdown counter, visit my homepage at
http://www.stonehenge.com/merlyn/
.
Besides the built-in functions and libraries, there are quite a few
date and time manipulation packages in the CPAN (the Comprehensive
Perl Archive Network). The most ambitious of these is Date::Manip
,
which you can install on your system if you don't already have it
using the instructions given in perldoc perlmodinstall
. (If you
don't have perlmodinstall, you should upgrade your Perl
installation to 5.005 or later.)
As it says in the first paragraph of the documentation for Date::Manip
:
This is a set of routines designed to make any common date/time
manipulation easy to do. Operations such as comparing two times,
calculating a time a given amount of time from another, or parsing
international times are all easily done. From the very beginning, the
main focus of Date::Manip
has been to be able to do ANY desired
date/time operation easily, not necessarily quickly. Also, it is
definitely oriented towards the type of operationw we (as people) tend
to think of rather than those operations used routinely by computers.
There are other modules that can do a small subset of the operations
available in Date::Manip
much quicker than those presented here, so
if speed is a primary issue, you should look elsewhere. Check out the
CPAN listing of Time and Date modules. But for sheer flexibility, I
believe that Date::Manip
is your best bet.
However, as alluded to in this text, Date::Manip
is pretty heavy,
taking about a half a CPU second on my fairly zippy system just to
load. That's probably OK for a long-running program, but it pretty
much kills it for use in short-lived CGI applications.
For many applications, when the built-in stuff isn't sufficient, you
can turn to the fairly lightweight Date::Calc
module instead (also
found in the CPAN). This module loads (for me) in under one-tenth of
a CPU second, making it pretty easy to justify in a short-lived CGI
program, or other frequently-invoked tool.
As an example of Date::Calc
's power, I decided to emulate the UNIX
cal
command, which can print out a calendar for a month or a year
anytime up from year 1 into the far future. Unfortunate, since
Date::Calc
doesn't understand the Julian calendar (adopted in 1752
in the US), I'll limit my program to more recent years, but keep the
same command-line interface.
My program ended up looking like this:
#!/usr/bin/perl -w use strict;
use Date::Calc qw( Today Day_of_Week Days_in_Month Month_to_Text Day_of_Week_Abbreviation );
if (@ARGV) { my $year = shift; if (@ARGV) { my $month = $year; my $year = shift; die "year $year must be > 1752" unless $year > 1752; &do_cal($year,$month); } else { die "year $year must be > 1752" unless $year > 1752; for my $month (1..12) { &do_cal($year,$month); print "\n" unless $month == 12; } } } else { my ($today_year, $today_month, $today_day) = Today; &do_cal($today_year, $today_month); }
sub do_cal { my $cal_year = shift; my $cal_month = shift;
my $month_name = Month_to_Text($cal_month); my $start_dow = Day_of_Week( $cal_year,$cal_month,1 ); my $end_day = Days_in_Month( $cal_year,$cal_month );
my $curday = 1 - $start_dow % 7;
print " $month_name $cal_year\n"; print map { sprintf " %4.4s ", Day_of_Week_Abbreviation($_) } 7,1..6; print "\n"; { for ($curday..($curday+6)) { if ($_ < 1) { print "<<<<<<"; } elsif ($_ <= $end_day) { printf "[ %2d ]", $_; } else { print ">>>>>>"; } } print "\n"; $curday += 7; redo if $curday <= $end_day; } }
The most notable things here are that I'm using Date::Calc
to get
today's month and year (Today
), get the text name of a month
(Month_to_Text
), find the weekday of the first day of the month
(Day_of_Week
), find the last day of the month (Days_in_Month
),
and even get the right names for the days
(Day_of_Week_Abbreviation
).
And having all these routines already available made it very easy to
write the program. What would have made it much easier would have
been to notice that nearly all of my precious &do_cal
routine could
have been replaced by the Date::Calc
routine called Calendar
,
which I didn't notice until I had nearly finished the program.
So, the important tip is ``Don't reinvent the wheel... read all the documentation''. Next time, I'll follow my own advice. And if you still don't think Perl is useful enough, now you at least know you can get a date with Perl, or at least have a good time. Enjoy.