[BZ #6024]
[platform/upstream/glibc.git] / time / tzset.c
index d6d1b20..a6fed4a 100644 (file)
@@ -1,20 +1,20 @@
-/* Copyright (C) 1991,92,93,94,95,96,97,98 Free Software Foundation, Inc.
+/* Copyright (C) 1991-2002,2003,2004,2007 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    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.
+   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.
 
    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
-   Library General Public License for more details.
+   Lesser General Public License for more details.
 
-   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.  */
+   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.  */
 
 #include <ctype.h>
 #include <errno.h>
@@ -38,7 +38,7 @@ weak_alias (__daylight, daylight)
 weak_alias (__timezone, timezone)
 
 /* This locks all the state variables in tzfile.c and this file.  */
-__libc_lock_define (static, tzset_lock)
+__libc_lock_define_initialized (static, tzset_lock)
 
 
 #define        min(a, b)       ((a) < (b) ? (a) : (b))
@@ -69,10 +69,9 @@ typedef struct
 static tz_rule tz_rules[2];
 
 
-static int compute_change __P ((tz_rule *rule, int year)) internal_function;
-static int tz_compute __P ((const struct tm *tm))
-     internal_function;
-static void tzset_internal __P ((int always)) internal_function;
+static void compute_change (tz_rule *rule, int year) __THROW internal_function;
+static void tzset_internal (int always, int explicit)
+     __THROW internal_function;
 \f
 /* List of buffers containing time zone strings. */
 struct tzstring_l
@@ -82,7 +81,7 @@ struct tzstring_l
   char data[0];
 };
 
-struct tzstring_l *tzstring_list;
+static struct tzstring_l *tzstring_list;
 
 /* Allocate a permanent home for S.  It will never be moved or deallocated,
    but may share space with other strings.
@@ -92,7 +91,7 @@ __tzstring (const char *s)
 {
   char *p;
   struct tzstring_l *t, *u, *new;
-  size_t len = strlen(s);
+  size_t len = strlen (s);
 
   /* Walk the list and look for a match.  If this string is the same
      as the end of an already-allocated string, it can share space. */
@@ -121,72 +120,52 @@ __tzstring (const char *s)
   return new->data;
 }
 \f
-static char *old_tz = NULL;
+/* Maximum length of a timezone name.  tzset_internal keeps this up to date
+   (never decreasing it) when ! __use_tzfile.
+   tzfile.c keeps it up to date when __use_tzfile.  */
+size_t __tzname_cur_max;
 
-/* Interpret the TZ envariable.  */
-static void
-internal_function
-tzset_internal (always)
-     int always;
+long int
+__tzname_max ()
 {
-  static int is_initialized = 0;
-  register const char *tz;
-  register size_t l;
-  char *tzbuf;
-  unsigned short int hh, mm, ss;
-  unsigned short int whichrule;
-
-  if (is_initialized && !always)
-    return;
-  is_initialized = 1;
-
-  /* Examine the TZ environment variable.  */
-  tz = getenv ("TZ");
-  if (tz == NULL)
-    /* No user specification; use the site-wide default.  */
-    tz = TZDEFAULT;
-  else if (*tz == '\0')
-    /* User specified the empty string; use UTC explicitly.  */
-    tz = "Universal";
-
-  /* A leading colon means "implementation defined syntax".
-     We ignore the colon and always use the same algorithm:
-     try a data file, and if none exists parse the 1003.1 syntax.  */
-  if (tz && *tz == ':')
-    ++tz;
+  __libc_lock_lock (tzset_lock);
 
-  /* Check whether the value changes since the last run.  */
-  if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
-    /* No change, simply return.  */
-    return;
+  tzset_internal (0, 0);
 
-  tz_rules[0].name = NULL;
-  tz_rules[1].name = NULL;
+  __libc_lock_unlock (tzset_lock);
 
-  /* Save the value of `tz'.  */
-  if (old_tz != NULL)
-    free (old_tz);
-  old_tz = tz ? __strdup (tz) : NULL;
+  return __tzname_cur_max;
+}
+\f
+static char *old_tz;
 
