X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmktime.c;h=852d4058b958c46108e930bae4db2f397dce0325;hb=692c4b6ea764fd34a53142b53c53a2d54b33ae6a;hp=1c5bf6466f2215cad96734e3ff49f17fa732d4d7;hpb=43e78b970f396160a887c9da94531569faeda8a0;p=gnulib.git diff --git a/lib/mktime.c b/lib/mktime.c index 1c5bf6466..852d4058b 100644 --- a/lib/mktime.c +++ b/lib/mktime.c @@ -1,4 +1,6 @@ -/* Copyright (C) 1991 Free Software Foundation, Inc. +/* Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc. + Contributed by Paul Eggert (eggert@twinsun.com). + This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -16,209 +18,391 @@ License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include -#include -#ifndef STDC_HEADERS -extern int errno; +/* Define this to have a standalone program to test this implementation of + mktime. */ +/* #define DEBUG 1 */ + +#ifdef HAVE_CONFIG_H +#include #endif -#ifdef TM_IN_SYS_TIME -#include -#else -#include + +/* Assume that leap seconds are possible, unless told otherwise. + If the host has a `zic' command with a `-L leapsecondfilename' option, + then it supports leap seconds; otherwise it probably doesn't. */ +#ifndef LEAP_SECONDS_POSSIBLE +#define LEAP_SECONDS_POSSIBLE 1 #endif -#ifdef HAVE_LIMITS_H + +#include /* Some systems define `time_t' here. */ +#include + +#if __STDC__ || __GNU_LIBRARY__ || STDC_HEADERS #include +#endif + +#if DEBUG +#include +#if __STDC__ || __GNU_LIBRARY__ || STDC_HEADERS +#include +#endif +/* Make it work even if the system's libc has its own mktime routine. */ +#define mktime my_mktime +#endif /* DEBUG */ + +#ifndef __P +#if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +#define __P(args) args #else -#define LONG_MAX (~(1 << (sizeof (long) * 8 - 1))) -#define LONG_MIN (-LONG_MAX - 1) -#define INT_MAX (~(1 << (sizeof (int) * 8 - 1))) -#define INT_MIN (-INT_MAX - 1) +#define __P(args) () +#endif /* GCC. */ +#endif /* Not __P. */ + +#ifndef CHAR_BIT +#define CHAR_BIT 8 +#endif + +#ifndef INT_MIN +#define INT_MIN (~0 << (sizeof (int) * CHAR_BIT - 1)) +#endif +#ifndef INT_MAX +#define INT_MAX (~0 - INT_MIN) #endif -#ifndef NULL -#define NULL 0 +#ifndef TIME_T_MIN +#define TIME_T_MIN (0 < (time_t) -1 ? (time_t) 0 \ + : ~ (time_t) 0 << (sizeof (time_t) * CHAR_BIT - 1)) +#endif +#ifndef TIME_T_MAX +#define TIME_T_MAX (~ (time_t) 0 - TIME_T_MIN) #endif +#define TM_YEAR_BASE 1900 +#define EPOCH_YEAR 1970 + #ifndef __isleap /* Nonzero if YEAR is a leap year (every 4 years, - except every 100th isn't, and every 1000th is). */ + except every 100th isn't, and every 400th is). */ #define __isleap(year) \ - ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 1000 == 0)) + ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) #endif -/* How many days are in each month. */ -static unsigned short int __mon_lengths[2][12] = +/* How many days come before each month (0-12). */ +const unsigned short int __mon_yday[2][13] = + { + /* Normal years. */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + /* Leap years. */ + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } + }; + +static time_t ydhms_tm_diff __P ((int, int, int, int, int, const struct tm *)); +time_t __mktime_internal __P ((struct tm *, + struct tm *(*) (const time_t *, struct tm *), + time_t *)); + + +#if ! HAVE_LOCALTIME_R && ! defined (localtime_r) +#ifdef _LIBC +#define localtime_r __localtime_r +#else +/* Approximate localtime_r as best we can in its absence. */ +#define localtime_r my_localtime_r +static struct tm *localtime_r __P ((const time_t *, struct tm *)); +static struct tm * +localtime_r (t, tp) + const time_t *t; + struct tm *tp; { - /* Normal years. */ - { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, - /* Leap years. */ - { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -}; - -#define invalid() return (time_t) -1 - -/* Return the `time_t' representation of TP and normalizes TP. - Return (time_t) -1 if TP is not representable as a `time_t'. - Note that 31 Dec 1969 23:59:59 is not representable - because it is represented as (time_t) -1. */ + struct tm *l = localtime (t); + if (! l) + return 0; + *tp = *l; + return tp; +} +#endif /* ! _LIBC */ +#endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */ + + +/* Yield the difference between (YEAR-YDAY HOUR:MIN:SEC) and (*TP), + measured in seconds, ignoring leap seconds. + YEAR uses the same numbering as TM->tm_year. + All values are in range, except possibly YEAR. + If overflow occurs, yield the low order bits of the correct answer. */ +static time_t +ydhms_tm_diff (year, yday, hour, min, sec, tp) + int year, yday, hour, min, sec; + const struct tm *tp; +{ + time_t ay = year + (time_t) (TM_YEAR_BASE - 1); + time_t by = tp->tm_year + (time_t) (TM_YEAR_BASE - 1); + time_t intervening_leap_days = + (ay/4 - by/4) - (ay/100 - by/100) + (ay/400 - by/400); + time_t years = ay - by; + time_t days = (365 * years + intervening_leap_days + + (yday - tp->tm_yday)); + return (60 * (60 * (24 * days + (hour - tp->tm_hour)) + + (min - tp->tm_min)) + + (sec - tp->tm_sec)); +} + + +/* Convert *TP to a time_t value. */ time_t -mktime(tp) -register struct tm *tp; +mktime (tp) + struct tm *tp; { - static struct tm min, max; - static char init = 0; + static time_t localtime_offset; + return __mktime_internal (tp, localtime_r, &localtime_offset); +} - register time_t result; - register time_t t; - register int i; - register unsigned short *l; - register struct tm *new; - time_t end; +/* Convert *TP to a time_t value, inverting + the monotonic and mostly-unit-linear conversion function CONVERT. + Use *OFFSET to keep track of a guess at the offset of the result, + compared to what the result would be for UTC without leap seconds. + If *OFFSET's guess is correct, only one CONVERT call is needed. */ +time_t +__mktime_internal (tp, convert, offset) + struct tm *tp; + struct tm *(*convert) __P ((const time_t *, struct tm *)); + time_t *offset; +{ + time_t t, dt, t0; + struct tm tm; - if (tp == NULL) - { - errno = EINVAL; - invalid(); - } + /* The maximum number of probes (calls to CONVERT) should be enough + to handle any combinations of time zone rule changes, solar time, + and leap seconds. Posix.1 prohibits leap seconds, but some hosts + have them anyway. */ + int remaining_probes = 4; - if (!init) - { - init = 1; - end = (time_t) LONG_MIN; - new = gmtime(&end); - if (new != NULL) - min = *new; - else - min.tm_sec = min.tm_min = min.tm_hour = - min.tm_mday = min.tm_mon = min.tm_year = INT_MIN; + /* Time requested. Copy it in case CONVERT modifies *TP; this can + occur if TP is localtime's returned value and CONVERT is localtime. */ + int sec = tp->tm_sec; + int min = tp->tm_min; + int hour = tp->tm_hour; + int mday = tp->tm_mday; + int mon = tp->tm_mon; + int year_requested = tp->tm_year; + int isdst = tp->tm_isdst; - end = (time_t) LONG_MAX; - new = gmtime(&end); - if (new != NULL) - max = *new; - else - max.tm_sec = max.tm_min = max.tm_hour = - max.tm_mday = max.tm_mon = max.tm_year = INT_MAX; - } + /* Ensure that mon is in range, and set year accordingly. */ + int mon_remainder = mon % 12; + int negative_mon_remainder = mon_remainder < 0; + int mon_years = mon / 12 - negative_mon_remainder; + int year = year_requested + mon_years; - /* Make all the elements of TP that we pay attention to - be within the ranges of reasonable values for those things. */ -#define normalize(elt, min, max, nextelt) \ - while (tp->elt < min) \ - { \ - --tp->nextelt; \ - tp->elt += max + 1; \ - } \ - while (tp->elt > max) \ - { \ - ++tp->nextelt; \ - tp->elt -= max + 1; \ - } + /* The other values need not be in range: + the remaining code handles minor overflows correctly, + assuming int and time_t arithmetic wraps around. + Major overflows are caught at the end. */ + + /* Calculate day of year from year, month, and day of month. + The result need not be in range. */ + int yday = ((__mon_yday[__isleap (year + TM_YEAR_BASE)] + [mon_remainder + 12 * negative_mon_remainder]) + + mday - 1); + +#if LEAP_SECONDS_POSSIBLE + /* Handle out-of-range seconds specially, + since ydhms_tm_diff assumes every minute has 60 seconds. */ + int sec_requested = sec; + if (sec < 0) + sec = 0; + if (59 < sec) + sec = 59; +#endif + + /* Invert CONVERT by probing. First assume the same offset as last time. + Then repeatedly use the error to improve the guess. */ - normalize (tm_sec, 0, 59, tm_min); - normalize (tm_min, 0, 59, tm_hour); - normalize (tm_hour, 0, 24, tm_mday); - - /* Normalize the month first so we can use - it to figure the range for the day. */ - normalize (tm_mon, 0, 11, tm_year); - normalize (tm_mday, 1, __mon_lengths[__isleap (tp->tm_year)][tp->tm_mon], - tm_mon); - - /* Normalize the month again, since normalizing - the day may have pushed it out of range. */ - normalize (tm_mon, 0, 11, tm_year); - - /* Normalize the day again, because normalizing - the month may have changed the range. */ - normalize (tm_mday, 1, __mon_lengths[__isleap (tp->tm_year)][tp->tm_mon], - tm_mon); - - /* Check for out-of-range values. */ -#define lowhigh(field, minmax, cmp) (tp->field cmp minmax.field) -#define low(field) lowhigh(field, min, <) -#define high(field) lowhigh(field, max, >) -#define oor(field) (low(field) || high(field)) -#define lowbound(field) (tp->field == min.field) -#define highbound(field) (tp->field == max.field) - if (oor(tm_year)) - invalid(); - else if (lowbound(tm_year)) + tm.tm_year = EPOCH_YEAR - TM_YEAR_BASE; + tm.tm_yday = tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + t0 = ydhms_tm_diff (year, yday, hour, min, sec, &tm); + + for (t = t0 + *offset; + (dt = ydhms_tm_diff (year, yday, hour, min, sec, (*convert) (&t, &tm))); + t += dt) + if (--remaining_probes == 0) + return -1; + + /* Check whether tm.tm_isdst has the requested value, if any. */ + if (0 <= isdst && 0 <= tm.tm_isdst) { - if (low(tm_mon)) - invalid(); - else if (lowbound(tm_mon)) + int dst_diff = (isdst != 0) - (tm.tm_isdst != 0); + if (dst_diff) { - if (low(tm_mday)) - invalid(); - else if (lowbound(tm_mday)) + /* Move two hours in the direction indicated by the disagreement, + probe some more, and switch to a new time if found. + The largest known fallback due to daylight savings is two hours: + once, in Newfoundland, 1988-10-30 02:00 -> 00:00. */ + time_t ot = t - 2 * 60 * 60 * dst_diff; + while (--remaining_probes != 0) { - if (low(tm_hour)) - invalid(); - else if (lowbound(tm_hour)) + struct tm otm; + if (! (dt = ydhms_tm_diff (year, yday, hour, min, sec, + (*convert) (&ot, &otm)))) { - if (low(tm_min)) - invalid(); - else if (lowbound(tm_min)) - { - if (low(tm_sec)) - invalid(); - } + t = ot; + tm = otm; + break; } + if ((ot += dt) == t) + break; /* Avoid a redundant probe. */ } } } - else if (highbound(tm_year)) + + *offset = t - t0; + +#if LEAP_SECONDS_POSSIBLE + if (sec_requested != tm.tm_sec) { - if (high(tm_mon)) - invalid(); - else if (highbound(tm_mon)) - { - if (high(tm_mday)) - invalid(); - else if (highbound(tm_mday)) - { - if (high(tm_hour)) - invalid(); - else if (highbound(tm_hour)) - { - if (high(tm_min)) - invalid(); - else if (highbound(tm_min)) - { - if (high(tm_sec)) - invalid(); - } - } - } - } + /* Adjust time to reflect the tm_sec requested, not the normalized value. + Also, repair any damage from a false match due to a leap second. */ + t += sec_requested - sec + (sec == 0 && tm.tm_sec == 60); + (*convert) (&t, &tm); } +#endif - t = 0; - for (i = 1970; i > 1900 + tp->tm_year; --i) - t -= __isleap(i) ? 366 : 365; - for (i = 1970; i < 1900 + tp->tm_year; ++i) - t += __isleap(i) ? 366 : 365; - l = __mon_lengths[__isleap(1900 + tp->tm_year)]; - for (i = 0; i < tp->tm_mon; ++i) - t += l[i]; - t += tp->tm_mday - 1; - result = ((t * 60 * 60 * 24) + - (tp->tm_hour * 60 * 60) + - (tp->tm_min * 60) + - tp->tm_sec); - - end = result; -#if 0 /* This code breaks it, on SunOS anyway. */ - if (tp->tm_isdst < 0) - new = localtime(&end); - else + if (TIME_T_MAX / INT_MAX / 366 / 24 / 60 / 60 < 3) + { + /* time_t isn't large enough to rule out overflows in ydhms_tm_diff, + so check for major overflows. A gross check suffices, + since if t has overflowed, it is off by a multiple of + TIME_T_MAX - TIME_T_MIN + 1. So ignore any component of + the difference that is bounded by a small value. */ + + double dyear = (double) year_requested + mon_years - tm.tm_year; + double dday = 366 * dyear + mday; + double dsec = 60 * (60 * (24 * dday + hour) + min) + sec_requested; + + if (TIME_T_MAX / 3 - TIME_T_MIN / 3 < (dsec < 0 ? - dsec : dsec)) + return -1; + } + + *tp = tm; + return t; +} + +#ifdef weak_alias +weak_alias (mktime, timelocal) #endif - new = gmtime(&end); - if (new == NULL) - invalid(); - new->tm_isdst = tp->tm_isdst; - *tp = *new; + +#if DEBUG + +static int +not_equal_tm (a, b) + struct tm *a; + struct tm *b; +{ + return ((a->tm_sec ^ b->tm_sec) + | (a->tm_min ^ b->tm_min) + | (a->tm_hour ^ b->tm_hour) + | (a->tm_mday ^ b->tm_mday) + | (a->tm_mon ^ b->tm_mon) + | (a->tm_year ^ b->tm_year) + | (a->tm_mday ^ b->tm_mday) + | (a->tm_yday ^ b->tm_yday) + | (a->tm_isdst ^ b->tm_isdst)); +} - return result; +static void +print_tm (tp) + struct tm *tp; +{ + printf ("%04d-%02d-%02d %02d:%02d:%02d yday %03d wday %d isdst %d", + tp->tm_year + TM_YEAR_BASE, tp->tm_mon + 1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec, + tp->tm_yday, tp->tm_wday, tp->tm_isdst); } + +static int +check_result (tk, tmk, tl, tml) + time_t tk; + struct tm tmk; + time_t tl; + struct tm tml; +{ + if (tk != tl || not_equal_tm (&tmk, &tml)) + { + printf ("mktime ("); + print_tm (&tmk); + printf (")\nyields ("); + print_tm (&tml); + printf (") == %ld, should be %ld\n", (long) tl, (long) tk); + return 1; + } + + return 0; +} + +int +main (argc, argv) + int argc; + char **argv; +{ + int status = 0; + struct tm tm, tmk, tml; + time_t tk, tl; + char trailer; + + if ((argc == 3 || argc == 4) + && (sscanf (argv[1], "%d-%d-%d%c", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &trailer) + == 3) + && (sscanf (argv[2], "%d:%d:%d%c", + &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &trailer) + == 3)) + { + tm.tm_year -= TM_YEAR_BASE; + tm.tm_mon--; + tm.tm_isdst = argc == 3 ? -1 : atoi (argv[3]); + tmk = tm; + tl = mktime (&tmk); + tml = *localtime (&tl); + printf ("mktime returns %ld == ", (long) tl); + print_tm (&tmk); + printf ("\n"); + status = check_result (tl, tmk, tl, tml); + } + else if (argc == 4 || (argc == 5 && strcmp (argv[4], "-") == 0)) + { + time_t from = atol (argv[1]); + time_t by = atol (argv[2]); + time_t to = atol (argv[3]); + + if (argc == 4) + for (tl = from; tl <= to; tl += by) + { + tml = *localtime (&tl); + tmk = tml; + tk = mktime (&tmk); + status |= check_result (tk, tmk, tl, tml); + } + else + for (tl = from; tl <= to; tl += by) + { + /* Null benchmark. */ + tml = *localtime (&tl); + tmk = tml; + tk = tl; + status |= check_result (tk, tmk, tl, tml); + } + } + else + printf ("Usage:\ +\t%s YYYY-MM-DD HH:MM:SS [ISDST] # Test given time.\n\ +\t%s FROM BY TO # Test values FROM, FROM+BY, ..., TO.\n\ +\t%s FROM BY TO - # Do not test those values (for benchmark).\n", + argv[0], argv[0], argv[0]); + + return status; +} + +#endif /* DEBUG */ + +/* +Local Variables: +compile-command: "gcc -DDEBUG=1 -Wall -O -g mktime.c -o mktime" +End: +*/