X-Git-Url: https://perl5.git.perl.org/perl5.git/blobdiff_plain/806a119aef40085e6ee4a5a8bbab77cca98c9d08..000814da477053c7627c7ace5ca3ce3d4c4aad08:/time64.c diff --git a/time64.c b/time64.c index 2cb6ad2..ca7f786 100644 --- a/time64.c +++ b/time64.c @@ -33,25 +33,28 @@ 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(). +Perl_localtime64_r() is a 64-bit equivalent of localtime_r(). -gmtime64_r() is a 64-bit equivalent of gmtime_r(). +Perl_gmtime64_r() is a 64-bit equivalent of gmtime_r(). */ +#include "EXTERN.h" +#define PERL_IN_TIME64_C +#include "perl.h" #include "time64.h" -static const int days_in_month[2][12] = { +static const char days_in_month[2][12] = { {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, }; -static const int julian_days_by_month[2][12] = { +static const short julian_days_by_month[2][12] = { {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}, }; -static const int length_of_year[2] = { 365, 366 }; +static const short length_of_year[2] = { 365, 366 }; /* Number of days in a 400 year Gregorian cycle */ static const Year years_in_gregorian_cycle = 400; @@ -59,7 +62,7 @@ static const int days_in_gregorian_cycle = (365 * 400) + 100 - 4 + 1; /* 28 year calendar cycle between 2010 and 2037 */ #define SOLAR_CYCLE_LENGTH 28 -static const int safe_years[SOLAR_CYCLE_LENGTH] = { +static const short safe_years[SOLAR_CYCLE_LENGTH] = { 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, @@ -69,16 +72,6 @@ static const int safe_years[SOLAR_CYCLE_LENGTH] = { 2012, 2013, 2014, 2015 }; -static const int dow_year_start[SOLAR_CYCLE_LENGTH] = { - 5, 0, 1, 2, /* 0 2016 - 2019 */ - 3, 5, 6, 0, /* 4 */ - 1, 3, 4, 5, /* 8 */ - 6, 1, 2, 3, /* 12 */ - 4, 6, 0, 1, /* 16 */ - 2, 4, 5, 6, /* 20 2036, 2037, 2010, 2011 */ - 0, 2, 3, 4 /* 24 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. @@ -88,33 +81,53 @@ static const int dow_year_start[SOLAR_CYCLE_LENGTH] = { #define CHEAT_YEARS 108 #define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0) +#undef WRAP /* some define this */ #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 -static 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); } -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; @@ -142,11 +155,12 @@ Time64_T timegm64(struct TM *date) { seconds += date->tm_min * 60; seconds += date->tm_sec; - return((Time64_T)seconds); + return(seconds); } -static 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); @@ -177,12 +191,13 @@ static int check_tm(struct TM *tm) return 1; } +#endif /* The exceptional centuries without leap years cause the cycle to shift by 16 */ -static Year cycle_offset(Year year) +static Year S_cycle_offset(Year year) { const Year start_year = 2000; Year year_diff = year - start_year; @@ -194,10 +209,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); - */ + TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n", + year, exceptions, year_diff); return exceptions * 16; } @@ -219,17 +232,17 @@ static Year cycle_offset(Year year) It doesn't need the same leap year status since we only care about January 1st. */ -static 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; @@ -242,81 +255,48 @@ 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); - */ + TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n", + year, year_cycle, safe_year); return safe_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 - } +static void S_copy_little_tm_to_big_TM(const struct tm *src, struct TM *dest) { + assert(src); + assert(dest); +#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) { +#ifdef __VMS + dTHX; /* the following is defined as Perl_my_localtime(aTHX_ ...) */ +#endif const struct tm *static_result = localtime(clock); assert(result != NULL); @@ -330,10 +310,14 @@ 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) { +#ifdef __VMS + dTHX; /* the following is defined as Perl_my_localtime(aTHX_ ...) */ +#endif const struct tm *static_result = gmtime(clock); assert(result != NULL); @@ -347,14 +331,14 @@ 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) +struct TM *Perl_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; @@ -363,12 +347,12 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p) /* Use the system gmtime() if time_t is small enough */ if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) { - time_t safe_time = *in_time; + time_t safe_time = (time_t)*in_time; struct tm safe_date; GMTIME_R(&safe_time, &safe_date); - copy_tm_to_TM(&safe_date, p); - assert(check_tm(p)); + S_copy_little_tm_to_big_TM(&safe_date, p); + assert(S_check_tm(p)); return p; } @@ -379,22 +363,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)Perl_fmod(time, 60.0); + time = time >= 0 ? Perl_floor(time / 60.0) : Perl_ceil(time / 60.0); + v_tm_min = (int)Perl_fmod(time, 60.0); + time = time >= 0 ? Perl_floor(time / 60.0) : Perl_ceil(time / 60.0); + v_tm_hour = (int)Perl_fmod(time, 24.0); + time = time >= 0 ? Perl_floor(time / 24.0) : Perl_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)Perl_fmod((v_tm_tday + 4.0), 7.0); if (v_tm_wday < 0) v_tm_wday += 7; m = v_tm_tday; @@ -406,7 +390,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 = floor(m / (Time64_T) days_in_gregorian_cycle); + cycles = (int)Perl_floor(m / (Time64_T) days_in_gregorian_cycle); if( cycles ) { m -= (cycles * (Time64_T) days_in_gregorian_cycle); year += (cycles * years_in_gregorian_cycle); @@ -430,7 +414,7 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p) year--; /* Gregorian cycles */ - cycles = ceil(m / (Time64_T) days_in_gregorian_cycle) + 1; + cycles = (int)Perl_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); @@ -461,18 +445,22 @@ 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) +struct TM *Perl_localtime64_r (const Time64_T *time, struct TM *local_tm) { time_t safe_time; struct tm safe_date; @@ -484,36 +472,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; + safe_time = (time_t)*time; + + TIME64_TRACE1("Using system localtime for %lld\n", *time); LOCALTIME_R(&safe_time, &safe_date); - copy_tm_to_TM(&safe_date, local_tm); - assert(check_tm(local_tm)); + 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( Perl_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 = timegm64(&gm_tm); - if( LOCALTIME_R(&safe_time, &safe_date) == 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; + } - copy_tm_to_TM(&safe_date, local_tm); + 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 @@ -540,13 +538,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; }