-  /* Try to read a data file.  */
-  __tzfile_read (tz);
-  if (__use_tzfile)
-    return;
+static void
+internal_function
+update_vars (void)
+{
+  __daylight = tz_rules[0].offset != tz_rules[1].offset;
+  __timezone = -tz_rules[0].offset;
+  __tzname[0] = (char *) tz_rules[0].name;
+  __tzname[1] = (char *) tz_rules[1].name;
 
-  /* No data file found.  Default to UTC if nothing specified.  */
+  /* Keep __tzname_cur_max up to date.  */
+  size_t len0 = strlen (__tzname[0]);
+  size_t len1 = strlen (__tzname[1]);
+  if (len0 > __tzname_cur_max)
+    __tzname_cur_max = len0;
+  if (len1 > __tzname_cur_max)
+    __tzname_cur_max = len1;
+}
 
-  if (tz == NULL || *tz == '\0')
-    {
-      tz_rules[0].name = tz_rules[1].name = "UTC";
-      tz_rules[0].type = tz_rules[1].type = J0;
-      tz_rules[0].m = tz_rules[0].n = tz_rules[0].d = 0;
-      tz_rules[1].m = tz_rules[1].n = tz_rules[1].d = 0;
-      tz_rules[0].secs = tz_rules[1].secs = 0;
-      tz_rules[0].offset = tz_rules[1].offset = 0L;
-      tz_rules[0].change = tz_rules[1].change = (time_t) -1;
-      tz_rules[0].computed_for = tz_rules[1].computed_for = 0;
-      return;
-    }
+/* Parse the POSIX TZ-style string.  */
+void
+__tzset_parse_tz (tz)
+     const char *tz;
+{
+  register size_t l;
+  char *tzbuf;
+  unsigned short int hh, mm, ss;
+  unsigned short int whichrule;
 
   /* Clear out old state and reset to unnamed UTC.  */
   memset (tz_rules, 0, sizeof tz_rules);
@@ -197,7 +176,7 @@ tzset_internal (always)
 
   if (sscanf (tz, "%[^0-9,+-]", tzbuf) != 1 ||
       (l = strlen (tzbuf)) < 3)
-    return;
+    goto out;
 
   tz_rules[0].name = __tzstring (tzbuf);
 
@@ -205,7 +184,7 @@ tzset_internal (always)
 
   /* Figure out the standard offset from UTC.  */
   if (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz)))
-    return;
+    goto out;
 
   if (*tz == '-' || *tz == '+')
     tz_rules[0].offset = *tz++ == '-' ? 1L : -1L;
@@ -214,7 +193,8 @@ tzset_internal (always)
   switch (sscanf (tz, "%hu:%hu:%hu", &hh, &mm, &ss))
     {
     default:
-      return;
+      tz_rules[0].offset = 0;
+      goto out;
     case 1:
       mm = 0;
     case 2:
@@ -223,7 +203,7 @@ tzset_internal (always)
       break;
     }
   tz_rules[0].offset *= (min (ss, 59) + (min (mm, 59) * 60) +
-                        (min (hh, 23) * 60 * 60));
+                        (min (hh, 24) * 60 * 60));
 
   for (l = 0; l < 3; ++l)
     {
@@ -386,48 +366,109 @@ tzset_internal (always)
     }
 
  out:
-  /* We know the offset now, set `__timezone'.  */
-  __timezone = -tz_rules[0].offset;
+  update_vars ();
 }
-\f
-/* Maximum length of a timezone name.  __tz_compute keeps this up to date
-   (never decreasing it) when ! __use_tzfile.
-   tzfile.c keeps it up to date when __use_tzfile.  */
-size_t __tzname_cur_max;
 
