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);
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.
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);
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;
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);
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
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);
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);
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.
Top comments (2)
Thanks for the writeup.
One note re:
DateTime
is that I strongly encourage people to look at theformat_cldr
method instead of usingstrftime
. The former lets you access the entirety of the locale data that is available in CLDR (throughDateTime::Locale
), whilestrftime
is much more limited.Hi Dave,
Thanks for this article - it takes me back. I bought Data Munging with Perl when it came out, and I joined the group with Dave Rolsky and others during the development of DateTime (I watched and kibbitzed). My question for you comes from how I approached using DateTime early on, when it was emphasized that its priority was accuracy and correctness rather than speed. For uses in which quick responsiveness was required, initialization of DateTime seemed to be an issue.
A solution I developed back then was to use DateTime's capabilities to generate a read-only SQLite database with a record for each date in the relevant range for the application. Each date record had fields for a few dozen attributes such as day, week, and month of the year, start and end dates of the date's week & month, day & month names and abbreviations (English in my uses, but localizable), and more. My calendaring apps - which by their nature deal with many dates at a time - could then do quick lookups and date arithmetic, without the overhead of loading DateTime or another similar module. The app using the date db was also provided an api to generate additional date info if needed, only then loading DT.
Today I still maintain production calendaring apps that use this approach. I've encapsulated the db generation and extension api into a simple module, and now I rarely find myself needing to think through how to get a quick date-related result for some use. Of course machines are considerably faster today, and DateTime is more sleek and powerful (although still favoring correctness over speed). So I'm wondering whether pre-calculating date info is still a good approach. To benchmark a real-world comparison would take a big re-factoring of one of my apps to make more direct use of DT, so I haven't done that yet. But I'm thinking about whether the future development path for my apps should entail moving away from the pre-calc approach. Or maybe I should publish my calendar generating module in case it's still a viable way to go.
Any thoughts?
Thanks!