diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2004-11-01 21:34:44 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2004-11-01 21:34:44 +0000 |
commit | 5ba04cd9f17ac91cce7460310a2a6cbd714b7e8a (patch) | |
tree | 0259ae8820694dd5b8cf1ef1bb9ce8e34761076b /src/backend/utils/adt/datetime.c | |
parent | 7fad5fff0df0476ca80716153963d90c5776acf3 (diff) | |
download | postgresql-5ba04cd9f17ac91cce7460310a2a6cbd714b7e8a.tar.gz postgresql-5ba04cd9f17ac91cce7460310a2a6cbd714b7e8a.zip |
Invent pg_next_dst_boundary() and rewrite DetermineLocalTimeZone() to
use it, as per my proposal of yesterday. This gives us a means of
determining the zone offset to impute to an unlabeled timestamp that
is both efficient and reliable, unlike all our previous tries involving
mktime() and localtime(). The behavior for invalid or ambiguous times
at a DST transition is fixed to be really and truly "assume standard
time", fixing a bug that has come and gone repeatedly but was back
again in 7.4. (There is some ongoing discussion about whether we should
raise an error instead, but for the moment I'll make it do what it was
previously intended to do.)
Diffstat (limited to 'src/backend/utils/adt/datetime.c')
-rw-r--r-- | src/backend/utils/adt/datetime.c | 140 |
1 files changed, 65 insertions, 75 deletions
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 4459d286c96..e47edb35b99 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.134 2004/08/30 02:54:39 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.135 2004/11/01 21:34:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1576,24 +1576,25 @@ DecodeDateTime(char **field, int *ftype, int nf, * tm_isdst field accordingly, and return the actual timezone offset. * * Note: it might seem that we should use mktime() for this, but bitter - * experience teaches otherwise. In particular, mktime() is generally - * incapable of coping reasonably with "impossible" times within a - * spring-forward DST transition. Typical implementations of mktime() - * turn out to be loops around localtime() anyway, so they're not even - * any faster than this code. + * experience teaches otherwise. This code is much faster than most versions + * of mktime(), anyway. */ int DetermineLocalTimeZone(struct pg_tm * tm) { - int tz; - int date , + int date, sec; pg_time_t day, - mysec, - locsec, - delta1, - delta2; - struct pg_tm *tx; + mytime, + prevtime, + boundary, + beforetime, + aftertime; + long int before_gmtoff, + after_gmtoff; + int before_isdst, + after_isdst; + int res; if (HasCTZSet) { @@ -1615,82 +1616,71 @@ DetermineLocalTimeZone(struct pg_tm * tm) if (day / 86400 != date) goto overflow; sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * 60) * 60; - mysec = day + sec; - /* since sec >= 0, overflow could only be from +day to -mysec */ - if (mysec < 0 && day > 0) + mytime = day + sec; + /* since sec >= 0, overflow could only be from +day to -mytime */ + if (mytime < 0 && day > 0) goto overflow; /* - * Use pg_localtime to convert that pg_time_t to broken-down time, and - * reassemble to get a representation of local time. (We could get - * overflow of a few hours in the result, but the delta calculation - * should still work.) + * Find the DST time boundary just before or following the target time. + * We assume that all zones have GMT offsets less than 24 hours, and + * that DST boundaries can't be closer together than 48 hours, so + * backing up 24 hours and finding the "next" boundary will work. */ - tx = pg_localtime(&mysec); - if (!tx) - goto overflow; /* probably can't happen */ - day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) - - UNIX_EPOCH_JDATE; - locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60; + prevtime = mytime - (24 * 60 * 60); + if (mytime < 0 && prevtime > 0) + goto overflow; - /* - * The local time offset corresponding to that GMT time is now - * computable as mysec - locsec. - */ - delta1 = mysec - locsec; + res = pg_next_dst_boundary(&prevtime, + &before_gmtoff, &before_isdst, + &boundary, + &after_gmtoff, &after_isdst); + if (res < 0) + goto overflow; /* failure? */ + + if (res == 0) + { + /* Non-DST zone, life is simple */ + tm->tm_isdst = before_isdst; + return - (int) before_gmtoff; + } /* - * 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 pg_time_t to be what we think the GMT time corresponding to our - * target local time is, and repeat the pg_localtime() call and delta - * calculation. - * - * We have to watch out for overflow while adjusting the pg_time_t. + * Form the candidate pg_time_t values with local-time adjustment */ - if ((delta1 < 0) ? (mysec < 0 && (mysec + delta1) > 0) : - (mysec > 0 && (mysec + delta1) < 0)) + beforetime = mytime - before_gmtoff; + if ((before_gmtoff > 0) ? (mytime < 0 && beforetime > 0) : + (mytime > 0 && beforetime < 0)) + goto overflow; + aftertime = mytime - after_gmtoff; + if ((after_gmtoff > 0) ? (mytime < 0 && aftertime > 0) : + (mytime > 0 && aftertime < 0)) goto overflow; - mysec += delta1; - tx = pg_localtime(&mysec); - if (!tx) - goto overflow; /* probably can't happen */ - day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) - - UNIX_EPOCH_JDATE; - locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60; - delta2 = mysec - locsec; /* - * 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 both before or both after the boundary time, we know what to do */ - if (delta2 != delta1 && tx->tm_isdst != 0) + if (beforetime <= boundary && aftertime < boundary) { - delta2 -= delta1; - if ((delta2 < 0) ? (mysec < 0 && (mysec + delta2) > 0) : - (mysec > 0 && (mysec + delta2) < 0)) - goto overflow; - mysec += delta2; - tx = pg_localtime(&mysec); - if (!tx) - goto overflow; /* probably can't happen */ - day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) - - UNIX_EPOCH_JDATE; - locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60; - delta2 = mysec - locsec; + tm->tm_isdst = before_isdst; + return - (int) before_gmtoff; } - tm->tm_isdst = tx->tm_isdst; - tz = (int) delta2; - - return tz; + if (beforetime > boundary && aftertime >= boundary) + { + tm->tm_isdst = after_isdst; + return - (int) after_gmtoff; + } + /* + * It's an invalid or ambiguous time due to timezone transition. + * Prefer the standard-time interpretation. + */ + if (after_isdst == 0) + { + tm->tm_isdst = after_isdst; + return - (int) after_gmtoff; + } + tm->tm_isdst = before_isdst; + return - (int) before_gmtoff; overflow: /* Given date is out of range, so assume UTC */ |