Update from y2038.
Michael G. Schwern [Sat, 4 Oct 2008 22:24:54 +0000 (18:24 -0400)]
Add trace code.

Fix implied negative time in localtie64_r().  This fixes Windows.

Put in some more tests around small negative and positive times to
try and catch the above.

Explain the loss of accuracy due to use of doubles in perlport.

lib/Time/gmtime.t
lib/Time/localtime.t
pod/perlport.pod
t/op/time.t
time64.c
time64_config.h

index 1ccd7fb..9c77f81 100644 (file)
@@ -9,7 +9,7 @@ BEGIN {
 
 my(@times, @methods);
 BEGIN {
-    @times   = (-2**33, -2**31-1, 0, 2**31-1, 2**33, time);
+    @times   = (-2**62, -2**50, -2**33, -2**31-1, -1, 0, 1, 2**31-1, 2**33, 2**50, 2**62, time);
     @methods = qw(sec min hour mday mon year wday yday isdst);
 
     plan tests => (@times * @methods) + 1;
@@ -17,7 +17,6 @@ BEGIN {
     use_ok Time::gmtime;
 }
 
-# Perl has its own gmtime() so it's safe to do negative times.
 for my $time (@times) {
     my $gmtime = gmtime $time;          # This is the OO gmtime.
     my @gmtime = CORE::gmtime $time;    # This is the gmtime function
index 8600eff..f300343 100644 (file)
@@ -7,8 +7,9 @@ BEGIN {
     require "./test.pl";
 }
 
+my(@times, @methods);
 BEGIN {
-    @times   = (-2**33, -2**31-1, 0, 2**31-1, 2**33, time);
+    @times   = (-2**62, -2**50, -2**33, -2**31-1, -1, 0, 1, 2**31-1, 2**33, 2**50, 2**62, time);
     @methods = qw(sec min hour mday mon year wday yday isdst);
 
     plan tests => (@times * @methods) + 1;
@@ -16,8 +17,6 @@ BEGIN {
     use_ok Time::localtime;
 }
 
-# Since Perl's localtime() still uses the system localtime, don't try
-# to do negative times.  The system might not support it.
 for my $time (@times) {
     my $localtime = localtime $time;          # This is the OO localtime.
     my @localtime = CORE::localtime $time;    # This is the localtime function
index 8a72de2..ff1ba39 100644 (file)
@@ -1860,7 +1860,10 @@ platforms.  See L<File::Glob> for portability information.
 
 =item gmtime
 
-gmtime() has a range of about 2 billion years before and after 1970.
+In theory, gmtime() is reliable from -2**63 to 2**63-1.  However,
+because work arounds in the implementation use floating point numbers,
+it will become inaccurate as the time gets larger.  This is a bug and
+will be fixed in the future.
 
 =item ioctl FILEHANDLE,FUNCTION,SCALAR
 
index 2e61eea..00c5b05 100755 (executable)
@@ -6,7 +6,7 @@ BEGIN {
     require './test.pl';
 }
 
-plan tests => 34;
+plan tests => 42;
 
 ($beguser,$begsys) = times;
 
@@ -82,16 +82,19 @@ ok(gmtime() =~ /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat)[ ]
 # Test gmtime over a range of times.
 {
     # gm/localtime should go all the way from -2**63 to 2**63-1
+    # but floating point hacks mean it gets unreliable for large numbers.
     my %tests = (
         # time_t         gmtime list                          scalar
-        -2**35        => [52, 13, 20, 7, 2, -1019, 5, 65, 0, "Fri Mar  7 20:13:52 881"],
-        -2**32        => [44, 31, 17, 24, 10, -67, 0, 327, 0, "Sun Nov 24 17:31:44 1833"],
-        -2**31        => [52, 45, 20, 13, 11, 1, 5, 346, 0, "Fri Dec 13 20:45:52 1901"],
-        0             => [0, 0, 0, 1, 0, 70, 4, 0, 0, "Thu Jan  1 00:00:00 1970"],
-        2**30         => [4, 37, 13, 10, 0, 104, 6, 9, 0, "Sat Jan 10 13:37:04 2004"],
-        2**31         => [8, 14, 3, 19, 0, 138, 2, 18, 0, "Tue Jan 19 03:14:08 2038"],
-        2**32         => [16, 28, 6, 7, 1, 206, 0, 37, 0, "Sun Feb  7 06:28:16 2106"],
-        2**39         => [8, 18, 12, 25, 0, 17491, 2, 24, 0, "Tue Jan 25 12:18:08 19391"],
+        -2**35  => [52, 13, 20, 7, 2, -1019, 5, 65, 0, "Fri Mar  7 20:13:52 881"],
+        -2**32  => [44, 31, 17, 24, 10, -67, 0, 327, 0, "Sun Nov 24 17:31:44 1833"],
+        -2**31  => [52, 45, 20, 13, 11, 1, 5, 346, 0, "Fri Dec 13 20:45:52 1901"],
+        -1      => [59, 59, 23, 31, 11, 69, 3, 364, 0, "Wed Dec 31 23:59:59 1969"],
+        0       => [0, 0, 0, 1, 0, 70, 4, 0, 0, "Thu Jan  1 00:00:00 1970"],
+        1       => [1, 0, 0, 1, 0, 70, 4, 0, 0, "Thu Jan  1 00:00:01 1970"],
+        2**30   => [4, 37, 13, 10, 0, 104, 6, 9, 0, "Sat Jan 10 13:37:04 2004"],
+        2**31   => [8, 14, 3, 19, 0, 138, 2, 18, 0, "Tue Jan 19 03:14:08 2038"],
+        2**32   => [16, 28, 6, 7, 1, 206, 0, 37, 0, "Sun Feb  7 06:28:16 2106"],
+        2**39   => [8, 18, 12, 25, 0, 17491, 2, 24, 0, "Tue Jan 25 12:18:08 19391"],
     );
 
     for my $time (keys %tests) {
@@ -110,9 +113,11 @@ ok(gmtime() =~ /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat)[ ]
     # the same regardless of the time zone.
     my %tests = (
         # time_t           month, year,  scalar
-        -8589934592     => [9,    -203,  qr/Oct \d+ .* 1697$/],
-        5000000000      => [5,    228,   qr/Jun \d+ .* 2128$/],
-        1163500000      => [10,   106,   qr/Nov \d+ .* 2006$/],
+        -8589934592     => [9,    -203,                 qr/Oct \d+ .* 1697$/],
+        -1296000        => [11,   69,                   qr/Dec \d+ .* 1969$/],
+        1296000         => [0,    70,                   qr/Jan \d+ .* 1970$/],
+        5000000000      => [5,    228,                  qr/Jun \d+ .* 2128$/],
+        1163500000      => [10,   106,                  qr/Nov \d+ .* 2006$/],
     );
 
     for my $time (keys %tests) {
index 8d5820d..21fe116 100644 (file)
--- a/time64.c
+++ b/time64.c
@@ -108,11 +108,18 @@ static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
 #    define SHOULD_USE_SYSTEM_GMTIME(a)         (0)
 #endif
 
+#ifdef TIME_64_DEBUG
+#    define TRACE(format, ...)    (fprintf(stderr, format, __VA_ARGS__))
+#    define TRACE_NO_VARS(format) (fprintf(stderr, format))
+#else
+#    define TRACE(format, ...)    ((void)0)
+#    define TRACE_NO_VARS(format) ((void)0)
+#endif
 
 static int is_exception_century(Year year)
 {
     int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
-    /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
+    TRACE("# is_exception_century: %s\n", is_exception ? "yes" : "no");
 
     return(is_exception);
 }
@@ -201,10 +208,8 @@ static Year cycle_offset(Year year)
     exceptions  = year_diff / 100;
     exceptions -= year_diff / 400;
 
-    /*
-    fprintf(stderr, "# year: %lld, exceptions: %lld, year_diff: %lld\n",
-            year, exceptions, year_diff);
-    */
+    TRACE("# year: %lld, exceptions: %lld, year_diff: %lld\n",
+          year, exceptions, year_diff);
 
     return exceptions * 16;
 }
@@ -249,10 +254,8 @@ static int safe_year(Year year)
 
     assert(safe_year <= 2037 && safe_year >= 2010);
 
-    /*
-    printf("year: %d, year_cycle: %d, safe_year: %d\n",
-           year, year_cycle, safe_year);
-    */
+    TRACE("# year: %lld, year_cycle: %lld, safe_year: %d\n",
+          year, year_cycle, safe_year);
 
     return safe_year;
 }
@@ -413,7 +416,7 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
 
     if (m >= 0) {
         /* Gregorian cycles, this is huge optimization for distant times */
-        cycles = m / (Time64_T) days_in_gregorian_cycle;
+        cycles = (int)(m / (Time64_T) days_in_gregorian_cycle);
         if( cycles ) {
             m -= (cycles * (Time64_T) days_in_gregorian_cycle);
             year += (cycles * years_in_gregorian_cycle);
@@ -437,7 +440,7 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
         year--;
 
         /* Gregorian cycles */
-        cycles = (m / (Time64_T) days_in_gregorian_cycle) + 1;
+        cycles = (int)((m / (Time64_T) days_in_gregorian_cycle) + 1);
         if( cycles ) {
             m -= (cycles * (Time64_T) days_in_gregorian_cycle);
             year += (cycles * years_in_gregorian_cycle);
@@ -497,6 +500,8 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
     if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
         safe_time = *time;
 
+        TRACE("Using system localtime for %lld\n", *time);
+
         LOCALTIME_R(&safe_time, &safe_date);
 
         copy_tm_to_TM(&safe_date, local_tm);
@@ -505,26 +510,34 @@ struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
         return local_tm;
     }
 
-    if( gmtime64_r(time, &gm_tm) == NULL )
+    if( gmtime64_r(time, &gm_tm) == NULL ) {
+        TRACE("gmtime64_r returned null for %lld\n", *time);
         return NULL;
+    }
 
     orig_year = gm_tm.tm_year;
 
     if (gm_tm.tm_year > (2037 - 1900) ||
-        gm_tm.tm_year < (1902 - 1900)
+        gm_tm.tm_year < (1970 - 1900)
        )
     {
+        TRACE("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year);
         gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year + 1900)) - 1900;
     }
 
     safe_time = timegm64(&gm_tm);
-    if( LOCALTIME_R(&safe_time, &safe_date) == NULL )
+    if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) {
+        TRACE("localtime_r(%d) returned NULL\n", (int)safe_time);
         return NULL;
+    }
 
     copy_tm_to_TM(&safe_date, local_tm);
 
     local_tm->tm_year = orig_year;
     if( local_tm->tm_year != orig_year ) {
+        TRACE("tm_year overflow: tm_year %lld, orig_year %lld\n",
+              (Year)local_tm->tm_year, (Year)orig_year);
+
 #ifdef EOVERFLOW
         errno = EOVERFLOW;
 #endif
index c22a115..6b54534 100644 (file)
@@ -7,6 +7,13 @@
    Sensible defaults provided.
 */
 
+/* Debugging
+   TIME_64_DEBUG
+   Define if you want debugging messages
+*/
+/* #define TIME_64_DEBUG */
+
+
 /* INT_64_T
    A 64 bit integer type to use to store time and others.
    Must be defined.