« Linux: Wine Turns to Vinegar | Main | Autodesk: Underwater archaeology with AutoCAD in 1984 »

Saturday, July 28, 2007

Perl: 2038 "Black Tuesday" Bug in Perl 5.8 gmtime and localtime Functions

Having more or less escaped cataclysm when the year rolled over from 1999 to 2000 (doomsayers having feared calendars would suddenly begin to read “Januark” due to the fabled y-to-k bug), the next big milestone about which to fret is January 19th, 2038, Black Tuesday, when Unix time() values, the number of seconds since January 1st, 1970, go negative if stored in a signed 32-bit integer.

Now, most people assume that long before that date, everybody will have migrated to 64-bit or wider platforms which are immune to this problem, or to programming languages with less constrained representations of date and time than legacy C libraries. But the future is long in the tooth, and it can bite you even today. Consider: next January, calculations related to a 30 year mortgage will run into the 2038 bug if performed on software vulnerable to it. My own humble Web application, The Hacker's Diet Online, ran afoul of it a couple of days ago, when a user experimenting with values in the diet calculator ended up with a diet end date beyond 2038 and saw, instead, a date in the past.

This was due to a vulnerability in Perl 5.8 to the underlying C library of which I was previously unaware. Perl does all of its arithmetic in 64-bit floating point, so I naïvely assumed that its gmtime and localtime functions were coded so as to correctly handle dates far beyond 2038, where 32-bit signed integer quantities go negative. This is not the case: Perl simply calls the corresponding C library functions and returns whatever they return, first truncating the value to the time_t type, which on a 32-bit platform will be 32 bits.

You can see the consequences of this by running the following tiny Perl program:

    my $years = 20;
    my $yearsec = 365.2425 * 24 * 60 * 60;
    my $then = gmtime(time() + ($years * $yearsec));
which will print a date around 20 years (the $years setting) in the future. But if you change the first line to set $years = 40, it prints (when run in 2007), a date in 1911—let's do the time warp again!

This problem should not occur (at least for a while!) on platforms on which Perl has been built with time_t defined as a 64-bit quantity, and is said to be fixed in Perl 6 (although I'll bet when 2038 rolls around, there are still many programs running under Perl 5.x implementations). The Hacker's Diet Online, like most of my programs, uses Julian dates almost exclusively, and only used Perl's gmtime in a few circumstances. Needless to say, I have converted all of them to use Julian dates, and if you need to handle dates beyond 2038 in your Perl programs which may run on 32-bit systems, be sure to use a package which handles arbitrary dates. I understand that the DateTime module, available from CPAN, is suitable, but I have no experience with it, as I use my own Julian module (which you can download as part of the source code for The Hacker's Diet Online) for such calculations.

Posted at July 28, 2007 22:36