What Time is Midnight?

Yesterday was time change in the U. S. of A. I pulled out my iPod Touch to update a Numbers spreadsheet, and hit the "today" button to put the current date in the date column. But when I did that I got not the current date but 11 PM the previous day. Today it works as advertised.

Now, I am not privy to the internals here, but this behavior would be explained if "today" were implemented by the Objective C (or Swift, or whatever) equivalent of the following Perl:

my $date = time + $zone_offset;
$date -= $date % 86400;

This code is clean, simple, obvious ... and subtly wrong because on time change day, after the change, midnight has a different offset than the current time. This is a bug that manifests only 44 hours in every year. I think that when I want the local day I round-trip through localtime and Time::Local::timelocal. But do I really? Always? And would the above snippet be valid for UTC?

I just did ack '%\s*86000\b' on my Perl modules, and came up dry. Then I looked for '=>\s*86000;' to find relevant manifest constants (three -- I'm considering picking a name and converging all modules to it), and find the modulo operations on them. Still nothing. Am I really clean? What about you?

6 Comments

A particularly tricky one because it's a case where the timezone *offset* changes depending what time of day it is. So only a module like DateTime with a complete timezone implementation can make it "just work":

my $date = DateTime->new(year => 2017, month => 11, day => 5, hour => 15, time_zone => 'America/New_York'); # sometime during the day
my $midnight = $date->clone->truncate(to => 'day');
say $midnight->epoch;

Of course if you're working in local time, you can take all of the local time pieces (year, month day, hour, etc), set the hour/minute/second to 0, and then use your localtime library of choice to get the epoch from those parameters. But I find that too tedious for everyday work.

The problem is that not every day has 86400 seconds in it. When calculating days between dates, use noon not midnight, as the time of day. If you must know it to the second, convert the date-times to their epochs, find the difference, and find the hours minutes, and seconds for that.

Well, according to https://en.wikipedia.org/wiki/Unix_time#Leap_seconds:

The Unix time number increases by exactly 86,400 each day, regardless of how long the day is.

Correct, leap seconds do not affect the unix epoch time, as they're ignored or skipped for that purpose. Time zone offset changes are another matter, since they're applied "on top" of the epoch time. So the simplest method is to do all your date math in UTC, and use the epoch. This doesn't help when you want to know when midnight is in local time though.

Is that a typo in your last paragraph, or did you really search for 23:53:40 (86000 seconds)?

Leave a comment

About Tom Wyant

user-pic I blog about Perl.