X-Git-Url: https://perl5.git.perl.org/perl5.git/blobdiff_plain/7643e68fdb02e6f17d9f2a5801be920285971156..e0b44199a03c8f4b454c18ecbb337f4facfcdc8c:/time64.c diff --git a/time64.c b/time64.c index cb74b55..9faab10 100644 --- a/time64.c +++ b/time64.c @@ -33,9 +33,9 @@ long' type can use localtime64_r() and gmtime64_r() which correctly converts the time even on 32-bit systems. Whether you have 64-bit time values will depend on the operating system. -localtime64_r() is a 64-bit equivalent of localtime_r(). +S_localtime64_r() is a 64-bit equivalent of localtime_r(). -gmtime64_r() is a 64-bit equivalent of gmtime_r(). +S_gmtime64_r() is a 64-bit equivalent of gmtime_r(). */ @@ -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 */ @@ -90,38 +90,50 @@ static const int dow_year_start[SOLAR_CYCLE_LENGTH] = { #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)) -#define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \ - USE_SYSTEM_LOCALTIME && \ +#ifdef USE_SYSTEM_LOCALTIME +# define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \ (a) <= SYSTEM_LOCALTIME_MAX && \ (a) >= SYSTEM_LOCALTIME_MIN \ ) -#define SHOULD_USE_SYSTEM_GMTIME(a) ( \ - USE_SYSTEM_GMTIME && \ +#else +# define SHOULD_USE_SYSTEM_LOCALTIME(a) (0) +#endif + +#ifdef USE_SYSTEM_GMTIME +# define SHOULD_USE_SYSTEM_GMTIME(a) ( \ (a) <= SYSTEM_GMTIME_MAX && \ (a) >= SYSTEM_GMTIME_MIN \ ) +#else +# define SHOULD_USE_SYSTEM_GMTIME(a) (0) +#endif +/* Multi varadic macros are a C99 thing, alas */ +#ifdef TIME_64_DEBUG +# define TIME64_TRACE(format) (fprintf(stderr, format)) +# define TIME64_TRACE1(format, var1) (fprintf(stderr, format, var1)) +# define TIME64_TRACE2(format, var1, var2) (fprintf(stderr, format, var1, var2)) +# define TIME64_TRACE3(format, var1, var2, var3) (fprintf(stderr, format, var1, var2, var3)) +#else +# define TIME64_TRACE(format) ((void)0) +# define TIME64_TRACE1(format, var1) ((void)0) +# define TIME64_TRACE2(format, var1, var2) ((void)0) +# define TIME64_TRACE3(format, var1, var2, var3) ((void)0) +#endif -int _is_exception_century(Int64 year) +static int S_is_exception_century(Year year) { int is_exception = ((year % 100 == 0) && !(year % 400 == 0)); - /* printf("is_exception_century: %s\n", is_exception ? "yes" : "no"); */ + TIME64_TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no"); return(is_exception); } -/* 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) { - int days = 0; - Int64 seconds = 0; - Int64 year; +static Time64_T S_timegm64(struct TM *date) { + int days = 0; + Time64_T seconds = 0; + Year year; if( date->tm_year > 70 ) { year = 70; @@ -149,11 +161,12 @@ Time64_T timegm64(struct tm *date) { seconds += date->tm_min * 60; seconds += date->tm_sec; - return((Time64_T)seconds); + return(seconds); } -int _check_tm(struct tm *tm) +#ifdef DEBUGGING +static int S_check_tm(struct TM *tm) { /* Don't forget leap seconds */ assert(tm->tm_sec >= 0); @@ -184,12 +197,13 @@ int _check_tm(struct tm *tm) return 1; } +#endif /* The exceptional centuries without leap years cause the cycle to shift by 16 */ -Year _cycle_offset(Year year) +static Year S_cycle_offset(Year year) { const Year start_year = 2000; Year year_diff = year - start_year; @@ -201,10 +215,8 @@ 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); - */ + TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n", + year, exceptions, year_diff); return exceptions * 16; } @@ -226,17 +238,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 S_safe_year(Year year) { int safe_year; - Year year_cycle = year + _cycle_offset(year); + Year year_cycle = year + S_cycle_offset(year); /* Change non-leap xx00 years to an equivalent */ - if( _is_exception_century(year) ) + if( S_is_exception_century(year) ) year_cycle += 11; /* Also xx01 years, since the previous year will be wrong */ - if( _is_exception_century(year - 1) ) + if( S_is_exception_century(year - 1) ) year_cycle += 17; year_cycle %= SOLAR_CYCLE_LENGTH; @@ -249,17 +261,49 @@ 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); - */ + TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n", + year, year_cycle, safe_year); return safe_year; } +static void S_copy_little_tm_to_big_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 + } +} + + +#ifndef HAS_LOCALTIME_R /* Simulate localtime_r() to the best of our ability */ -struct tm * fake_localtime_r(const time_t *clock, struct tm *result) { +static struct tm * S_localtime_r(const time_t *clock, struct tm *result) { + dTHX; /* in case the following is defined as Perl_my_localtime(aTHX_ ...) */ const struct tm *static_result = localtime(clock); assert(result != NULL); @@ -273,10 +317,12 @@ struct tm * fake_localtime_r(const time_t *clock, struct tm *result) { return result; } } +#endif - +#ifndef HAS_GMTIME_R /* Simulate gmtime_r() to the best of our ability */ -struct tm * fake_gmtime_r(const time_t *clock, struct tm *result) { +static struct tm * S_gmtime_r(const time_t *clock, struct tm *result) { + dTHX; /* in case the following is defined as Perl_my_gmtime(aTHX_ ...) */ const struct tm *static_result = gmtime(clock); assert(result != NULL); @@ -290,24 +336,29 @@ struct tm * fake_gmtime_r(const time_t *clock, struct tm *result) { return result; } } +#endif - -struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p) +static struct TM *S_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; + Time64_T v_tm_tday; int leap; - Int64 m; + Time64_T 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)); + time_t safe_time = (time_t)*in_time; + struct tm safe_date; + GMTIME_R(&safe_time, &safe_date); + + S_copy_little_tm_to_big_TM(&safe_date, p); + assert(S_check_tm(p)); + return p; } @@ -317,22 +368,22 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p) p->tm_isdst = 0; #ifdef HAS_TM_TM_ZONE - p->tm_zone = "UTC"; + p->tm_zone = (char *)"UTC"; #endif - v_tm_sec = (int)(time % 60); - time /= 60; - v_tm_min = (int)(time % 60); - time /= 60; - v_tm_hour = (int)(time % 24); - time /= 24; + v_tm_sec = (int)fmod(time, 60.0); + time = time >= 0 ? floor(time / 60.0) : ceil(time / 60.0); + v_tm_min = (int)fmod(time, 60.0); + time = time >= 0 ? floor(time / 60.0) : ceil(time / 60.0); + v_tm_hour = (int)fmod(time, 24.0); + time = time >= 0 ? floor(time / 24.0) : ceil(time / 24.0); v_tm_tday = time; WRAP (v_tm_sec, v_tm_min, 60); WRAP (v_tm_min, v_tm_hour, 60); WRAP (v_tm_hour, v_tm_tday, 24); - v_tm_wday = (int)((v_tm_tday + 4) % 7); + v_tm_wday = (int)fmod((v_tm_tday + 4.0), 7.0); if (v_tm_wday < 0) v_tm_wday += 7; m = v_tm_tday; @@ -344,9 +395,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 = (int)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 +419,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 = (int)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 */ @@ -397,21 +450,26 @@ struct tm *gmtime64_r (const Time64_T *in_time, struct tm *p) return NULL; } + /* At this point m is less than a year so casting to an int is safe */ p->tm_mday = (int) m + 1; - p->tm_yday = (int) julian_days_by_month[leap][v_tm_mon] + m; - 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; + p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m; + 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(S_check_tm(p)); return p; } -struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm) +static struct TM *S_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; @@ -419,30 +477,46 @@ 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)); + safe_time = (time_t)*time; + + TIME64_TRACE1("Using system localtime for %lld\n", *time); + + LOCALTIME_R(&safe_time, &safe_date); + + S_copy_little_tm_to_big_TM(&safe_date, local_tm); + assert(S_check_tm(local_tm)); + return local_tm; } - if( gmtime64_r(time, &gm_tm) == NULL ) + if( S_gmtime64_r(time, &gm_tm) == NULL ) { + TIME64_TRACE1("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) ) { - gm_tm.tm_year = _safe_year(gm_tm.tm_year + 1900) - 1900; + TIME64_TRACE1("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year); + gm_tm.tm_year = S_safe_year((Year)(gm_tm.tm_year + 1900)) - 1900; } - safe_time = TIMEGM(&gm_tm); - if( LOCALTIME_R(&safe_time, local_tm) == NULL ) + safe_time = (time_t)S_timegm64(&gm_tm); + if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) { + TIME64_TRACE1("localtime_r(%d) returned NULL\n", (int)safe_time); return NULL; + } + + S_copy_little_tm_to_big_TM(&safe_date, local_tm); local_tm->tm_year = orig_year; if( local_tm->tm_year != orig_year ) { + TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n", + (Year)local_tm->tm_year, (Year)orig_year); + #ifdef EOVERFLOW errno = EOVERFLOW; #endif @@ -469,13 +543,13 @@ struct tm *localtime64_r (const Time64_T *time, struct tm *local_tm) /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st in a non-leap xx00. There is one point in the cycle we can't account for which the safe xx00 year is a leap - year. So we need to correct for Dec 31st comming out as + year. So we need to correct for Dec 31st coming out as the 366th day of the year. */ if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 ) local_tm->tm_yday--; - assert(_check_tm(local_tm)); + assert(S_check_tm(local_tm)); return local_tm; }