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.
use Time::Piece; use Time::Seconds; my $days = shift // 10; my $now = localtime; say $now + ($days * ONE_DAY);
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
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.
use Time::Piece; use Time::Seconds; my $now = localtime; my $days = $now->day_of_week + 1; say $now - ($days * ONE_DAY);
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.
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;
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.
use DateTime; my $days = shift // 10; my $now = DateTime->now; say $now->add(days => $days);
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:
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.
use DateTime; my $now = DateTime->now;; my $days = $now->day_of_week + 1; say $now->subtract(days => $days);
This is also very similar in shape to the Time::Piece version. We're just converting the same logic to the DateTime syntax.
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);
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
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.