The 2003 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 14th day of Advent my True Language brought to me..
Date::Parse

Quite often you need to specify a time and date to a computer program. This can be hard. And confusing. And you can easily make mistakes.

The way Perl natively deals with times is to use Unix epoch seconds, which is the number of seconds that have elapsed since midnight on the first of January 1970. This is not particularly easy to deal with - it's very hard to know 1071357230 means 11:13:50pm on the 13th of December. It's much easier to specify the dates in human readable strings and convert these to epoch seconds when you need to. And the easiest way to do this is Date::Parse.

There's really not that much to say about this module, except it's a lot less convoluted to use than Time::Local. When you load it it exports one function, str2date, which converts a string to epoch seconds. And that's it - really, it is that easy.

  #!/usr/bin/perl
  # turn on perl's safety features
  use strict;
  use warnings;
  # get what was specified on the command line
  my $time = join ' ', @ARGV;
  # convert that to seconds
  use Date::Parse;
  my $secs = str2time($time);
  # check we got something useful back
  unless (defined $secs)
   { die "Can't parse '$time' as a time or date\n" }
  # print out the result
  print "'$time' parses as:\n",
        "'$secs' (".gmtime($secs).")\n";

This works on pretty much any string I feel like throwing at it that vaguely looks like a common time:

  bash-2.05b$ time2epoch 4th March 1978 5:30pm
  '4th March 1978 5:30pm' parses as:
  '257880600' (Sat Mar  4 17:30:00 1978)

Of course, it does get some things 'wrong'

  bash-2.05b$ time2epoch 4/3/1978 5:30pm
  '4/3/1978 5:30pm' parses as:
  '260469000' (Mon Apr  3 16:30:00 1978)

But that's because the module is using the American standard not the English. Of course, this is your own fault for using an ambiguous date format. The ISO standard date format is of course supported, and should be favoured:

  bash-2.05b$ time2epoch 1978-03-04 17:30
  '1978-03-04 17:30' parses as:
  '257880600' (Sat Mar  4 17:30:00 1978)
  bash-2.05b$ time2epoch 19780304T1730
  '19780304T1730' parses as:
  '257880600' (Sat Mar  4 17:30:00 1978)

Note that the module will try and do it's best to work out what exactly you mean with respect to timezones. If you don't specify a timezone it'll assume that it's in localtime. For example, if I tell it 2pm on the 4th of July this year, it'll assume (since I'm in the UK and that's in the summertime and we're one hour ahead of GMT then) that I mean 1pm on the 4th of July GMT

  bash-2.05b$ time2epoch 4th July 2003 2pm
  '4th July 2003 2pm' parses as:
  '1057323600' (Fri Jul  4 13:00:00 2003)

I can always tell it I really mean GMT

  bash-2.05b$ time2epoch 4th July 2003 2pm GMT
  '4th July 2003 2pm GMT' parses as:
  '1057327200' (Fri Jul  4 14:00:00 2003)

There are a wide range of formats that Date::Parse accepts, all of which are covered in the manual page.

The only problem with Date::Parse that you should be aware of is that the module will not cope with dates outside of the range of epoch seconds. This means that you can only represent times between Thursday January 1st 00:00:00 1970 and Tuesday January 19 03:14:07 2038. This isn't a problem for me in day to day computer operation.

Number::Compare::Date

Number::Compare allows you to produce objects that allow arbitrary comparisons on numbers. For example, you can create an object that checks if a number is less that a hundred.

   use Number::Compare;
   my $compare = Number::Compare->new("<100");
   print "Will print" if $compare->(30);
   print "Won't print" if $compare->(200);

This is most useful when you're trying to create some arbitrary test that you then pass into a subroutine. For example, a subroutine that checks if the current time against what you pass.

   if (compare_time(">=1072916000"))
    { print "Perl 5.8.3 should have been released\n" }     
  
   use Number::Compare;
   sub compare_time
   {
      my $comparison = Number::Compare->new(shift);
      return $comparison->(time());
   }

Of course, this is fine, but who knows that 1072916000 means the turn of new year. After two or three costly mistakes, I decided to extend Number::Compare to allow you to use anything that Date::Parse can parse:

   if (compare_time(">=1st Jan 2004 GMT"))
    { print "Perl 5.8.3 should have been released\n" }     
  
   use Number::Compare::Date;
   sub compare_time
   {
      my $comparison = Number::Compare::Date->new(shift);
      return $comparison->(time());
   }

  • Number::Compare
  • Number::Compare::Date