Date arithmetic can be dangerous

Every time I review code others have written, I blame people for doing date arithmetic of their own. However, some time ago, I received a pull request for a module that had some date arithmetic inside. As all tests passed, I could not see something dangerous in it and followed the pull request. Today, I found the date tests failing. Why? Why today? Well, this is worth some investigation.

The main part of the module generates an HTTP-Header using this construct ($c is the mocked catalyst context, expire_in is a method containing the nr of seconds to expire in):

$c->response->headers->expires(time() + $self->expire_in)
    if ....some_condition...

Well, adding a number of seconds to an epoch value cannot hurt. Can it? The test looked like this:

my $expected_date =
    (DateTime->now 
     + DateTime::Duration->new(seconds => $controller->{expire_in}
    )->strftime('%a, %d %b %Y %H:%M:%S GMT');
is $c->response->header('expires'), $expected_date,
   'expired header date is OK';

For the test case having an expiry of 3 years, the test failed with a difference of exactly one second:

#   Failed test 'expired header date is OK'
#   at t/5-expire.t line 55.
#          got: 'Thu, 19 Feb 2015 18:27:59 GMT'
#     expected: 'Thu, 19 Feb 2015 18:27:58 GMT'
not ok 4 - expired header date is OK

One second? Do we have a rounding problem? No. After a while, I remembered that DateTime mentioned a leap-second in its Change-Log some time ago. This could explain our difference.

Doing time-calculations by ourselves is wrong. Replacing the header generation with a more complicated but correct construct worked. Using DateTime before 0.71 kept the old date arithmetic-tests working, but actually the tests should have failed, because the leap-second was ignored.

$c->response->headers->expires(
    DateTime->now
            ->add(seconds => $self->expire_in)
            ->epoch
)

My personal conclusion: Date arithmetic is dangerous. Be more strict to any kind of date arithmetic. Never ever do date-math again. Never. Instead: keep all date manipulating modules at a current version and trust in them. Their authors known what they are doing, I don't. I didn't :-(

5 Comments

Just because you doesn't know how date arithmetic is done, doesn't meen it's dangerous! Assuming that a day consist of 60*60*24 seconds is ignorant!

I once had a similar problem, then partly due to daylight savings times. It's an interesting question: should tests of date/time be based on the module testers environment, or on a specific environment established by the module author? My preferred solution was to avoid DateTime->now in tests, because it means the tests are now dependent on whatever date/time context is in force when the tests are run. I used a chosen specific time for most tests, because that allowed my tests to specifically check particular leap intervals and timezone switches.

Doing date/time arithmetic yourself [properly] is a lot of work. Leap-seconds, in the grand scheme of things, are relativity easy to deal with but daylight savings changes are a real nightmare. Handling them properly requires keeping track of the entire history of changes of every geographic locale.

Leave a comment

About Wolfgang Kinkeldei

user-pic I blog about Perl.