Commit 94a13b8a authored by Tom Lane's avatar Tom Lane

Okay, I've had it with mktime() bugs. While chasing Torello Querci's

recent gripe, I discovered not one but two undocumented, undesirable
behaviors of glibc's mktime.  So, stop using it entirely, and always
rely on inversion of localtime() to determine the local time zone.
It's not even very much slower, as it turns out that mktime (at least
in the glibc implementation) also does repeated reverse-conversions.
parent f5175906
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.116 2003/08/27 23:29:28 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.117 2003/09/13 21:12:38 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1570,9 +1570,10 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -1570,9 +1570,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
* (ie, regular or daylight-savings time) at that time. Set the struct tm's * (ie, regular or daylight-savings time) at that time. Set the struct tm's
* tm_isdst field accordingly, and return the actual timezone offset. * tm_isdst field accordingly, and return the actual timezone offset.
* *
* This subroutine exists to centralize uses of mktime() and defend against * Note: this subroutine exists because mktime() has such a spectacular
* mktime() bugs/restrictions on various platforms. This should be * variety of, ahem, odd behaviors on various platforms. We used to try to
* the *only* call of mktime() in the backend. * use mktime() here, but finally gave it up as a bad job. Avoid using
* mktime() anywhere else.
*/ */
int int
DetermineLocalTimeZone(struct tm * tm) DetermineLocalTimeZone(struct tm * tm)
...@@ -1586,44 +1587,7 @@ DetermineLocalTimeZone(struct tm * tm) ...@@ -1586,44 +1587,7 @@ DetermineLocalTimeZone(struct tm * tm)
} }
else if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday)) else if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
{ {
#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
/* /*
* Some buggy mktime() implementations may change the
* year/month/day when given a time right at a DST boundary. To
* prevent corruption of the caller's data, give mktime() a
* copy...
*/
struct tm tt,
*tmp = &tt;
*tmp = *tm;
/* change to Unix conventions for year/month */
tmp->tm_year -= 1900;
tmp->tm_mon -= 1;
/* indicate timezone unknown */
tmp->tm_isdst = -1;
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 */
tz = -(tmp->tm_gmtoff);
#elif defined(HAVE_INT_TIMEZONE)
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 * 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 * 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 * least not for time_t taken as signed) because of the range
...@@ -1635,6 +1599,7 @@ DetermineLocalTimeZone(struct tm * tm) ...@@ -1635,6 +1599,7 @@ DetermineLocalTimeZone(struct tm * tm)
delta1, delta1,
delta2; delta2;
time_t mytime; time_t mytime;
struct tm *tx;
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE; day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE;
mysec = tm->tm_sec + (tm->tm_min + (day * 24 + tm->tm_hour) * 60) * 60; mysec = tm->tm_sec + (tm->tm_min + (day * 24 + tm->tm_hour) * 60) * 60;
...@@ -1644,10 +1609,10 @@ DetermineLocalTimeZone(struct tm * tm) ...@@ -1644,10 +1609,10 @@ DetermineLocalTimeZone(struct tm * tm)
* Use localtime to convert that time_t to broken-down time, * Use localtime to convert that time_t to broken-down time,
* and reassemble to get a representation of local time. * and reassemble to get a representation of local time.
*/ */
tmp = localtime(&mytime); tx = localtime(&mytime);
day = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) - day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
UNIX_EPOCH_JDATE; UNIX_EPOCH_JDATE;
locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60; locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
/* /*
* The local time offset corresponding to that GMT time is now * The local time offset corresponding to that GMT time is now
...@@ -1661,43 +1626,40 @@ DetermineLocalTimeZone(struct tm * tm) ...@@ -1661,43 +1626,40 @@ DetermineLocalTimeZone(struct tm * tm)
* daylight-savings-time transition, then this is not the time * daylight-savings-time transition, then this is not the time
* offset we want. So, adjust the time_t to be what we think * offset we want. So, adjust the time_t to be what we think
* the GMT time corresponding to our target local time is, and * the GMT time corresponding to our target local time is, and
* repeat the localtime() call and delta calculation. We may * repeat the localtime() call and delta calculation.
* 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; mysec += delta1;
mytime = (time_t) mysec; mytime = (time_t) mysec;
tmp = localtime(&mytime); tx = localtime(&mytime);
day = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) - day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
UNIX_EPOCH_JDATE; UNIX_EPOCH_JDATE;
locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60; locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
delta2 = mysec - locsec; delta2 = mysec - locsec;
if (delta2 != delta1)
/*
* We may have to do it again to get the correct delta.
*
* It might seem we should just loop until we get the same delta
* twice in a row, but 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. The behavior we want is that "impossible"
* times are taken as standard time, and also that ambiguous times
* (during a fall-back transition) are taken as standard time.
* Therefore, we bias the code to prefer the standard-time solution.
*/
if (delta2 != delta1 && tx->tm_isdst != 0)
{ {
mysec += (delta2 - delta1); mysec += (delta2 - delta1);
mytime = (time_t) mysec; mytime = (time_t) mysec;
tmp = localtime(&mytime); tx = localtime(&mytime);
day = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) - day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
UNIX_EPOCH_JDATE; UNIX_EPOCH_JDATE;
locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60; locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
delta2 = mysec - locsec; delta2 = mysec - locsec;
} }
tm->tm_isdst = tmp->tm_isdst; tm->tm_isdst = tx->tm_isdst;
tz = (int) delta2; tz = (int) delta2;
} }
#else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
/* Assume UTC if no system timezone info available */
tm->tm_isdst = 0;
tz = 0;
#endif
}
else else
{ {
/* Given date is out of range, so assume UTC */ /* Given date is out of range, so assume UTC */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment