diff options
-rw-r--r-- | src/backend/utils/adt/datetime.c | 140 | ||||
-rw-r--r-- | src/include/pgtime.h | 15 | ||||
-rw-r--r-- | src/timezone/localtime.c | 96 |
3 files changed, 171 insertions, 80 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 */ diff --git a/src/include/pgtime.h b/src/include/pgtime.h index 1c66a63d2fe..8a1370cc8dd 100644 --- a/src/include/pgtime.h +++ b/src/include/pgtime.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/include/pgtime.h,v 1.4 2004/08/29 05:06:55 momjian Exp $ + * $PostgreSQL: pgsql/src/include/pgtime.h,v 1.5 2004/11/01 21:34:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,12 +37,19 @@ struct pg_tm const char *tm_zone; }; -extern struct pg_tm *pg_localtime(const pg_time_t *); -extern struct pg_tm *pg_gmtime(const pg_time_t *); -extern bool pg_tzset(const char *tzname); +extern struct pg_tm *pg_localtime(const pg_time_t *timep); +extern struct pg_tm *pg_gmtime(const pg_time_t *timep); +extern int pg_next_dst_boundary(const pg_time_t *timep, + long int *before_gmtoff, + int *before_isdst, + pg_time_t *boundary, + long int *after_gmtoff, + int *after_isdst); extern size_t pg_strftime(char *s, size_t max, const char *format, const struct pg_tm * tm); + extern void pg_timezone_initialize(void); +extern bool pg_tzset(const char *tzname); extern bool tz_acceptable(void); extern const char *select_default_timezone(void); extern const char *pg_get_current_timezone(void); diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c index 5cd0b41ed29..8e64f065b45 100644 --- a/src/timezone/localtime.c +++ b/src/timezone/localtime.c @@ -3,7 +3,7 @@ * 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). * * IDENTIFICATION - * $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.8 2004/08/29 05:07:02 momjian Exp $ + * $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.9 2004/11/01 21:34:44 tgl Exp $ */ /* @@ -1059,6 +1059,100 @@ timesub(const pg_time_t *timep, const long offset, tmp->tm_gmtoff = offset; } +/* + * Find the next DST transition time at or after the given time + * + * *timep is the input value, the other parameters are output values. + * + * When the function result is 1, *boundary is set to the time_t + * representation of the next DST transition time at or after *timep, + * *before_gmtoff and *before_isdst are set to the GMT offset and isdst + * state prevailing just before that boundary, and *after_gmtoff and + * *after_isdst are set to the state prevailing just after that boundary. + * + * When the function result is 0, there is no known DST transition at or + * after *timep, but *before_gmtoff and *before_isdst indicate the GMT + * offset and isdst state prevailing at *timep. (This would occur in + * DST-less time zones, for example.) + * + * A function result of -1 indicates failure (this case does not actually + * occur in our current implementation). + */ +int +pg_next_dst_boundary(const pg_time_t *timep, + long int *before_gmtoff, + int *before_isdst, + pg_time_t *boundary, + long int *after_gmtoff, + int *after_isdst) +{ + register struct state *sp; + register const struct ttinfo *ttisp; + int i; + int j; + const pg_time_t t = *timep; + + sp = lclptr; + if (sp->timecnt == 0) + { + /* non-DST zone, use lowest-numbered standard type */ + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) + { + i = 0; + break; + } + ttisp = &sp->ttis[i]; + *before_gmtoff = ttisp->tt_gmtoff; + *before_isdst = ttisp->tt_isdst; + return 0; + } + if (t > sp->ats[sp->timecnt - 1]) + { + /* No known transition >= t, so use last known segment's type */ + i = sp->types[sp->timecnt - 1]; + ttisp = &sp->ttis[i]; + *before_gmtoff = ttisp->tt_gmtoff; + *before_isdst = ttisp->tt_isdst; + return 0; + } + if (t <= sp->ats[0]) + { + /* For "before", use lowest-numbered standard type */ + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) + { + i = 0; + break; + } + ttisp = &sp->ttis[i]; + *before_gmtoff = ttisp->tt_gmtoff; + *before_isdst = ttisp->tt_isdst; + *boundary = sp->ats[0]; + /* And for "after", use the first segment's type */ + i = sp->types[0]; + ttisp = &sp->ttis[i]; + *after_gmtoff = ttisp->tt_gmtoff; + *after_isdst = ttisp->tt_isdst; + return 1; + } + /* Else search to find the containing segment */ + for (i = 1; i < sp->timecnt; ++i) + if (t <= sp->ats[i]) + break; + j = sp->types[i - 1]; + ttisp = &sp->ttis[j]; + *before_gmtoff = ttisp->tt_gmtoff; + *before_isdst = ttisp->tt_isdst; + *boundary = sp->ats[i]; + j = sp->types[i]; + ttisp = &sp->ttis[j]; + *after_gmtoff = ttisp->tt_gmtoff; + *after_isdst = ttisp->tt_isdst; + return 1; +} /* * Return the name of the current timezone |