Processing dates and times with Perl

Dave Cross - Nov 10 '21 - - Dev Community

Twenty years ago, I wrote a book called Data Munging with Perl. People said nice things about it, but the publishers let it go out of print several years ago. That's probably fair - to be honest a lot of its advice is looking a bit dated.

One of the things I covered was manipulating dates and times with Perl. Back then we didn't have tools like Time::Piece or DateTime so my examples used Perl's built-in date and time functions and the "state of the art" CPAN modules Date::Calc and Date::Manip.

If there were one section of the book that I could go back and rewrite, it would be this one. Date and time handling in Perl has come on a long way in the last twenty years and it pains me to see people still using things like Date::Manip.

The book took three common examples:

  • Finding the date in x days time
  • Finding the date of the previous Saturday
  • Finding the date of the first Monday in a given year

Following a discussion on Reddit, I thought it would be interesting to reproduce my examples using more modern date and time handling tools.

So let's see how we'd do those things using modern Perl classes.

Using Time::Piece

Finding the date in x days time

use Time::Piece;
use Time::Seconds;

my $days = shift // 10;

my $now = localtime;
say $now + ($days * ONE_DAY);
Enter fullscreen mode Exit fullscreen mode

This is pretty simple stuff. Time::Piece overrides the standard localtime() function so that I get a Time::Piece object back. I can then add seconds to that object using one of the constants defined in Time::Seconds to get the time I want.

The output I get from running this program is:

Sat Nov 20 17:02:19 2021
Enter fullscreen mode Exit fullscreen mode

Note that by just printing my Time::Piece object, I get a nicely-formatted date/time string. If the format isn't quite to my liking, I could use the strftime() method to get the format that I want.

Finding the date of the previous Saturday

use Time::Piece;
use Time::Seconds;

my $now = localtime;
my $days = $now->day_of_week + 1;
say $now - ($days * ONE_DAY);
Enter fullscreen mode Exit fullscreen mode

This is very similar to the previous example. We get a Time::Piece object that contains the current date and time and then work out how many days to go back to get to the previous Saturday. The day_of_week() method returns a number between 0 and 6, with Sunday being 0. We need to add one to that number to get to Saturday.

Finding the date of the first Monday in a given year

use Time::Piece;
use Time::Seconds;

my $year = localtime->year;

my $first_mon = Time::Piece->strptime("$year Jan 1", '%Y %b %e');

$first_mon += (8 - $first_mon->day_of_week) % 7 * ONE_DAY;

say $first_mon;
Enter fullscreen mode Exit fullscreen mode

This also works on a very similar principle. We get the current year and create a Time::Piece object that contains the 1st January from that year. We then just work out how many days (perhaps zero) we need to add to get to a Monday.

Using DateTime

Finding the date in x days time

use DateTime;

my $days = shift // 10;

my $now = DateTime->now;
say $now->add(days => $days);
Enter fullscreen mode Exit fullscreen mode

The form of this is pretty similar to the Time::Piece example. We can use DateTime->now to get a DateTime object containing the current date and time and then use the add() method on that to add a number of days.

The output I get from running this program is:

2021-11-20T18:51:57
Enter fullscreen mode Exit fullscreen mode

Note that, like Time::Piece, we can just print a DateTime object and get a nicely-formatted string. I prefer DateTime's default format as it uses the ISO standard for dates and times. But, as with Time::Piece, there's a method called strftime() that you can use to produce strings in other formats.

Finding the date of the previous Saturday

use DateTime;

my $now = DateTime->now;;
my $days = $now->day_of_week + 1;
say $now->subtract(days => $days);
Enter fullscreen mode Exit fullscreen mode

This is also very similar in shape to the Time::Piece version. We're just converting the same logic to the DateTime syntax.

Finding the date of the first Monday in a given year

use DateTime;

my $year = DateTime->now->year;

my $first_mon = DateTime->new(
  year  => $year,
  month => 1,
  day   => 1,
);

my $days = (8 - $first_mon->day_of_week) % 7;

say $first_mon->add(days => $days);
Enter fullscreen mode Exit fullscreen mode

And this is another case where we're mostly just translating Time::Piece syntax to DateTime syntax. The only other real difference is that DateTime has a real constructor method, whereas to construct a Time::Piece object for an arbitrary date we needed to create a string a parse it using strptime().

Conclusions

I hope you can see from these examples that using more modern date and time tools can make you code smaller and easier to understand than it would be if you used Perl's built-in functions for this kind of work.

Here are a few advantages that I think you get from using these libraries:

  • Storing your date and time in a single, structured variable rather than separate scalars for the various parts of a date and time.
  • Easy parsing of date and time strings into an object.
  • Easy production of many different output formats.
  • Easy addition and subtraction of dates and times.
  • Objects are easy to compare (and, therefore, sort).
  • You no longer need to care that localtime() gives a month that's between 0 and 11 or a year that is the actual year minus 1900.

I haven't shown it here, but these classes also understand timezones - so that's another area that will give you far fewer headaches if you switch to using these classes.

I've covered what are probably the two most popular modern date and time classes for Perl. I expect you're wondering how you would decide which one to use.

  • Time::Piece has been included in the standard Perl distribution since version 5.10 was released in 2007. It's therefore good in situations where it's hard to install CPAN modules.
  • DateTime needs to be installed from CPAN, but it's an incredibly powerful module and sits at the centre of a massive ecosystem of other date and time classes. If, for example, you want to deal with calendars other than the Gregorian calendar, then there's probably a DateTime add-on that can help you.

The rule of thumb that I use is this - if it's a simple project and Time::Piece can do the job, then I use that. Otherwise, I reach for DateTime or one of its friends.

But honestly, learning to use these date and time modules will make your programming life easier.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .