Remove the AIX work around code. Instead it should just set it's LOCALTIME_MAX to...
Michael G. Schwern [Thu, 2 Oct 2008 20:18:54 +0000 (16:18 -0400)]
Update from y2038.

Use the new TM64 struct so years can go out past y2**31

Defines a Year type to avoid converting years to ints.

Remove the TIMGM work around code, using timegm64() is fine and it
saves us from having to convert from TM to tm.

Make functions private with static rather than the _foo convention.

Even faster for distant dates.

pp_sys.c
time64.c
time64.h

index 788aa7b..74958ac 100644 (file)
--- a/pp_sys.c
+++ b/pp_sys.c
@@ -201,15 +201,6 @@ void endservent(void);
 
 #undef PERL_EFF_ACCESS /* EFFective uid/gid ACCESS */
 
-/* AIX 5.2 and below use mktime for localtime, and defines the edge case
- * for time 0x7fffffff to be valid only in UTC. AIX 5.3 provides localtime64
- * available in the 32bit environment, which could warrant Configure
- * checks in the future.
- */
-#ifdef  _AIX
-#define LOCALTIME_EDGECASE_BROKEN
-#endif
-
 /* F_OK unused: if stat() cannot find it... */
 
 #if !defined(PERL_EFF_ACCESS) && defined(HAS_ACCESS) && defined(EFF_ONLY_OK) && !defined(NO_EFF_ONLY_OK)
@@ -4404,53 +4395,13 @@ PP(pp_tms)
 #endif /* HAS_TIMES */
 }
 