-long int
-__tzname_max ()
+/* Interpret the TZ envariable.  */
+static void
+internal_function
+tzset_internal (always, explicit)
+     int always;
+     int explicit;
 {
-  __libc_lock_lock (tzset_lock);
+  static int is_initialized;
+  register const char *tz;
 
-  tzset_internal (0);
+  if (is_initialized && !always)
+    return;
+  is_initialized = 1;
 
-  __libc_lock_unlock (tzset_lock);
+  /* Examine the TZ environment variable.  */
+  tz = getenv ("TZ");
+  if (tz == NULL && !explicit)
+    /* Use the site-wide default.  This is a file name which means we
+       would not see changes to the file if we compare only the file
+       name for change.  We want to notice file changes if tzset() has
+       been called explicitly.  Leave TZ as NULL in this case.  */
+    tz = TZDEFAULT;
+  if (tz && *tz == '\0')
+    /* User specified the empty string; use UTC explicitly.  */
+    tz = "Universal";
 
-  return __tzname_cur_max;
+  /* A leading colon means "implementation defined syntax".
+     We ignore the colon and always use the same algorithm:
+     try a data file, and if none exists parse the 1003.1 syntax.  */
+  if (tz && *tz == ':')
+    ++tz;
+
+  /* Check whether the value changed since the last run.  */
+  if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
+    /* No change, simply return.  */
+    return;
+
+  if (tz == NULL)
+    /* No user specification; use the site-wide default.  */
+    tz = TZDEFAULT;
+
+  tz_rules[0].name = NULL;
+  tz_rules[1].name = NULL;
+
+  /* Save the value of `tz'.  */
+  free (old_tz);
+  old_tz = tz ? __strdup (tz) : NULL;
+
+  /* Try to read a data file.  */
+  __tzfile_read (tz, 0, NULL);
+  if (__use_tzfile)
+    return;
+
+  /* No data file found.  Default to UTC if nothing specified.  */
+
+  if (tz == NULL || *tz == '\0'
+      || (TZDEFAULT != NULL && strcmp (tz, TZDEFAULT) == 0))
+    {
+      tz_rules[0].name = tz_rules[1].name = "UTC";
+      tz_rules[0].type = tz_rules[1].type = J0;
+      tz_rules[0].m = tz_rules[0].n = tz_rules[0].d = 0;
+      tz_rules[1].m = tz_rules[1].n = tz_rules[1].d = 0;
+      tz_rules[0].secs = tz_rules[1].secs = 0;
+      tz_rules[0].offset = tz_rules[1].offset = 0L;
+      tz_rules[0].change = tz_rules[1].change = (time_t) -1;
+      tz_rules[0].computed_for = tz_rules[1].computed_for = 0;
+      update_vars ();
+      return;
+    }
+
+  __tzset_parse_tz (tz);
 }
 \f
 /* Figure out the exact time (as a time_t) in YEAR
    when the change described by RULE will occur and
-   put it in RULE->change, saving YEAR in RULE->computed_for.
-   Return nonzero if successful, zero on failure.  */
-static int
+   put it in RULE->change, saving YEAR in RULE->computed_for.  */
+static void
 internal_function
 compute_change (rule, year)
      tz_rule *rule;
      int year;
 {
   register time_t t;
-  int y;
 
   if (year != -1 && rule->computed_for == year)
-    /* Operations on times in 1969 will be slower.  Oh well.  */
-    return 1;
+    /* Operations on times in 2 BC will be slower.  Oh well.  */
+    return;
 
   /* First set T to January 1st, 0:00:00 GMT in YEAR.  */
-  t = 0;
-  for (y = 1970; y < year; ++y)
-    t += SECSPERDAY * (__isleap (y) ? 366 : 365);
+  if (year > 1970)
+    t = ((year - 1970) * 365
+        + /* Compute the number of leapdays between 1970 and YEAR
+             (exclusive).  There is a leapday every 4th year ...  */
+        + ((year - 1) / 4 - 1970 / 4)
+        /* ... except every 100th year ... */
+        - ((year - 1) / 100 - 1970 / 100)
+        /* ... but still every 400th year.  */
+        + ((year - 1) / 400 - 1970 / 400)) * SECSPERDAY;
+  else
+    t = 0;
 
   switch (rule->type)
     {
@@ -490,45 +531,39 @@ compute_change (rule, year)
 
   rule->change = t - rule->offset + rule->secs;
   rule->computed_for = year;
-  return 1;
 }
 
 
 /* Figure out the correct timezone for TM and set `__tzname',
-   `__timezone', and `__daylight' accordingly.  Return nonzero on
-   success, zero on failure.  */
-static int
+   `__timezone', and `__daylight' accordingly.  */
+void
 internal_function
-tz_compute (tm)
-     const struct tm *tm;
+__tz_compute (timer, tm, use_localtime)
+     time_t timer;
+     struct tm *tm;
+     int use_localtime;
 {
-  if (! compute_change (&tz_rules[0], 1900 + tm->tm_year)
-      || ! compute_change (&tz_rules[1], 1900 + tm->tm_year))
-    return 0;
-  /* We have to distinguish between northern and southern hemisphere.
-     For the later the daylight saving time ends in the next year.
-     It is easier to detect this after first computing the time for the
-     wrong year since now we simply can compare the times to switch.  */
-  if (tz_rules[0].change > tz_rules[1].change
-      && ! compute_change (&tz_rules[1], 1900 + tm->tm_year + 1))
-    return 0;
-
-  __daylight = tz_rules[0].offset != tz_rules[1].offset;
-  __timezone = -tz_rules[0].offset;
-  __tzname[0] = (char *) tz_rules[0].name;
-  __tzname[1] = (char *) tz_rules[1].name;
+  compute_change (&tz_rules[0], 1900 + tm->tm_year);
+  compute_change (&tz_rules[1], 1900 + tm->tm_year);
 
-  {
-    /* Keep __tzname_cur_max up to date.  */
-    size_t len0 = strlen (__tzname[0]);
-    size_t len1 = strlen (__tzname[1]);
-    if (len0 > __tzname_cur_max)
-      __tzname_cur_max = len0;
-    if (len1 > __tzname_cur_max)
-      __tzname_cur_max = len1;
-  }
-
-  return 1;
+  if (use_localtime)
+    {
+      int isdst;
+
+      /* We have to distinguish between northern and southern
+        hemisphere.  For the latter the daylight saving time
+        ends in the next year.  */
+      if (__builtin_expect (tz_rules[0].change
+                           > tz_rules[1].change, 0))
+       isdst = (timer < tz_rules[1].change
+                || timer >= tz_rules[0].change);
+      else
+       isdst = (timer >= tz_rules[0].change
+                && timer < tz_rules[1].change);
+      tm->tm_isdst = isdst;
+      tm->tm_zone = __tzname[isdst];
+      tm->tm_gmtoff = tz_rules[isdst].offset;
+    }
 }
 \f
 /* Reinterpret the TZ environment variable and set `tzname'.  */
@@ -539,7 +574,7 @@ __tzset (void)
 {
   __libc_lock_lock (tzset_lock);
 
-  tzset_internal (1);
+  tzset_internal (1, 1);
 
   if (!__use_tzfile)
     {
@@ -570,38 +605,25 @@ __tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
 
   /* Update internal database according to current TZ setting.
      POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
-     This is a good idea since this allows at least a bit more parallelism.
-     By analogy we apply the same rule to gmtime_r.  */
-  tzset_internal (tp == &_tmbuf);
+     This is a good idea since this allows at least a bit more parallelism.  */
+  tzset_internal (tp == &_tmbuf && use_localtime, 1);
 
   if (__use_tzfile)
-    {
-      if (! __tzfile_compute (*timer, use_localtime,
-                             &leap_correction, &leap_extra_secs, tp))
-       tp = NULL;
-    }
+    __tzfile_compute (*timer, use_localtime, &leap_correction,
+                     &leap_extra_secs, tp);
   else
     {
-      if (! (__offtime (timer, 0, tp) && tz_compute (tp)))
+      if (! __offtime (timer, 0, tp))
        tp = NULL;
+      else
+       __tz_compute (*timer, tp, use_localtime);
       leap_correction = 0L;
       leap_extra_secs = 0;
     }
 
   if (tp)
     {
-      if (use_localtime)
-       {
-         if (!__use_tzfile)
-           {
-             int isdst = (*timer >= tz_rules[0].change
-                          && *timer < tz_rules[1].change);
-             tp->tm_isdst = isdst;
-             tp->tm_zone = __tzname[isdst];
-             tp->tm_gmtoff = tz_rules[isdst].offset;
-           }
-       }
-      else
+      if (! use_localtime)
        {
          tp->tm_isdst = 0;
          tp->tm_zone = "GMT";
@@ -618,3 +640,17 @@ __tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
 
   return tp;
 }
+
+
+libc_freeres_fn (free_mem)
+{
+  while (tzstring_list != NULL)
+    {
+      struct tzstring_l *old = tzstring_list;
+
+      tzstring_list = tzstring_list->next;
+      free (old);
+    }
+  free (old_tz);
+  old_tz = NULL;
+}