*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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)
{
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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 $
*/
/*
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