/* * 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, const pg_tz *tz) { const struct state *sp; const struct ttinfo *ttisp; int i; int j; const pg_time_t t = *timep; sp = &tz->state; 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 ((sp->goback && t < sp->ats[0]) || (sp->goahead && t > sp->ats[sp->timecnt - 1])) { /* For values outside the transition table, extrapolate */ pg_time_t newt = t; pg_time_t seconds; pg_time_t tcycles; int64 icycles; int result; if (t < sp->ats[0]) seconds = sp->ats[0] - t; else seconds = t - sp->ats[sp->timecnt - 1]; --seconds; tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR; ++tcycles; icycles = tcycles; if (tcycles - icycles >= 1 || icycles - tcycles >= 1) return -1; seconds = icycles; seconds *= YEARSPERREPEAT; seconds *= AVGSECSPERYEAR; if (t < sp->ats[0]) newt += seconds; else newt -= seconds; if (newt < sp->ats[0] || newt > sp->ats[sp->timecnt - 1]) return -1; /* "cannot happen" */ result = pg_next_dst_boundary(&newt, before_gmtoff, before_isdst, boundary, after_gmtoff, after_isdst, tz); if (t < sp->ats[0]) *boundary -= seconds; else *boundary += seconds; return result; } 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 */ { int lo = 1; int hi = sp->timecnt; while (lo < hi) { int mid = (lo + hi) >> 1; if (t < sp->ats[mid]) hi = mid; else lo = mid + 1; } i = lo; } 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; }
/* DetermineTimeZoneOffset() * * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and * tm_sec fields are set, attempt to determine the applicable time zone * (ie, regular or daylight-savings time) at that time. Set the struct pg_tm's * 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. This code is much faster than most versions * of mktime(), anyway. */ int DetermineTimeZoneOffset(struct tm * tm, pg_tz *tzp) { int date, sec; pg_time_t day, mytime, prevtime, boundary, beforetime, aftertime; long int before_gmtoff, after_gmtoff; int before_isdst, after_isdst; int res; if (tzp == session_timezone && HasCTZSet) { tm->tm_isdst = 0; /* for lack of a better idea */ return CTimeZone; } /* * First, generate the pg_time_t value corresponding to the given * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the * timezone is GMT. (We only need to worry about overflow on machines * where pg_time_t is 32 bits.) */ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) goto overflow; date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE; day = ((pg_time_t) date) * SECS_PER_DAY; if (day / SECS_PER_DAY != date) goto overflow; sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE; mytime = day + sec; /* since sec >= 0, overflow could only be from +day to -mytime */ if (mytime < 0 && day > 0) goto overflow; /* * 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. */ prevtime = mytime - SECS_PER_DAY; if (mytime < 0 && prevtime > 0) goto overflow; res = pg_next_dst_boundary(&prevtime, &before_gmtoff, &before_isdst, &boundary, &after_gmtoff, &after_isdst, tzp); if (res < 0) goto overflow; /* failure? */ if (res == 0) { /* Non-DST zone, life is simple */ tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } /* * Form the candidate pg_time_t values with local-time adjustment */ beforetime = mytime - before_gmtoff; if ((before_gmtoff > 0 && mytime < 0 && beforetime > 0) || (before_gmtoff <= 0 && mytime > 0 && beforetime < 0)) goto overflow; aftertime = mytime - after_gmtoff; if ((after_gmtoff > 0 && mytime < 0 && aftertime > 0) || (after_gmtoff <= 0 && mytime > 0 && aftertime < 0)) goto overflow; /* * If both before or both after the boundary time, we know what to do */ if (beforetime <= boundary && aftertime < boundary) { tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } 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 */ tm->tm_isdst = 0; return 0; }