From 86f27321e25b5a1a7b6c78400e54f063525b07d5 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 3 Sep 2002 19:46:32 +0000 Subject: [PATCH] Work around mktime() brain damage in recent versions of glibc by using a series of localtime() calls to determine the local timezone offset when mktime() fails. This eliminates regression failures on RHL 7.3, and should continue to work until it occurs to the glibc boys to break localtime() as well. By then I hope we'll have our own timezone code... --- src/backend/utils/adt/datetime.c | 99 +++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 12 deletions(-) diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 9392c3efb9..40d1dbc74c 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.94 2002/09/02 02:47:04 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.95 2002/09/03 19:46:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1446,13 +1446,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) @@ -1460,7 +1462,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) @@ -1482,20 +1487,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; -- 2.11.0