-/* mktime: convert a `struct tm' to a time_t value
- Copyright (C) 1993-1997, 1998 Free Software Foundation, Inc.
+/* Convert a `struct tm' to a time_t value.
+ Copyright (C) 1993-1999, 2002 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 Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 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
+ Lesser 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 Lesser General Public
+ License along with the GNU C Library; 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. */
# include <config.h>
#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
+# define STATIC /* empty */
+#else
+# define STATIC static
#endif
/* Assume that leap seconds are possible, unless told otherwise.
# include <stdio.h>
# if STDC_HEADERS
# include <stdlib.h>
+# include <string.h>
# 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__)
+# if defined __GNUC__ || (defined __STDC__ && __STDC__)
# define __P(args) args
# else
# define __P(args) ()
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)
#endif
/* How many days come before each month (0-12). */
+#ifndef _LIBC
+static
+#endif
const unsigned short int __mon_yday[2][13] =
{
/* Normal years. */
{ 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)
*tp = *l;
return tp;
}
-# endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */
#endif /* ! _LIBC */
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;
}
}
-
-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;
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. */
+#ifndef _LIBC
+static
+#endif
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. */
int year_requested = tp->tm_year;
int isdst = tp->tm_isdst;
+ /* 1 if the previous probe was DST. */
+ int dst2;
+
/* Ensure that mon is in range, and set year accordingly. */
int mon_remainder = mon % 12;
int negative_mon_remainder = mon_remainder < 0;
+ mday - 1);
int sec_requested = sec;
+
+ /* Only years after 1970 are defined.
+ If year is 69, it might still be representable due to
+ timezone differences. */
+ if (year < 69)
+ return -1;
+
#if LEAP_SECONDS_POSSIBLE
/* Handle out-of-range seconds specially,
since ydhms_tm_diff assumes every minute has 60 seconds. */
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, dst2 = 0;
(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, dst2 = tm.tm_isdst != 0)
+ if (t == t1 && t != t2
+ && (tm.tm_isdst < 0
+ || (isdst < 0
+ ? dst2 <= (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. (If no tm_isdst
+ was requested and only one of the two values has a nonzero
+ tm_isdst, prefer that 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;
}
}
}
return -1;
}
+ if (year == 69)
+ {
+ /* If year was 69, need to check whether the time was representable
+ or not. */
+ if (t < 0 || t > 2 * 24 * 60 * 60)
+ return -1;
+ }
+
*tp = tm;
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
+
+#ifdef _LIBC
+libc_hidden_def (mktime)
+libc_hidden_weak (timelocal)
+#endif
\f
#if DEBUG
\f
/*
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:
*/