Wrapper around strftime for wide character version.
[kopensolaris-gnu/glibc.git] / time / mktime.c
index ce691c8..c8ecd07 100644 (file)
@@ -1,4 +1,5 @@
-/* Copyright (C) 1993, 94, 95, 96, 97, 98 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).
 
 # include <config.h>
 #endif
 
-/* Some systems need this in order to declare localtime_r properly.  */
-#ifndef __EXTENSIONS__
-# define __EXTENSIONS__ 1
-#endif
-
 #ifdef _LIBC
 # define HAVE_LIMITS_H 1
-# define HAVE_LOCALTIME_R 1
 # define STDC_HEADERS 1
 #endif
 
@@ -60,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) ()
@@ -112,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)
@@ -139,7 +123,6 @@ localtime_r (t, tp)
   *tp = *l;
   return tp;
 }
-# endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */
 #endif /* ! _LIBC */
 
 
@@ -150,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;
@@ -179,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;
 
@@ -251,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.  */
@@ -309,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;
            }
        }
     }
@@ -384,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
@@ -528,6 +523,6 @@ main (argc, argv)
 \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:
 */