X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmktime.c;h=c8ecd07f12b6592b92b6425e7bf353d316b41550;hb=ade79def12b42c40afa2d4bd47989ee40df7fe98;hp=4709d56895c9997d9533e985e27847b42a549184;hpb=bfd921d6669111f84fe57b2b3c9de31ed2eba54f;p=gnulib.git diff --git a/lib/mktime.c b/lib/mktime.c index 4709d5689..c8ecd07f1 100644 --- a/lib/mktime.c +++ b/lib/mktime.c @@ -1,23 +1,22 @@ -/* Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998 Free Software Foundation, Inc. +/* Convert a `struct tm' to a time_t value. + Copyright (C) 1993, 94, 95, 96, 97, 98, 99 Free Software Foundation, Inc. + This file is part of the GNU C Library. Contributed by Paul Eggert (eggert@twinsun.com). - NOTE: The canonical source of this file is maintained with the GNU C Library. - Bugs can be reported to bug-glibc@prep.ai.mit.edu. + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. - This program is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any - later version. - - This program is distributed in the hope that it will be useful, + The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - USA. */ + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ /* Define this to have a standalone program to test this implementation of mktime. */ @@ -27,14 +26,8 @@ # include #endif -/* Some systems need this in order to declare localtime_r properly. */ -#ifndef _REENTRANT -# define _REENTRANT 1 -#endif - #ifdef _LIBC # define HAVE_LIMITS_H 1 -# define HAVE_LOCALTIME_R 1 # define STDC_HEADERS 1 #endif @@ -62,7 +55,7 @@ #endif /* DEBUG */ #ifndef __P -# if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +# if defined __GNUC__ || (defined __STDC__ && __STDC__) # define __P(args) args # else # define __P(args) () @@ -79,7 +72,7 @@ It is necessary at least when t == time_t. */ #define TYPE_MINIMUM(t) ((t) (TYPE_SIGNED (t) \ ? ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)) -#define TYPE_MAXIMUM(t) (~ (t) 0 - TYPE_MINIMUM (t)) +#define TYPE_MAXIMUM(t) ((t) (~ (t) 0 - TYPE_MINIMUM (t))) #ifndef INT_MIN # define INT_MIN TYPE_MINIMUM (int) @@ -114,26 +107,15 @@ const unsigned short int __mon_yday[2][13] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; -static struct tm *ranged_convert __P ((struct tm *(*) __P ((const time_t *, - struct tm *)), - time_t *, struct tm *)); -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 *)); - #ifdef _LIBC -# define localtime_r __localtime_r +# define my_mktime_localtime_r __localtime_r #else -# if ! HAVE_LOCALTIME_R && ! defined localtime_r -/* Approximate localtime_r as best we can in its absence. */ -# define localtime_r my_mktime_localtime_r -static struct tm *localtime_r __P ((const time_t *, struct tm *)); +/* If we're a mktime substitute in a GNU program, then prefer + localtime to localtime_r, since many localtime_r implementations + are buggy. */ static struct tm * -localtime_r (t, tp) - const time_t *t; - struct tm *tp; +my_mktime_localtime_r (const time_t *t, struct tm *tp) { struct tm *l = localtime (t); if (! l) @@ -141,7 +123,6 @@ localtime_r (t, tp) *tp = *l; return tp; } -# endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */ #endif /* ! _LIBC */ @@ -152,9 +133,8 @@ localtime_r (t, tp) If TP is null, return a nonzero value. 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; +ydhms_tm_diff (int year, int yday, int hour, int min, int sec, + const struct tm *tp) { if (!tp) return 1; @@ -181,32 +161,12 @@ ydhms_tm_diff (year, yday, hour, min, sec, tp) } } - -static time_t localtime_offset; - -/* Convert *TP to a time_t value. */ -time_t -mktime (tp) - struct tm *tp; -{ -#ifdef _LIBC - /* POSIX.1 8.1.1 requires that whenever mktime() is called, the - time zone names contained in the external variable `tzname' shall - be set as if the tzset() function had been called. */ - __tzset (); -#endif - - return __mktime_internal (tp, localtime_r, &localtime_offset); -} - /* Use CONVERT to convert *T to a broken down time in *TP. If *T is out of range for conversion, adjust it so that it is the nearest in-range value and then convert that. */ static struct tm * -ranged_convert (convert, t, tp) - struct tm *(*convert) __P ((const time_t *, struct tm *)); - time_t *t; - struct tm *tp; +ranged_convert (struct tm *(*convert) (const time_t *, struct tm *), + time_t *t, struct tm *tp) { struct tm *r; @@ -253,19 +213,18 @@ ranged_convert (convert, t, tp) 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; +__mktime_internal (struct tm *tp, + struct tm *(*convert) (const time_t *, struct tm *), + time_t *offset) { - time_t t, dt, t0; + time_t t, dt, t0, t1, t2; struct tm tm; /* 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; + leap seconds, and oscillations around a spring-forward gap. + POSIX.1 prohibits leap seconds, but some hosts have them anyway. */ + int remaining_probes = 6; /* Time requested. Copy it in case CONVERT modifies *TP; this can occur if TP is localtime's returned value and CONVERT is localtime. */ @@ -311,36 +270,52 @@ __mktime_internal (tp, convert, offset) 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; + for (t = t1 = t2 = t0 + *offset; (dt = ydhms_tm_diff (year, yday, hour, min, sec, ranged_convert (convert, &t, &tm))); - t += dt) - if (--remaining_probes == 0) + t1 = t2, t2 = t, t += dt) + if (t == t1 && t != t2 + && (isdst < 0 || tm.tm_isdst < 0 + || (isdst != 0) != (tm.tm_isdst != 0))) + /* We can't possibly find a match, as we are oscillating + between two values. The requested time probably falls + within a spring-forward gap of size DT. Follow the common + practice in this case, which is to return a time that is DT + away from the requested time, preferring a time whose + tm_isdst differs from the requested value. In practice, + this is more useful than returning -1. */ + break; + else 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 we have a match, check whether tm.tm_isdst has the requested + value, if any. */ + if (dt == 0 && isdst != tm.tm_isdst && 0 <= isdst && 0 <= tm.tm_isdst) { - int dst_diff = (isdst != 0) - (tm.tm_isdst != 0); - if (dst_diff) + /* tm.tm_isdst has the wrong value. Look for a neighboring + time with the right value, and use its UTC offset. + Heuristic: probe the previous three calendar quarters (approximately), + looking for the desired isdst. This isn't perfect, + but it's good enough in practice. */ + int quarter = 7889238; /* seconds per average 1/4 Gregorian year */ + int i; + + /* If we're too close to the time_t limit, look in future quarters. */ + if (t < TIME_T_MIN + 3 * quarter) + quarter = -quarter; + + for (i = 1; i <= 3; i++) { - /* 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) + time_t ot = t - i * quarter; + struct tm otm; + ranged_convert (convert, &ot, &otm); + if (otm.tm_isdst == isdst) { - struct tm otm; - if (! (dt = ydhms_tm_diff (year, yday, hour, min, sec, - ranged_convert (convert, &ot, &otm)))) - { - t = ot; - tm = otm; - break; - } - if ((ot += dt) == t) - break; /* Avoid a redundant probe. */ + /* We found the desired tm_isdst. + Extrapolate back to the desired time. */ + t = ot + ydhms_tm_diff (year, yday, hour, min, sec, &otm); + ranged_convert (convert, &t, &tm); + break; } } } @@ -386,6 +361,24 @@ __mktime_internal (tp, convert, offset) return t; } + +static time_t localtime_offset; + +/* Convert *TP to a time_t value. */ +time_t +mktime (tp) + struct tm *tp; +{ +#ifdef _LIBC + /* POSIX.1 8.1.1 requires that whenever mktime() is called, the + time zone names contained in the external variable `tzname' shall + be set as if the tzset() function had been called. */ + __tzset (); +#endif + + return __mktime_internal (tp, my_mktime_localtime_r, &localtime_offset); +} + #ifdef weak_alias weak_alias (mktime, timelocal) #endif @@ -530,6 +523,6 @@ main (argc, argv) /* Local Variables: -compile-command: "gcc -DDEBUG -D__EXTENSIONS__ -DHAVE_LIMITS_H -DHAVE_LOCALTIME_R -DSTDC_HEADERS -Wall -W -O -g mktime.c -o mktime" +compile-command: "gcc -DDEBUG -DHAVE_LIMITS_H -DSTDC_HEADERS -Wall -W -O -g mktime.c -o mktime" End: */