-#ifdef LOCALTIME_EDGECASE_BROKEN
-static struct tm *S_my_localtime (pTHX_ Time_t *tp)
-{
-    auto time_t     T;
-    auto struct tm *P;
-
-    /* No workarounds in the valid range */
-    if (!tp || *tp < 0x7fff573f || *tp >= 0x80000000)
-       return (localtime (tp));
-
-    /* This edge case is to workaround the undefined behaviour, where the
-     * TIMEZONE makes the time go beyond the defined range.
-     * gmtime (0x7fffffff) => 2038-01-19 03:14:07
-     * If there is a negative offset in TZ, like MET-1METDST, some broken
-     * implementations of localtime () (like AIX 5.2) barf with bogus
-     * return values:
-     * 0x7fffffff gmtime               2038-01-19 03:14:07
-     * 0x7fffffff localtime            1901-12-13 21:45:51
-     * 0x7fffffff mylocaltime          2038-01-19 04:14:07
-     * 0x3c19137f gmtime               2001-12-13 20:45:51
-     * 0x3c19137f localtime            2001-12-13 21:45:51
-     * 0x3c19137f mylocaltime          2001-12-13 21:45:51
-     * Given that legal timezones are typically between GMT-12 and GMT+12
-     * we turn back the clock 23 hours before calling the localtime
-     * function, and add those to the return value. This will never cause
-     * day wrapping problems, since the edge case is Tue Jan *19*
-     */
-    T = *tp - 82800; /* 23 hour. allows up to GMT-23 */
-    P = localtime (&T);
-    P->tm_hour += 23;
-    if (P->tm_hour >= 24) {
-       P->tm_hour -= 24;
-       P->tm_mday++;   /* 18  -> 19  */
-       P->tm_wday++;   /* Mon -> Tue */
-       P->tm_yday++;   /* 18  -> 19  */
-    }
-    return (P);
-} /* S_my_localtime */
-#endif
-
 PP(pp_gmtime)
 {
     dVAR;
     dSP;
     Time64_T when;
-    struct tm tmbuf;
-    struct tm *err;
+    struct TM tmbuf;
+    struct TM *err;
     static const char * const dayname[] =
        {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
     static const char * const monname[] =
@@ -4483,7 +4434,7 @@ PP(pp_gmtime)
        if (err == NULL)
            RETPUSHUNDEF;
 
-       tsv = Perl_newSVpvf(aTHX_ "%s %s %2d %02d:%02d:%02d %d",
+       tsv = Perl_newSVpvf(aTHX_ "%s %s %2d %02d:%02d:%02d %lld",
                            dayname[tmbuf.tm_wday],
                            monname[tmbuf.tm_mon],
                            tmbuf.tm_mday,
index cb74b55..2cb6ad2 100644 (file)
--- a/time64.c
+++ b/time64.c
@@ -54,11 +54,12 @@ static const int julian_days_by_month[2][12] = {
 static const int length_of_year[2] = { 365, 366 };
 
 /* Number of days in a 400 year Gregorian cycle */
-static const int years_in_gregorian_cycle = 400;
+static const Year years_in_gregorian_cycle = 400;
 static const int days_in_gregorian_cycle  = (365 * 400) + 100 - 4 + 1;
 
 /* 28 year calendar cycle between 2010 and 2037 */
-static const int safe_years[28] = {
+#define SOLAR_CYCLE_LENGTH 28
+static const int safe_years[SOLAR_CYCLE_LENGTH] = {
     2016, 2017, 2018, 2019,
     2020, 2021, 2022, 2023,
     2024, 2025, 2026, 2027,
@@ -68,7 +69,6 @@ static const int safe_years[28] = {
     2012, 2013, 2014, 2015
 };
 
-#define SOLAR_CYCLE_LENGTH 28
 static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
     5, 0, 1, 2,     /* 0       2016 - 2019 */
     3, 5, 6, 0,     /* 4  */
@@ -102,7 +102,7 @@ static const int dow_year_start[SOLAR_CYCLE_LENGTH] = {
 )
 
 
-int _is_exception_century(Int64 year)
+static int is_exception_century(Int64 year)
 {
     int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
     /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */
@@ -111,14 +111,7 @@ int _is_exception_century(Int64 year)
 }
 
 
-/* timegm() is a GNU extension, so emulate it here if we need it */
-#ifdef HAS_TIMEGM
-#    define TIMEGM(n) timegm(n);
-#else
-#    define TIMEGM(n) ((time_t)timegm64(n));
-#endif
-
-Time64_T timegm64(struct tm *date) {
+Time64_T timegm64(struct TM *date) {
     int   days    = 0;
     Int64 seconds = 0;
     Int64 year;
@@ -153,7 +146,7 @@ Time64_T timegm64(struct tm *date) {
 }
 
 
-int _check_tm(struct tm *tm)
+static int check_tm(struct TM *tm)
 {
     /* Don't forget leap seconds */
     assert(tm->tm_sec >= 0);
@@ -189,7 +182,7 @@ int _check_tm(struct tm *tm)
 /* The exceptional centuries without leap years cause the cycle to
    shift by 16
 */
-Year _cycle_offset(Year year)
+static Year cycle_offset(Year year)
 {
     const Year start_year = 2000;
     Year year_diff  = year - start_year;
@@ -226,17 +219,17 @@ Year _cycle_offset(Year year)
    It doesn't need the same leap year status since we only care about
    January 1st.
 */
-int _safe_year(Year year)
+static int safe_year(Year year)
 {
     int safe_year;
-    Year year_cycle = year + _cycle_offset(year);
+    Year year_cycle = year + cycle_offset(year);
 
     /* Change non-leap xx00 years to an equivalent */
-    if( _is_exception_century(year) )
+    if( is_exception_century(year) )
         year_cycle += 11;
 
     /* Also xx01 years, since the previous year will be wrong */
-    if( _is_exception_century(year - 1) )
+    if( is_exception_century(year - 1) )
         year_cycle += 17;
 
     year_cycle %= SOLAR_CYCLE_LENGTH;
@@ -258,6 +251,70 @@ int _safe_year(Year year)
 }
 
 
+void copy_tm_to_TM(const struct tm *src, struct TM *dest) {
+    if( src == NULL ) {
+        memset(dest, 0, sizeof(*dest));
+    }
+    else {
+#       ifdef USE_TM64
+            dest->tm_sec        = src->tm_sec;
+            dest->tm_min        = src->tm_min;
+            dest->tm_hour       = src->tm_hour;
+            dest->tm_mday       = src->tm_mday;
+            dest->tm_mon        = src->tm_mon;
+            dest->tm_year       = (Year)src->tm_year;
+            dest->tm_wday       = src->tm_wday;
+            dest->tm_yday       = src->tm_yday;
+            dest->tm_isdst      = src->tm_isdst;
+
+#           ifdef HAS_TM_TM_GMTOFF
+                dest->tm_gmtoff  = src->tm_gmtoff;
+#           endif
+
+#           ifdef HAS_TM_TM_ZONE
+                dest->tm_zone  = src->tm_zone;
+#           endif
+
+#       else
+            /* They're the same type */
+            memcpy(dest, src, sizeof(*dest));
+#       endif
+    }
+}
+
+
+void copy_TM_to_tm(const struct TM *src, struct tm *dest) {
+    if( src == NULL ) {
+        memset(dest, 0, sizeof(*dest));
+    }
+    else {
+#       ifdef USE_TM64
+            dest->tm_sec        = src->tm_sec;
+            dest->tm_min        = src->tm_min;
+            dest->tm_hour       = src->tm_hour;
+            dest->tm_mday       = src->tm_mday;
+            dest->tm_mon        = src->tm_mon;
+            dest->tm_year       = (int)src->tm_year;
+            dest->tm_wday       = src->tm_wday;
+            dest->tm_yday       = src->tm_yday;
+            dest->tm_isdst      = src->tm_isdst;
+
+#           ifdef HAS_TM_TM_GMTOFF
+                dest->tm_gmtoff  = src->tm_gmtoff;
+#           endif
+
+#           ifdef HAS_TM_TM_ZONE
+                dest->tm_zone  = src->tm_zone;
+#           endif
+
+#       else
+            /* They're the same type */
+            memcpy(dest, src, sizeof(*dest));
+#       endif
+    }
+}
+
+
 /* Simulate localtime_r() to the best of our ability */
 struct tm * fake_localtime_r(const time_t *clock, struct tm *result) {
     const struct tm *static_result = localtime(clock);
@@ -292,7 +349,7 @@ struct tm * fake_gmtime_r(const time_t *clock, struct tm *result) {
 }
 
 
-struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
+struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
 {
     int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
     Int64 v_tm_tday;
@@ -300,14 +357,19 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
     Int64 m;
     Time64_T time = *in_time;
     Year year = 70;
+    int cycles = 0;
 
     assert(p != NULL);
 
     /* Use the system gmtime() if time_t is small enough */
     if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
         time_t safe_time = *in_time;
-        GMTIME_R(&safe_time, p);
-        assert(_check_tm(p));
+        struct tm safe_date;
+        GMTIME_R(&safe_time, &safe_date);
+
+        copy_tm_to_TM(&safe_date, p);
+        assert(check_tm(p));
+
         return p;
     }
 
@@ -344,9 +406,10 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
 
     if (m >= 0) {
         /* Gregorian cycles, this is huge optimization for distant times */
-        while (m >= (Time64_T) days_in_gregorian_cycle) {
-            m -= (Time64_T) days_in_gregorian_cycle;
-            year += years_in_gregorian_cycle;
+        cycles = floor(m / (Time64_T) days_in_gregorian_cycle);
+        if( cycles ) {
+            m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+            year += (cycles * years_in_gregorian_cycle);
         }
 
         /* Years */
@@ -367,9 +430,10 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
         year--;
 
         /* Gregorian cycles */
-        while (m < (Time64_T) -days_in_gregorian_cycle) {
-            m += (Time64_T) days_in_gregorian_cycle;
-            year -= years_in_gregorian_cycle;
+        cycles = ceil(m / (Time64_T) days_in_gregorian_cycle) + 1;
+        if( cycles ) {
+            m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+            year += (cycles * years_in_gregorian_cycle);
         }
 
         /* Years */
@@ -402,16 +466,17 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
     p->tm_sec = v_tm_sec, p->tm_min = v_tm_min, p->tm_hour = v_tm_hour,
         p->tm_mon = v_tm_mon, p->tm_wday = v_tm_wday;
 
-    assert(_check_tm(p));
+    assert(check_tm(p));
 
     return p;
 }
 
 
-struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
+struct TM *localtime64_r (const Time64_T *time, struct TM *local_tm)
 {
     time_t safe_time;
-    struct tm gm_tm;
+    struct tm safe_date;
+    struct TM gm_tm;
     Year orig_year;
     int month_diff;
 
@@ -420,8 +485,12 @@ struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
     /* Use the system localtime() if time_t is small enough */
     if( SHOULD_USE_SYSTEM_LOCALTIME(*time) ) {
         safe_time = *time;
-        LOCALTIME_R(&safe_time, local_tm);
-        assert(_check_tm(local_tm));
+
+        LOCALTIME_R(&safe_time, &safe_date);
+
+        copy_tm_to_TM(&safe_date, local_tm);
+        assert(check_tm(local_tm));
+
         return local_tm;
     }
 
@@ -434,13 +503,15 @@ struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
         gm_tm.tm_year < (1902 - 1900)
        )
     {
-        gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
+        gm_tm.tm_year = safe_year(gm_tm.tm_year + 1900) - 1900;
     }
 
-    safe_time = TIMEGM(&gm_tm);
-    if( LOCALTIME_R(&safe_time, local_tm) == NULL )
+    safe_time = timegm64(&gm_tm);
+    if( LOCALTIME_R(&safe_time, &safe_date) == NULL )
         return NULL;
 
+    copy_tm_to_TM(&safe_date, local_tm);
+
     local_tm->tm_year = orig_year;
     if( local_tm->tm_year != orig_year ) {
 #ifdef EOVERFLOW
@@ -475,7 +546,7 @@ struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
     if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
         local_tm->tm_yday--;
 
-    assert(_check_tm(local_tm));
+    assert(check_tm(local_tm));
 
     return local_tm;
 }
index db8ddbf..85e2ff5 100644 (file)
--- a/time64.h
+++ b/time64.h
@@ -1,3 +1,5 @@
+#include <time.h>
+
 #ifndef LOCALTIME64_H
 #    define LOCALTIME64_H
 
    USE_SYSTEM_LOCALTIME
    USE_SYSTEM_GMTIME
    Should we use the system functions if the time is inside their range?
+
+   USE_TM64
+   Should we use a 64 bit safe tm struct which can handle a
+   year range greater than 2 billion?
 */
+
 #define SYSTEM_LOCALTIME_MAX    LOCALTIME_MAX
 #define SYSTEM_LOCALTIME_MIN    LOCALTIME_MIN
 #define SYSTEM_GMTIME_MAX       GMTIME_MAX
 #define USE_SYSTEM_LOCALTIME    1
 #define USE_SYSTEM_GMTIME       1
 
+/* Let's get all the time */
+#define USE_TM64
+
+#ifdef USE_TM64
+#define TM      TM64
+#else
+#define TM      tm
+#endif
 
 /* 64 bit types.  Set as appropriate for your system. */
 typedef Quad_t               Time64_T;
 typedef Quad_t               Int64;
 typedef Int64                Year;
 
-struct tm *gmtime64_r    (const Time64_T *, struct tm *);
-struct tm *localtime64_r (const Time64_T *, struct tm *);
-Time64_T   timegm64      (struct tm *);
+struct TM *gmtime64_r    (const Time64_T *, struct TM *);
+struct TM *localtime64_r (const Time64_T *, struct TM *);
+Time64_T   timegm64      (struct TM *);
+
+/* A copy of the tm struct but with a 64 bit year */
+struct TM64 {
+        int     tm_sec;
+        int     tm_min;
+        int     tm_hour;
+        int     tm_mday;
+        int     tm_mon;
+        Year    tm_year;
+        int     tm_wday;
+        int     tm_yday;
+        int     tm_isdst;
+
+#ifdef HAS_TM_TM_GMTOFF
+        long    tm_gmtoff;
+#endif
+
+#ifdef HAS_TM_TM_ZONE
+        char    *tm_zone;
+#endif
+};
 
 
 /* Not everyone has gm/localtime_r() */