Update to the latest version of the y2038 code.
Michael G Schwern [Sat, 13 Sep 2008 10:01:53 +0000 (03:01 -0700)]
In this update...

* Replace unportable "long" with Quad_t
* Improve performance for current and future dates.
* Provide a version of timegm() if its not available.
* Wrap the use of EOVERFLOW in case its not portable.

This should address all known portabilty issues.

localtime64.c
localtime64.h

index 92372ad..80c0707 100644 (file)
@@ -83,11 +83,18 @@ static const int dow_year_start[28] = {
     0, 2, 3, 4      /* 2012, 2013, 2014, 2015 */
 };
 
+/* Let's assume people are going to be looking for dates in the future.
+   Let's provide some cheats so you can skip ahead.
+   This has a 4x speed boost when near 2008.
+*/
+/* Number of days since epoch on Jan 1st, 2008 GMT */
+#define CHEAT_DAYS  (1199145600 / 24 / 60 / 60)
+#define CHEAT_YEARS 108
 
 #define IS_LEAP(n)     ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
 #define WRAP(a,b,m)    ((a) = ((a) <  0  ) ? ((b)--, (a) + (m)) : (a))
 
-int _is_exception_century(long year)
+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"); */
@@ -95,41 +102,88 @@ int _is_exception_century(long year)
     return(is_exception);
 }
 
