aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/datetime.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/datetime.c')
-rw-r--r--src/backend/utils/adt/datetime.c140
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 */