Skip to content

Commit 94a13b8

Browse files
committed
Okay, I've had it with mktime() bugs. While chasing Torello Querci's
recent gripe, I discovered not one but two undocumented, undesirable behaviors of glibc's mktime. So, stop using it entirely, and always rely on inversion of localtime() to determine the local time zone. It's not even very much slower, as it turns out that mktime (at least in the glibc implementation) also does repeated reverse-conversions.
1 parent f517590 commit 94a13b8

File tree

1 file changed

+66
-104
lines changed

1 file changed

+66
-104
lines changed

src/backend/utils/adt/datetime.c

Lines changed: 66 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.116 2003/08/27 23:29:28 tgl Exp $
11+
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.117 2003/09/13 21:12:38 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -1570,9 +1570,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
15701570
* (ie, regular or daylight-savings time) at that time. Set the struct tm's
15711571
* tm_isdst field accordingly, and return the actual timezone offset.
15721572
*
1573-
* This subroutine exists to centralize uses of mktime() and defend against
1574-
* mktime() bugs/restrictions on various platforms. This should be
1575-
* the *only* call of mktime() in the backend.
1573+
* Note: this subroutine exists because mktime() has such a spectacular
1574+
* variety of, ahem, odd behaviors on various platforms. We used to try to
1575+
* use mktime() here, but finally gave it up as a bad job. Avoid using
1576+
* mktime() anywhere else.
15761577
*/
15771578
int
15781579
DetermineLocalTimeZone(struct tm * tm)
@@ -1586,117 +1587,78 @@ DetermineLocalTimeZone(struct tm * tm)
15861587
}
15871588
else if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
15881589
{
1589-
#if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
1590+
/*
1591+
* First, generate the time_t value corresponding to the given
1592+
* y/m/d/h/m/s taken as GMT time. This will not overflow (at
1593+
* least not for time_t taken as signed) because of the range
1594+
* check we did above.
1595+
*/
1596+
long day,
1597+
mysec,
1598+
locsec,
1599+
delta1,
1600+
delta2;
1601+
time_t mytime;
1602+
struct tm *tx;
1603+
1604+
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE;
1605+
mysec = tm->tm_sec + (tm->tm_min + (day * 24 + tm->tm_hour) * 60) * 60;
1606+
mytime = (time_t) mysec;
15901607

15911608
/*
1592-
* Some buggy mktime() implementations may change the
1593-
* year/month/day when given a time right at a DST boundary. To
1594-
* prevent corruption of the caller's data, give mktime() a
1595-
* copy...
1609+
* Use localtime to convert that time_t to broken-down time,
1610+
* and reassemble to get a representation of local time.
15961611
*/
1597-
struct tm tt,
1598-
*tmp = &tt;
1612+
tx = localtime(&mytime);
1613+
day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
1614+
UNIX_EPOCH_JDATE;
1615+
locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
15991616

1600-
*tmp = *tm;
1601-
/* change to Unix conventions for year/month */
1602-
tmp->tm_year -= 1900;
1603-
tmp->tm_mon -= 1;
1617+
/*
1618+
* The local time offset corresponding to that GMT time is now
1619+
* computable as mysec - locsec.
1620+
*/
1621+
delta1 = mysec - locsec;
16041622

1605-
/* indicate timezone unknown */
1606-
tmp->tm_isdst = -1;
1623+
/*
1624+
* However, if that GMT time and the local time we are
1625+
* actually interested in are on opposite sides of a
1626+
* daylight-savings-time transition, then this is not the time
1627+
* offset we want. So, adjust the time_t to be what we think
1628+
* the GMT time corresponding to our target local time is, and
1629+
* repeat the localtime() call and delta calculation.
1630+
*/
1631+
mysec += delta1;
1632+
mytime = (time_t) mysec;
1633+
tx = localtime(&mytime);
1634+
day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
1635+
UNIX_EPOCH_JDATE;
1636+
locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
1637+
delta2 = mysec - locsec;
16071638

1608-
if (mktime(tmp) != ((time_t) -1) &&
1609-
tmp->tm_isdst >= 0)
1610-
{
1611-
/* mktime() succeeded, trust its result */
1612-
tm->tm_isdst = tmp->tm_isdst;
1613-
1614-
#if defined(HAVE_TM_ZONE)
1615-
/* tm_gmtoff is Sun/DEC-ism */
1616-
tz = -(tmp->tm_gmtoff);
1617-
#elif defined(HAVE_INT_TIMEZONE)
1618-
tz = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
1619-
#endif /* HAVE_INT_TIMEZONE */
1620-
}
1621-
else
1639+
/*
1640+
* We may have to do it again to get the correct delta.
1641+
*
1642+
* It might seem we should just loop until we get the same delta
1643+
* twice in a row, but if we've been given an "impossible" local
1644+
* time (in the gap during a spring-forward transition) we'd never
1645+
* get out of the loop. The behavior we want is that "impossible"
1646+
* times are taken as standard time, and also that ambiguous times
1647+
* (during a fall-back transition) are taken as standard time.
1648+
* Therefore, we bias the code to prefer the standard-time solution.
1649+
*/
1650+
if (delta2 != delta1 && tx->tm_isdst != 0)
16221651
{
1623-
/*
1624-
* We have a buggy (not to say deliberately brain damaged)
1625-
* mktime(). Work around it by using localtime() instead.
1626-
*
1627-
* First, generate the time_t value corresponding to the given
1628-
* y/m/d/h/m/s taken as GMT time. This will not overflow (at
1629-
* least not for time_t taken as signed) because of the range
1630-
* check we did above.
1631-
*/
1632-
long day,
1633-
mysec,
1634-
locsec,
1635-
delta1,
1636-
delta2;
1637-
time_t mytime;
1638-
1639-
day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE;
1640-
mysec = tm->tm_sec + (tm->tm_min + (day * 24 + tm->tm_hour) * 60) * 60;
1652+
mysec += (delta2 - delta1);
16411653
mytime = (time_t) mysec;
1642-
1643-
/*
1644-
* Use localtime to convert that time_t to broken-down time,
1645-
* and reassemble to get a representation of local time.
1646-
*/
1647-
tmp = localtime(&mytime);
1648-
day = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
1649-
UNIX_EPOCH_JDATE;
1650-
locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
1651-
1652-
/*
1653-
* The local time offset corresponding to that GMT time is now
1654-
* computable as mysec - locsec.
1655-
*/
1656-
delta1 = mysec - locsec;
1657-
1658-
/*
1659-
* However, if that GMT time and the local time we are
1660-
* actually interested in are on opposite sides of a
1661-
* daylight-savings-time transition, then this is not the time
1662-
* offset we want. So, adjust the time_t to be what we think
1663-
* the GMT time corresponding to our target local time is, and
1664-
* repeat the localtime() call and delta calculation. We may
1665-
* have to do it twice before we have a trustworthy delta.
1666-
*
1667-
* Note: think not to put a loop here, since if we've been given
1668-
* an "impossible" local time (in the gap during a
1669-
* spring-forward transition) we'd never get out of the loop.
1670-
* Twice is enough to give the behavior we want, which is that
1671-
* "impossible" times are taken as standard time, while at a
1672-
* fall-back boundary ambiguous times are also taken as
1673-
* standard.
1674-
*/
1675-
mysec += delta1;
1676-
mytime = (time_t) mysec;
1677-
tmp = localtime(&mytime);
1678-
day = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
1654+
tx = localtime(&mytime);
1655+
day = date2j(tx->tm_year + 1900, tx->tm_mon + 1, tx->tm_mday) -
16791656
UNIX_EPOCH_JDATE;
1680-
locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
1657+
locsec = tx->tm_sec + (tx->tm_min + (day * 24 + tx->tm_hour) * 60) * 60;
16811658
delta2 = mysec - locsec;
1682-
if (delta2 != delta1)
1683-
{
1684-
mysec += (delta2 - delta1);
1685-
mytime = (time_t) mysec;
1686-
tmp = localtime(&mytime);
1687-
day = date2j(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday) -
1688-
UNIX_EPOCH_JDATE;
1689-
locsec = tmp->tm_sec + (tmp->tm_min + (day * 24 + tmp->tm_hour) * 60) * 60;
1690-
delta2 = mysec - locsec;
1691-
}
1692-
tm->tm_isdst = tmp->tm_isdst;
1693-
tz = (int) delta2;
16941659
}
1695-
#else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
1696-
/* Assume UTC if no system timezone info available */
1697-
tm->tm_isdst = 0;
1698-
tz = 0;
1699-
#endif
1660+
tm->tm_isdst = tx->tm_isdst;
1661+
tz = (int) delta2;
17001662
}
17011663
else
17021664
{

0 commit comments

Comments
 (0)