From: Michael G Schwern Date: Sat, 13 Sep 2008 10:01:53 +0000 (-0700) Subject: Update to the latest version of the y2038 code. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=9af245219bab70f094b76d8f420ddd3f7f211a2f;p=p5sagit%2Fp5-mst-13.2.git Update to the latest version of the y2038 code. 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. --- diff --git a/localtime64.c b/localtime64.c index 92372ad..80c0707 100644 --- a/localtime64.c +++ b/localtime64.c @@ -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; diff --git a/localtime64.h b/localtime64.h index 5ffeaa1..b6c65fc 100644 --- a/localtime64.h +++ b/localtime64.h @@ -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