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.


Randal L. Schwartz is a renowned expert on the Perl programming language (the lifeblood of the Internet), having contributed to a dozen top-selling books on the subject, and over 200 magazine articles. Schwartz runs a Perl training and consulting company (Stonehenge Consulting Services, Inc of Portland, Oregon), and is a highly sought-after speaker for his masterful stage combination of technical skill, comedic timing, and crowd rapport. And he's a pretty good Karaoke singer, winning contests regularly.

Schwartz can be reached for comment at merlyn@stonehenge.com or +1 503 777-0095, and welcomes questions on Perl and other related topics.