Perl is now Y2038 safe

schwern on 2008-02-07T13:14:45

They said it couldn't be done. They said it SHOULDN'T be done! But I have here a working 64 bit localtime_r() on a machine with just 32 bits of time_t. Time zones, daylight savings time... it all works.

$ ./miniperl -wle 'print scalar localtime(2**35)'
Mon Oct 25 20:46:08 3058


Perl will be Y2038 safe. And yes, I'm going to get it backported to 5.10.

The underlying C functions are solid, but I just sort of rammed them down perl's throat because it's 5am. Here's the patch for the intrepid.

The underlying C implementation is a heavily modified version of the code from 2038bug.com written by Paul Sheer. He came up with the same basic algorithm I did:

1) Write a 64 bit clean gmtime(), that's a SMOP and Paul did that. 2) Run your time through this new gmtime_64(). 3) Change the year to a year between 2012 and 2037. 4) Run it through the 32 bit system localtime() to get time zone stuff. 5) Move the year back to the original.

The trick is using a 32 bit safe year that has the same properties as the real year. This means same leap year status and same calendar layout. Had to do some tricky Gregorian calendar math aided by Graham, Nick and Abigail who realized there's a 28 year cycle within the larger 400 year Gregorian cycle and Mark Mielke who provided a big table of 64 bit localtime() results to test against. There's also some edge cases around New Year's, but they're all taken care of.

This approach will break when daylight savings time changes, but I'd rather be off by an hour than 137 years. The full Perl patch will always prefer the system's 64 bit localtime() if one is available. As more machines upgrade time_t this hack will be used less.

The nice part is this code isn't specific to Perl and can be used to fix up any other C-based Unix program.


Y2038 safe

pne on 2008-02-07T16:25:47

He came up with the same basic algorithm I did: [snip]

Ah, clever. Especially step 4.

As for your patch, I think you can completely delete "if (!&tmbuf) RETPUSHUNDEF", and replace "else if (&tmbuf)" with "else", since now your tmbuf struct will always be there -- it's not a pointer returned from a function which can be NULL on error. In fact, those functions don't seem to have an error check (except for the asserts in _check_tm).

Re:Y2038 safe

schwern on 2008-02-07T23:56:17

Yes, step four solved a large number of problems and made it much more efficient and accurate. The original 2038bug code wrote it's own 64 bit clean mktime() and was running it through that several times to get offsets and the dst information was wrong, all sorts of minor inaccuracies. But #3, picking the right safe year, was what consumed the most time.

Don't look to hard at the perl patch, it was done in 5 minutes so that I'd have a working miniperl to point at. There's all sorts of configure and system probing and cross-platform work left to do. The _r() functions are not always available. timegm() isn't in the C standard. Even my assumptions about the fields of the tm struct are too broad.

Nothing to derail the whole thing, just niggling details.