Perl 6 small stuff #10: Q: How many seconds is a day? A: 86.400, 82.800 and 90.000

Over on Twitter Joelle Maslak (Twitter handle @jmaslak) asked a question about a date and time problem I honestly had never thought about.

How to calculate the number of seconds in a day on dates starting or ending DST? I’ve always just assumed that 86.400 is the correct answer. It makes sense, doesn’t it? 24 hours * 60 minutes * 60 seconds = 86.400.

Joelle reminded me that these two days a year — at least here in Norway — are either more or less than 86.400. This year (2018) we switched to DST March 25. So I thought that, surely, that had to be something Perl’s Datetime object could figure out? To be honest I thought Joelle had misunderstood something. As it turned out, she hadn’t:

my $d1 = Date.new("2018-03-25").DateTime.posix;
my $d2 = Date.new("2018-03-26").DateTime.posix;
say $d2 - $d1;
Output:
86400

Stubborn as I am I double checked what’d happen if I checked October 28, 2018 (the day Norway switches from DST to winter time).

my $d1 = Date.new("2018-10-28").DateTime.posix;
my $d2 = Date.new("2018-10-29").DateTime.posix;
say $d2 - $d1;
Output:
86400

It seems that DateTime ignores daylight savings time here, and counts 1 day from the first to the latter. I guess there are good reasons for this — without having checked the underlying code; perhaps it is based on UTC which is without all that DST nonsense?

No matter what the reason is: Those of us with locales and time zones that suffer DST changes twice yearly, and who experience that days sometimes are longer, sometimes shorter, knows that the reality feels and is different. What to do?

Well, sometimes it helps to have lived for a while, because I vaguely remembered that Perl 5’s localtime function returned an array with an element that flagged whether that particular time was DST or not (the Perl 5 localtime documentation calls this flag $isdst). Alas, it seemed that Perl 6 didn’t have the similar built-in.

Researching this I found Liz’s module Time::localtime. It basically ports Perl 5’s localtime functions to Perl 6 [1]. Using zef to install it, I had what it took to write a code snippet that answered Joelle’s original question:

#!/usr/bin/env perl6
use Time::localtime;
sub seconds-in-a-day($start-date) {
my $d1 = Date.new($start-date).DateTime.posix;
my $d2 = Date.new($start-date).DateTime.later(days => 1).posix;
my $l1 = localtime($d1);
my $l2 = localtime($d2);
return ($l2.isdst ?? $d2 - 3600 !! $d2)
- ($l1.isdst ?? $d1 - 3600 !! $d1);
}
say seconds-in-a-day("2018-03-25");
say seconds-in-a-day("2018-05-17");
say seconds-in-a-day("2018-10-28");
Output:
82800
86400
90000

So now we’ve solved Joelle’s problem. The number of seconds in a day is 86.400 and 82.800 and 90.000. Case closed…


…or is it? Since one day is 82.800, another is 90.000 and the rest is 86.400, a year surely consists of 365 * 86.400 = 31.536.000? Well, yes, unless you consider leap years which consists of 366 days.

Luckily, DateTime handles leap years perfectly and reports that the year 2016 consists of 31.622.400 seconds. Great. That was what I expected. Case closed…


…or is it? No, because 2016 also had a leap second, so the answer should really have been 31.622.401 seconds. If I’m honest we’ve now entered the realm of obscure details. But having fixed the 86.400 DST problem, it’s infuriating to learn that a day really isn’t 86.400 seconds long either. Rather it’s 86.400 and a few milliseconds; every year the day is slowing a little more as well.

This phenomena is called solar drift. From time to time it’s decided that we need a leap second to compensate for the drift. A bureau overlooks this, and the rule of thumb is that every time the drift closes in or exceeds 0.6 seconds compared to how we count, they insert a leap second (the goal is to keep the difference less than 0.9 seconds at all times). The leap second comes at irregular times and as such is probably difficult to take into count when programming date calculations. For these reasons I guess we shouldn’t expect core Perl 6 to handle it.

Anyway, we’re lucky that Joelle didn’t ask about these things as well, because that would have become a very different article :-)

Update: Read Brad Gilbert’s excellent comment below. Perl 6 do take leap seconds into consideration, provided that the programmer knows how to ask for it. I sure didn’t.


Notes

[1] There’s another module that implements a version of is-dst as well, DateTime::DST. For all I know it works as well as Time::localtime, but I couldn’t get it to install on MacOS. My guess is that I (or MacOS in general) lack some kind of OS level shared library for this to work. Luckily Time::localtime did the job for me.

Like what you read? Give Jo Christian Oterhals a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.