Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5ba04cd

Browse files
committed
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.)
1 parent 7fad5ff commit 5ba04cd

File tree

3 files changed

+171
-80
lines changed

3 files changed

+171
-80
lines changed

src/backend/utils/adt/datetime.c

+65-75
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.134 2004/08/30 02:54:39 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.135 2004/11/01 21:34:38 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -1576,24 +1576,25 @@ DecodeDateTime(char **field, int *ftype, int nf,
15761576
* tm_isdst field accordingly, and return the actual timezone offset.
15771577
*
15781578
* Note: it might seem that we should use mktime() for this, but bitter
1579-
* experience teaches otherwise. In particular, mktime() is generally
1580-
* incapable of coping reasonably with "impossible" times within a
1581-
* spring-forward DST transition. Typical implementations of mktime()
1582-
* turn out to be loops around localtime() anyway, so they're not even
1583-
* any faster than this code.
1579+
* experience teaches otherwise. This code is much faster than most versions
1580+
* of mktime(), anyway.
15841581
*/
15851582
int
15861583
DetermineLocalTimeZone(struct pg_tm * tm)
15871584
{
1588-
int tz;
1589-
int date ,
1585+
int date,
15901586
sec;
15911587
pg_time_t day,
1592-
mysec,
1593-
locsec,
1594-
delta1,
1595-
delta2;
1596-
struct pg_tm *tx;
1588+
mytime,
1589+
prevtime,
1590+
boundary,
1591+
beforetime,
1592+
aftertime;
1593+
long int before_gmtoff,
1594+
after_gmtoff;
1595+
int before_isdst,
1596+
after_isdst;
1597+
int res;
15971598

15981599
if (HasCTZSet)
15991600
{
@@ -1615,82 +1616,71 @@ DetermineLocalTimeZone(struct pg_tm * tm)
16151616
if (day / 86400 != date)
16161617
goto overflow;
16171618
sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * 60) * 60;
1618-
mysec = day + sec;
1619-
/* since sec >= 0, overflow could only be from +day to -mysec */
1620-
if (mysec < 0 && day > 0)
1619+
mytime = day + sec;
1620+
/* since sec >= 0, overflow could only be from +day to -mytime */
1621+
if (mytime < 0 && day > 0)
16211622
goto overflow;
16221623

16231624
/*
1624-
* Use pg_localtime to convert that pg_time_t to broken-down time, and
1625-
* reassemble to get a representation of local time. (We could get
1626-
* overflow of a few hours in the result, but the delta calculation
1627-
* should still work.)
1625+
* Find the DST time boundary just before or following the target time.
1626+
* We assume that all zones have GMT offsets less than 24 hours, and
1627+
* that DST boundaries can't be closer together than 48 hours, so
1628+
* backing up 24 hours and finding the "next" boundary will work.
16281629
*/
1629-
tx = pg_localtime(&mysec);
1630-
if (!tx)
1631-
goto overflow; /* probably can't happen */
1632-
day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
1633-
UNIX_EPOCH_JDATE;
1634-
locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
1630+
prevtime = mytime - (24 * 60 * 60);
1631+
if (mytime < 0 && prevtime > 0)
1632+
goto overflow;
16351633

1636-
/*
1637-
* The local time offset corresponding to that GMT time is now
1638-
* computable as mysec - locsec.
1639-
*/
1640-
delta1 = mysec - locsec;
1634+
res = pg_next_dst_boundary(&prevtime,
1635+
&before_gmtoff, &before_isdst,
1636+
&boundary,
1637+
&after_gmtoff, &after_isdst);
1638+
if (res < 0)
1639+
goto overflow; /* failure? */
1640+
1641+
if (res == 0)
1642+
{
1643+
/* Non-DST zone, life is simple */
1644+
tm->tm_isdst = before_isdst;
1645+
return - (int) before_gmtoff;
1646+
}
16411647

16421648
/*
1643-
* However, if that GMT time and the local time we are actually
1644-
* interested in are on opposite sides of a daylight-savings-time
1645-
* transition, then this is not the time offset we want. So, adjust
1646-
* the pg_time_t to be what we think the GMT time corresponding to our
1647-
* target local time is, and repeat the pg_localtime() call and delta
1648-
* calculation.
1649-
*
1650-
* We have to watch out for overflow while adjusting the pg_time_t.
1649+
* Form the candidate pg_time_t values with local-time adjustment
16511650
*/
1652-
if ((delta1 < 0) ? (mysec < 0 && (mysec + delta1) > 0) :
1653-
(mysec > 0 && (mysec + delta1) < 0))
1651+
beforetime = mytime - before_gmtoff;
1652+
if ((before_gmtoff > 0) ? (mytime < 0 && beforetime > 0) :
1653+
(mytime > 0 && beforetime < 0))
1654+
goto overflow;
1655+
aftertime = mytime - after_gmtoff;
1656+
if ((after_gmtoff > 0) ? (mytime < 0 && aftertime > 0) :
1657+
(mytime > 0 && aftertime < 0))
16541658
goto overflow;
1655-
mysec += delta1;
1656-
tx = pg_localtime(&mysec);
1657-
if (!tx)
1658-
goto overflow; /* probably can't happen */
1659-
day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
1660-
UNIX_EPOCH_JDATE;
1661-
locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
1662-
delta2 = mysec - locsec;
16631659

16641660
/*
1665-
* We may have to do it again to get the correct delta.
1666-
*
1667-
* It might seem we should just loop until we get the same delta twice in
1668-
* a row, but if we've been given an "impossible" local time (in the
1669-
* gap during a spring-forward transition) we'd never get out of the
1670-
* loop. The behavior we want is that "impossible" times are taken as
1671-
* standard time, and also that ambiguous times (during a fall-back
1672-
* transition) are taken as standard time. Therefore, we bias the code
1673-
* to prefer the standard-time solution.
1661+
* If both before or both after the boundary time, we know what to do
16741662
*/
1675-
if (delta2 != delta1 && tx->tm_isdst != 0)
1663+
if (beforetime <= boundary && aftertime < boundary)
16761664
{
1677-
delta2 -= delta1;
1678-
if ((delta2 < 0) ? (mysec < 0 && (mysec + delta2) > 0) :
1679-
(mysec > 0 && (mysec + delta2) < 0))
1680-
goto overflow;
1681-
mysec += delta2;
1682-
tx = pg_localtime(&mysec);
1683-
if (!tx)
1684-
goto overflow; /* probably can't happen */
1685-
day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
1686-
UNIX_EPOCH_JDATE;
1687-
locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
1688-
delta2 = mysec - locsec;
1665+
tm->tm_isdst = before_isdst;
1666+
return - (int) before_gmtoff;
16891667
}
1690-
tm->tm_isdst = tx->tm_isdst;
1691-
tz = (int) delta2;
1692-
1693-
return tz;
1668+
if (beforetime > boundary && aftertime >= boundary)
1669+
{
1670+
tm->tm_isdst = after_isdst;
1671+
return - (int) after_gmtoff;
1672+
}
1673+
/*
1674+
* It's an invalid or ambiguous time due to timezone transition.
1675+
* Prefer the standard-time interpretation.
1676+
*/
1677+
if (after_isdst == 0)
1678+
{
1679+
tm->tm_isdst = after_isdst;
1680+
return - (int) after_gmtoff;
1681+
}
1682+
tm->tm_isdst = before_isdst;
1683+
return - (int) before_gmtoff;
16941684

16951685
overflow:
16961686
/* Given date is out of range, so assume UTC */

src/include/pgtime.h

+11-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
77
*
88
* IDENTIFICATION
9-
* $PostgreSQL: pgsql/src/include/pgtime.h,v 1.4 2004/08/29 05:06:55 momjian Exp $
9+
* $PostgreSQL: pgsql/src/include/pgtime.h,v 1.5 2004/11/01 21:34:41 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -37,12 +37,19 @@ struct pg_tm
3737
const char *tm_zone;
3838
};
3939

40-
extern struct pg_tm *pg_localtime(const pg_time_t *);
41-
extern struct pg_tm *pg_gmtime(const pg_time_t *);
42-
extern bool pg_tzset(const char *tzname);
40+
extern struct pg_tm *pg_localtime(const pg_time_t *timep);
41+
extern struct pg_tm *pg_gmtime(const pg_time_t *timep);
42+
extern int pg_next_dst_boundary(const pg_time_t *timep,
43+
long int *before_gmtoff,
44+
int *before_isdst,
45+
pg_time_t *boundary,
46+
long int *after_gmtoff,
47+
int *after_isdst);
4348
extern size_t pg_strftime(char *s, size_t max, const char *format,
4449
const struct pg_tm * tm);
50+
4551
extern void pg_timezone_initialize(void);
52+
extern bool pg_tzset(const char *tzname);
4653
extern bool tz_acceptable(void);
4754
extern const char *select_default_timezone(void);
4855
extern const char *pg_get_current_timezone(void);

src/timezone/localtime.c

+95-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
44
*
55
* IDENTIFICATION
6-
* $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.8 2004/08/29 05:07:02 momjian Exp $
6+
* $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.9 2004/11/01 21:34:44 tgl Exp $
77
*/
88

99
/*
@@ -1059,6 +1059,100 @@ timesub(const pg_time_t *timep, const long offset,
10591059
tmp->tm_gmtoff = offset;
10601060
}
10611061

1062+
/*
1063+
* Find the next DST transition time at or after the given time
1064+
*
1065+
* *timep is the input value, the other parameters are output values.
1066+
*
1067+
* When the function result is 1, *boundary is set to the time_t
1068+
* representation of the next DST transition time at or after *timep,
1069+
* *before_gmtoff and *before_isdst are set to the GMT offset and isdst
1070+
* state prevailing just before that boundary, and *after_gmtoff and
1071+
* *after_isdst are set to the state prevailing just after that boundary.
1072+
*
1073+
* When the function result is 0, there is no known DST transition at or
1074+
* after *timep, but *before_gmtoff and *before_isdst indicate the GMT
1075+
* offset and isdst state prevailing at *timep. (This would occur in
1076+
* DST-less time zones, for example.)
1077+
*
1078+
* A function result of -1 indicates failure (this case does not actually
1079+
* occur in our current implementation).
1080+
*/
1081+
int
1082+
pg_next_dst_boundary(const pg_time_t *timep,
1083+
long int *before_gmtoff,
1084+
int *before_isdst,
1085+
pg_time_t *boundary,
1086+
long int *after_gmtoff,
1087+
int *after_isdst)
1088+
{
1089+
register struct state *sp;
1090+
register const struct ttinfo *ttisp;
1091+
int i;
1092+
int j;
1093+
const pg_time_t t = *timep;
1094+
1095+
sp = lclptr;
1096+
if (sp->timecnt == 0)
1097+
{
1098+
/* non-DST zone, use lowest-numbered standard type */
1099+
i = 0;
1100+
while (sp->ttis[i].tt_isdst)
1101+
if (++i >= sp->typecnt)
1102+
{
1103+
i = 0;
1104+
break;
1105+
}
1106+
ttisp = &sp->ttis[i];
1107+
*before_gmtoff = ttisp->tt_gmtoff;
1108+
*before_isdst = ttisp->tt_isdst;
1109+
return 0;
1110+
}
1111+
if (t > sp->ats[sp->timecnt - 1])
1112+
{
1113+
/* No known transition >= t, so use last known segment's type */
1114+
i = sp->types[sp->timecnt - 1];
1115+
ttisp = &sp->ttis[i];
1116+
*before_gmtoff = ttisp->tt_gmtoff;
1117+
*before_isdst = ttisp->tt_isdst;
1118+
return 0;
1119+
}
1120+
if (t <= sp->ats[0])
1121+
{
1122+
/* For "before", use lowest-numbered standard type */
1123+
i = 0;
1124+
while (sp->ttis[i].tt_isdst)
1125+
if (++i >= sp->typecnt)
1126+
{
1127+
i = 0;
1128+
break;
1129+
}
1130+
ttisp = &sp->ttis[i];
1131+
*before_gmtoff = ttisp->tt_gmtoff;
1132+
*before_isdst = ttisp->tt_isdst;
1133+
*boundary = sp->ats[0];
1134+
/* And for "after", use the first segment's type */
1135+
i = sp->types[0];
1136+
ttisp = &sp->ttis[i];
1137+
*after_gmtoff = ttisp->tt_gmtoff;
1138+
*after_isdst = ttisp->tt_isdst;
1139+
return 1;
1140+
}
1141+
/* Else search to find the containing segment */
1142+
for (i = 1; i < sp->timecnt; ++i)
1143+
if (t <= sp->ats[i])
1144+
break;
1145+
j = sp->types[i - 1];
1146+
ttisp = &sp->ttis[j];
1147+
*before_gmtoff = ttisp->tt_gmtoff;
1148+
*before_isdst = ttisp->tt_isdst;
1149+
*boundary = sp->ats[i];
1150+
j = sp->types[i];
1151+
ttisp = &sp->ttis[j];
1152+
*after_gmtoff = ttisp->tt_gmtoff;
1153+
*after_isdst = ttisp->tt_isdst;
1154+
return 1;
1155+
}
10621156

10631157
/*
10641158
* Return the name of the current timezone

0 commit comments

Comments
 (0)