-void _check_tm(struct tm *tm)
-{
-    /* Don't forget leap seconds */
-    assert(tm->tm_sec  >= 0 && tm->tm_sec <= 61);
-    assert(tm->tm_min  >= 0 && tm->tm_min <= 59);
-    assert(tm->tm_hour >= 0 && tm->tm_hour <= 23);
-    assert(tm->tm_mday >= 1 && tm->tm_mday <= 31);
-    assert(tm->tm_mon  >= 0 && tm->tm_mon  <= 11);
-    assert(tm->tm_wday >= 0 && tm->tm_wday <= 6);
-    assert(tm->tm_yday >= 0 && tm->tm_yday <= 365);
-
-#ifdef TM_HAS_GMTOFF
-    assert(   tm->tm_gmtoff >= -24 * 60 * 60
-           && tm->tm_gmtoff <=  24 * 60 * 60);
+
+/* 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) _my_timegm(n);
 #endif
 
-    if( !IS_LEAP(tm->tm_year) ) {
-        /* no more than 365 days in a non_leap year */
-        assert( tm->tm_yday <= 364 );
+time_t _my_timegm(struct tm *date) {
+    int days    = 0;
+    int seconds = 0;
+    time_t time;
+    int year;
 
-        /* and no more than 28 days in Feb */
-        if( tm->tm_mon == 1 ) {
-            assert( tm->tm_mday <= 28 );
+    if( date->tm_year > 70 ) {
+        year = 70;
+        while( year < date->tm_year ) {
+            days += length_of_year[IS_LEAP(year)];
+            year++;
         }
     }
+    else if ( date->tm_year < 70 ) {
+        year = 69;
+        do {
+            days -= length_of_year[IS_LEAP(year)];
+            year--;
+        } while( year >= date->tm_year );
+    }
+
+    days += julian_days_by_month[IS_LEAP(date->tm_year)][date->tm_mon];
+    days += date->tm_mday - 1;
+
+    seconds += date->tm_hour * 60 * 60;
+    seconds += date->tm_min * 60;
+    seconds += date->tm_sec;
+
+    time = (time_t)(days * 60 * 60 * 24) + seconds;
+
+    return(time);
+}
+
+
+void _check_tm(struct tm *tm)
+{
+    int is_leap = IS_LEAP(tm->tm_year);
+
+    /* Don't forget leap seconds */
+    assert(tm->tm_sec  >= 0);
+    assert(tm->tm_sec <= 61);
+
+    assert(tm->tm_min  >= 0);
+    assert(tm->tm_min <= 59);
+
+    assert(tm->tm_hour >= 0);
+    assert(tm->tm_hour <= 23);
+
+    assert(tm->tm_mday >= 1);
+    assert(tm->tm_mday <= days_in_month[is_leap][tm->tm_mon]);
+
+    assert(tm->tm_mon  >= 0);
+    assert(tm->tm_mon  <= 11);
+
+    assert(tm->tm_wday >= 0);
+    assert(tm->tm_wday <= 6);
+
+    assert(tm->tm_yday >= 0);
+    assert(tm->tm_yday <= length_of_year[is_leap]);
+
+#ifdef HAS_TM_TM_GMTOFF
+    assert(tm->tm_gmtoff >= -24 * 60 * 60);
+    assert(tm->tm_gmtoff <=  24 * 60 * 60);
+#endif
 }
 
 /* The exceptional centuries without leap years cause the cycle to
    shift by 16
 */
-int _cycle_offset(long year)
+int _cycle_offset(Int64 year)
 {
-    const long start_year = 2000;
-    long year_diff  = year - start_year - 1;
-    long exceptions  = year_diff / 100;
+    const Int64 start_year = 2000;
+    Int64 year_diff  = year - start_year - 1;
+    Int64 exceptions  = year_diff / 100;
     exceptions     -= year_diff / 400;
 
     assert( year >= 2001 );
@@ -143,10 +197,10 @@ int _cycle_offset(long year)
    year in the 28 year calendar cycle.
 */
 #define SOLAR_CYCLE_LENGTH 28
-int _safe_year(long year)
+int _safe_year(Int64 year)
 {
     int safe_year;
-    long year_cycle = year + _cycle_offset(year);
+    Int64 year_cycle = year + _cycle_offset(year);
 
     /* Change non-leap xx00 years to an equivalent */
     if( _is_exception_century(year) )
@@ -169,18 +223,18 @@ int _safe_year(long year)
 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;
-    Time64_T v_tm_tday;
+    Int64 v_tm_tday;
     int leap;
-    Time64_T m;
+    Int64 m;
     Time64_T time = *in_time;
-    Time64_T year;
+    Int64 year = 70;
 
-#ifdef TM_HAS_GMTOFF
+#ifdef HAS_TM_TM_GMTOFF
     p->tm_gmtoff = 0;
 #endif
     p->tm_isdst  = 0;
 
-#ifdef TM_HAS_ZONE
+#ifdef HAS_TM_TM_ZONE
     p->tm_zone   = "UTC";
 #endif
 
@@ -197,9 +251,13 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
     if ((v_tm_wday = (v_tm_tday + 4) % 7) < 0)
         v_tm_wday += 7;
     m = v_tm_tday;
-    if (m >= 0) {
-        year = 70;
 
+    if (m >= CHEAT_DAYS) {
+        year = CHEAT_YEARS;
+        m -= CHEAT_DAYS;
+    }
+
+    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;
@@ -221,7 +279,7 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
             v_tm_mon++;
         }
     } else {
-        year = 69;
+        year--;
 
         /* Gregorian cycles */
         while (m < (Time64_T) -days_in_gregorian_cycle) {
@@ -248,7 +306,9 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p)
 
     p->tm_year = year;
     if( p->tm_year != year ) {
+#ifdef EOVERFLOW
         errno = EOVERFLOW;
+#endif
         return NULL;
     }
 
@@ -267,7 +327,7 @@ struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
 {
     time_t safe_time;
     struct tm gm_tm;
-    long orig_year;
+    Int64 orig_year;
     int month_diff;
 
     gmtime64_r(time, &gm_tm);
@@ -276,7 +336,7 @@ struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm)
     if (gm_tm.tm_year > (2037 - 1900))
         gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900;
 
-    safe_time = timegm(&gm_tm);
+    safe_time = TIMEGM(&gm_tm);
     localtime_r(&safe_time, local_tm);
 
     local_tm->tm_year = orig_year;
index 5ffeaa1..b6c65fc 100644 (file)
@@ -3,9 +3,25 @@
 #ifndef LOCALTIME64_H
 #    define LOCALTIME64_H
 
-typedef Quad_t Time64_T;
+/* Configuration. */
+/* Define as appropriate for your system */
+/*
+   HAS_TIMEGM
+   Defined if your system has timegm()
 
-struct tm *gmtime64_r    (const Time64_T *in_time, struct tm *p);
-struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm);
+   HAS_TM_TM_GMTOFF
+   Defined if your tm struct has a "tm_gmtoff" element.
+
+   HAS_TM_TM_ZONE
+   Defined if your tm struct has a "tm_zone" element.
+*/
+
+
+/* 64 bit types.  Set as appropriate for your system. */
+typedef Quad_t               Time64_T;
+typedef Quad_t               Int64;
+
+struct tm *gmtime64_r    (const Time64_T *, struct tm *);
+struct tm *localtime64_r (const Time64_T *, struct tm *);
 
 #endif