Back-patch fixes to work around broken mktime() in recent glibc releases.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 30 Sep 2002 20:57:11 +0000 (20:57 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 30 Sep 2002 20:57:11 +0000 (20:57 +0000)
src/backend/utils/adt/date.c
src/backend/utils/adt/datetime.c

index 24c603bd7bad5a704689be3f8d537758d1ee8a5d..03051dd4b1941d434ea9eb5160077900ac6d20ff 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.64.2.2 2002/08/22 05:27:41 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.64.2.3 2002/09/30 20:57:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -274,28 +274,20 @@ date_timestamptz(PG_FUNCTION_ARGS)
    TimestampTz result;
    struct tm   tt,
               *tm = &tt;
-   time_t      utime;
 
-   j2date((dateVal + date2j(2000, 1, 1)), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
+   j2date((dateVal + date2j(2000, 1, 1)),
+          &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
 
    if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
    {
-#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
+       int         tz;
+
        tm->tm_hour = 0;
        tm->tm_min = 0;
        tm->tm_sec = 0;
-       tm->tm_isdst = -1;
+       tz = DetermineLocalTimeZone(tm);
 
-       tm->tm_year -= 1900;
-       tm->tm_mon -= 1;
-       utime = mktime(tm);
-       if (utime == -1)
-           elog(ERROR, "Unable to convert date to tm");
-
-       result = utime + ((date2j(1970, 1, 1) - date2j(2000, 1, 1)) * 86400.0);
-#else
-       result = dateVal * 86400.0 + CTimeZone;
-#endif
+       result = dateVal * 86400.0 + tz;
    }
    else
    {
index 31e0111f333bfd2a33d8b9a1c208ec3e1f4f4d25..50c9ba8923c32fe5f76e02df11fc58e9739b9759 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.87.2.1 2002/02/25 16:22:48 thomas Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.87.2.2 2002/09/30 20:57:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1427,13 +1427,15 @@ DecodeDateTime(char **field, int *ftype, int nf,
 
 
 /* DetermineLocalTimeZone()
+ *
  * Given a struct tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
  * tm_sec fields are set, attempt to determine the applicable local zone
  * (ie, regular or daylight-savings time) at that time.  Set the struct tm's
  * tm_isdst field accordingly, and return the actual timezone offset.
  *
- * This subroutine exists mainly to centralize uses of mktime() and defend
- * against mktime() bugs on various platforms...
+ * This subroutine exists to centralize uses of mktime() and defend against
+ * mktime() bugs/restrictions on various platforms.  This should be
+ * the *only* call of mktime() in the backend.
  */
 int
 DetermineLocalTimeZone(struct tm * tm)
@@ -1441,7 +1443,10 @@ DetermineLocalTimeZone(struct tm * tm)
    int         tz;
 
    if (HasCTZSet)
+   {
+       tm->tm_isdst = 0;       /* for lack of a better idea */
        tz = CTimeZone;
+   }
    else if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
    {
 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
@@ -1463,20 +1468,90 @@ DetermineLocalTimeZone(struct tm * tm)
        /* indicate timezone unknown */
        tmp->tm_isdst = -1;
 
-       mktime(tmp);
-
-       tm->tm_isdst = tmp->tm_isdst;
+       if (mktime(tmp) != ((time_t) -1) &&
+           tmp->tm_isdst >= 0)
+       {
+           /* mktime() succeeded, trust its result */
+           tm->tm_isdst = tmp->tm_isdst;
 
 #if defined(HAVE_TM_ZONE)
-       /* tm_gmtoff is Sun/DEC-ism */
-       if (tmp->tm_isdst >= 0)
+           /* tm_gmtoff is Sun/DEC-ism */
            tz = -(tmp->tm_gmtoff);
-       else
-           tz = 0;             /* assume UTC if mktime failed */
 #elif defined(HAVE_INT_TIMEZONE)
-       tz = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
+           tz = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
 #endif   /* HAVE_INT_TIMEZONE */
-
+       }
+       else
+       {
+           /*
+            * We have a buggy (not to say deliberately brain damaged)
+            * mktime().  Work around it by using localtime() instead.
+            *
+            * First, generate the time_t value corresponding to the given
+            * y/m/d/h/m/s taken as GMT time.  This will not overflow (at
+            * least not for time_t taken as signed) because of the range
+            * check we did above.
+            */
+           long        day,
+                       mysec,
+                       locsec,
+                       delta1,
+                       delta2;
+           time_t      mytime;
+
+           day = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) -
+                  date2j(1970, 1, 1));
+           mysec = tm->tm_sec + (tm->tm_min + (day * 24 + tm->tm_hour) * 60) * 60;
+           mytime = (time_t) mysec;
+           /*
+            * Use localtime to convert that time_t to broken-down time, and
+            * reassemble to get a representation of local time.
+            */
+           tmp = localtime(&mytime);
+           day = (date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
+                  date2j(1970, 1, 1));
+           locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
+           /*
+            * The local time offset corresponding to that GMT time is
+            * now computable as mysec - locsec.
+            */
+           delta1 = mysec - locsec;
+           /*
+            * However, if that GMT time and the local time we are actually
+            * interested in are on opposite sides of a daylight-savings-time
+            * transition, then this is not the time offset we want.  So,
+            * adjust the time_t to be what we think the GMT time corresponding
+            * to our target local time is, and repeat the localtime() call
+            * and delta calculation.  We may have to do it twice before we
+            * have a trustworthy delta.
+            *
+            * Note: think not to put a loop here, since if we've been given
+            * an "impossible" local time (in the gap during a spring-forward
+            * transition) we'd never get out of the loop.  Twice is enough
+            * to give the behavior we want, which is that "impossible" times
+            * are taken as standard time, while at a fall-back boundary
+            * ambiguous times are also taken as standard.
+            */
+           mysec += delta1;
+           mytime = (time_t) mysec;
+           tmp = localtime(&mytime);
+           day = (date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
+                  date2j(1970, 1, 1));
+           locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
+           delta2 = mysec - locsec;
+           if (delta2 != delta1)
+           {
+               mysec += (delta2 - delta1);
+               mytime = (time_t) mysec;
+               tmp = localtime(&mytime);
+               day = (date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
+                      date2j(1970, 1, 1));
+               locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
+               delta2 = mysec - locsec;
+           }
+           tm->tm_isdst = tmp->tm_isdst;
+           tz = (int) delta2;
+       }
 #else                          /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
        tm->tm_isdst = 0;
        tz = CTimeZone;