From 37152e9703110a595e470378f98f9e6de4ba284b Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Mon, 8 Dec 2014 11:48:31 +0000 Subject: * libc/time/gmtime_r.c (gmtime_r): Fixed bug in calculations for dates after year 2069 or before year 1901. Ideas for solution taken from musl's __secs_to_tm() --- newlib/ChangeLog | 6 +++ newlib/libc/time/gmtime_r.c | 107 +++++++++++++++++++++++++++++--------------- 2 files changed, 77 insertions(+), 36 deletions(-) diff --git a/newlib/ChangeLog b/newlib/ChangeLog index 521732d86..4eaef9f74 100644 --- a/newlib/ChangeLog +++ b/newlib/ChangeLog @@ -1,3 +1,9 @@ +2014-12-07 Freddie Chopin + + * libc/time/gmtime_r.c (gmtime_r): Fixed bug in calculations for dates + after year 2069 or before year 1901. Ideas for solution taken from + musl's __secs_to_tm() + 2014-12-05 Yaakov Selkowitz * libc/include/stdlib.h (__bsd_qsort_r): Declare. diff --git a/newlib/libc/time/gmtime_r.c b/newlib/libc/time/gmtime_r.c index dddd5763a..f50c199b9 100644 --- a/newlib/libc/time/gmtime_r.c +++ b/newlib/libc/time/gmtime_r.c @@ -6,6 +6,9 @@ * - Fixed bug in mday computations - 08/12/04, Alex Mogilnikov * - Fixed bug in __tzcalc_limits - 08/12/04, Alex Mogilnikov * - Move code from _mktm_r() to gmtime_r() - 05/09/14, Freddie Chopin + * - Fixed bug in calculations for dates after year 2069 or before year 1901. Ideas for + * solution taken from musl's __secs_to_tm() - 07/12/2014, Freddie Chopin + * * * Converts the calendar time pointed to by tim_p into a broken-down time * expressed as local time. Returns a pointer to a structure containing the @@ -14,19 +17,28 @@ #include "local.h" +/* move epoch from 01.01.1970 to 01.03.2000 - this is the first day of new + * 400-year long cycle, right after additional day of leap year. This adjustment + * is required only for date calculation, so instead of modifying time_t value + * (which would require 64-bit operations to work correctly) it's enough to + * adjust the calculated number of days since epoch. */ +#define EPOCH_ADJUSTMENT_DAYS 11017 +/* year to which the adjustment was made */ +#define ADJUSTED_EPOCH_YEAR 2000 +/* 1st March of 2000 is Wednesday */ +#define ADJUSTED_EPOCH_WDAY 3 /* there are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 * 366) */ #define DAYS_PER_400_YEARS 146097L /* there are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 * 366) */ #define DAYS_PER_100_YEARS 36524L /* there is one leap year every 4 years */ #define DAYS_PER_4_YEARS (3 * 365 + 366) - -static _CONST int days_per_year[4] = { - 365, /* 1970 or 1966 */ - 365, /* 1971 or 1967 */ - 366, /* 1972 or 1968 */ - 365 /* 1973 or 1969 */ -} ; +/* number of days in a non-leap year */ +#define DAYS_PER_YEAR 365 +/* number of days in January */ +#define DAYS_IN_JANUARY 31 +/* number of days in non-leap February */ +#define DAYS_IN_FEBRUARY 28 struct tm * _DEFUN (gmtime_r, (tim_p, res), @@ -35,14 +47,14 @@ _DEFUN (gmtime_r, (tim_p, res), { long days, rem; _CONST time_t lcltime = *tim_p; - int year; - int years400, years100, years4; + int year, month, yearday, weekday; + int years400, years100, years4, remainingyears; int yearleap; _CONST int *ip; - days = ((long)lcltime) / SECSPERDAY; + days = ((long)lcltime) / SECSPERDAY - EPOCH_ADJUSTMENT_DAYS; rem = ((long)lcltime) % SECSPERDAY; - while (rem < 0) + if (rem < 0) { rem += SECSPERDAY; --days; @@ -55,45 +67,68 @@ _DEFUN (gmtime_r, (tim_p, res), res->tm_sec = (int) (rem % SECSPERMIN); /* compute day of week */ - if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) - res->tm_wday += DAYSPERWEEK; + if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) + weekday += DAYSPERWEEK; + res->tm_wday = weekday; /* compute year & day of year */ years400 = days / DAYS_PER_400_YEARS; days -= years400 * DAYS_PER_400_YEARS; + /* simplify by making the values positive */ + if (days < 0) + { + days += DAYS_PER_400_YEARS; + --years400; + } + years100 = days / DAYS_PER_100_YEARS; + if (years100 == 4) /* required for proper day of year calculation */ + --years100; days -= years100 * DAYS_PER_100_YEARS; years4 = days / DAYS_PER_4_YEARS; days -= years4 * DAYS_PER_4_YEARS; + remainingyears = days / DAYS_PER_YEAR; + if (remainingyears == 4) /* required for proper day of year calculation */ + --remainingyears; + days -= remainingyears * DAYS_PER_YEAR; - year = EPOCH_YEAR + years400 * 400 + years100 * 100 + years4 * 4; + year = ADJUSTED_EPOCH_YEAR + years400 * 400 + years100 * 100 + years4 * 4 + + remainingyears; - /* the value left in days is based in 1970 */ - if (days < 0) + /* If remainingyears is zero, it means that the years were completely + * "consumed" by modulo calculations by 400, 100 and 4, so the year is: + * 1. a multiple of 4, but not a multiple of 100 or 400 - it's a leap year, + * 2. a multiple of 4 and 100, but not a multiple of 400 - it's not a leap + * year, + * 3. a multiple of 4, 100 and 400 - it's a leap year. + * If years4 is non-zero, it means that the year is not a multiple of 100 or + * 400 (case 1), so it's a leap year. If years100 is zero (and years4 is zero + * - due to short-circuiting), it means that the year is a multiple of 400 + * (case 3), so it's also a leap year. */ + yearleap = remainingyears == 0 && (years4 != 0 || years100 == 0); + + /* adjust back to 1st January */ + yearday = days + DAYS_IN_JANUARY + DAYS_IN_FEBRUARY + yearleap; + if (yearday >= DAYS_PER_YEAR + yearleap) { - ip = &days_per_year[3]; - while (days < 0) - { - days += *ip--; - --year; - } + yearday -= DAYS_PER_YEAR + yearleap; + ++year; } - else + res->tm_yday = yearday; + res->tm_year = year - YEAR_BASE; + + /* Because "days" is the number of days since 1st March, the additional leap + * day (29th of February) is the last possible day, so it doesn't matter much + * whether the year is actually leap or not. */ + ip = __month_lengths[1]; + month = 2; + while (days >= ip[month]) { - ip = &days_per_year[0]; - while (days >= *ip) - { - days -= *ip++; - ++year; - } + days -= ip[month]; + if (++month >= MONSPERYEAR) + month = 0; } - - res->tm_year = year - YEAR_BASE; - res->tm_yday = days; - yearleap = isleap(year); - ip = __month_lengths[yearleap]; - for (res->tm_mon = 0; days >= ip[res->tm_mon]; ++res->tm_mon) - days -= ip[res->tm_mon]; + res->tm_mon = month; res->tm_mday = days + 1; res->tm_isdst = 0; -- cgit v1.2.3