88 *
99 *
1010 * IDENTIFICATION
11- * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.94 2002/09/02 02:47:04 momjian Exp $
11+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.95 2002/09/03 19:46:32 tgl Exp $
1212 *
1313 *-------------------------------------------------------------------------
1414 */
@@ -1446,21 +1446,26 @@ DecodeDateTime(char **field, int *ftype, int nf,
14461446
14471447
14481448/* DetermineLocalTimeZone()
1449+ *
14491450 * Given a struct tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
14501451 * tm_sec fields are set, attempt to determine the applicable local zone
14511452 * (ie, regular or daylight-savings time) at that time. Set the struct tm's
14521453 * tm_isdst field accordingly, and return the actual timezone offset.
14531454 *
1454- * This subroutine exists mainly to centralize uses of mktime() and defend
1455- * against mktime() bugs on various platforms...
1455+ * This subroutine exists to centralize uses of mktime() and defend against
1456+ * mktime() bugs/restrictions on various platforms. This should be
1457+ * the *only* call of mktime() in the backend.
14561458 */
14571459int
14581460DetermineLocalTimeZone (struct tm * tm )
14591461{
14601462int tz ;
14611463
14621464if (HasCTZSet )
1465+ {
1466+ tm -> tm_isdst = 0 ;/* for lack of a better idea */
14631467tz = CTimeZone ;
1468+ }
14641469else if (IS_VALID_UTIME (tm -> tm_year ,tm -> tm_mon ,tm -> tm_mday ))
14651470{
14661471#if defined(HAVE_TM_ZONE )|| defined(HAVE_INT_TIMEZONE )
@@ -1482,20 +1487,90 @@ DetermineLocalTimeZone(struct tm * tm)
14821487/* indicate timezone unknown */
14831488tmp -> tm_isdst = -1 ;
14841489
1485- mktime (tmp );
1486-
1487- tm -> tm_isdst = tmp -> tm_isdst ;
1490+ if (mktime (tmp )!= ((time_t )- 1 )&&
1491+ tmp -> tm_isdst >=0 )
1492+ {
1493+ /* mktime() succeeded, trust its result */
1494+ tm -> tm_isdst = tmp -> tm_isdst ;
14881495
14891496#if defined(HAVE_TM_ZONE )
1490- /* tm_gmtoff is Sun/DEC-ism */
1491- if (tmp -> tm_isdst >=0 )
1497+ /* tm_gmtoff is Sun/DEC-ism */
14921498tz = - (tmp -> tm_gmtoff );
1493- else
1494- tz = 0 ;/* assume UTC if mktime failed */
14951499#elif defined(HAVE_INT_TIMEZONE )
1496- tz = ((tmp -> tm_isdst > 0 ) ? (TIMEZONE_GLOBAL - 3600 ) :TIMEZONE_GLOBAL );
1500+ tz = ((tmp -> tm_isdst > 0 ) ? (TIMEZONE_GLOBAL - 3600 ) :TIMEZONE_GLOBAL );
14971501#endif /* HAVE_INT_TIMEZONE */
1498-
1502+ }
1503+ else
1504+ {
1505+ /*
1506+ * We have a buggy (not to say deliberately brain damaged)
1507+ * mktime(). Work around it by using localtime() instead.
1508+ *
1509+ * First, generate the time_t value corresponding to the given
1510+ * y/m/d/h/m/s taken as GMT time. This will not overflow (at
1511+ * least not for time_t taken as signed) because of the range
1512+ * check we did above.
1513+ */
1514+ long day ,
1515+ mysec ,
1516+ locsec ,
1517+ delta1 ,
1518+ delta2 ;
1519+ time_t mytime ;
1520+
1521+ day = (date2j (tm -> tm_year ,tm -> tm_mon ,tm -> tm_mday )-
1522+ date2j (1970 ,1 ,1 ));
1523+ mysec = tm -> tm_sec + (tm -> tm_min + (day * 24 + tm -> tm_hour )* 60 )* 60 ;
1524+ mytime = (time_t )mysec ;
1525+ /*
1526+ * Use localtime to convert that time_t to broken-down time, and
1527+ * reassemble to get a representation of local time.
1528+ */
1529+ tmp = localtime (& mytime );
1530+ day = (date2j (tmp -> tm_year + 1900 ,tmp -> tm_mon + 1 ,tmp -> tm_mday )-
1531+ date2j (1970 ,1 ,1 ));
1532+ locsec = tmp -> tm_sec + (tmp -> tm_min + (day * 24 + tmp -> tm_hour )* 60 )* 60 ;
1533+ /*
1534+ * The local time offset corresponding to that GMT time is
1535+ * now computable as mysec - locsec.
1536+ */
1537+ delta1 = mysec - locsec ;
1538+ /*
1539+ * However, if that GMT time and the local time we are actually
1540+ * interested in are on opposite sides of a daylight-savings-time
1541+ * transition, then this is not the time offset we want. So,
1542+ * adjust the time_t to be what we think the GMT time corresponding
1543+ * to our target local time is, and repeat the localtime() call
1544+ * and delta calculation. We may have to do it twice before we
1545+ * have a trustworthy delta.
1546+ *
1547+ * Note: think not to put a loop here, since if we've been given
1548+ * an "impossible" local time (in the gap during a spring-forward
1549+ * transition) we'd never get out of the loop. Twice is enough
1550+ * to give the behavior we want, which is that "impossible" times
1551+ * are taken as standard time, while at a fall-back boundary
1552+ * ambiguous times are also taken as standard.
1553+ */
1554+ mysec += delta1 ;
1555+ mytime = (time_t )mysec ;
1556+ tmp = localtime (& mytime );
1557+ day = (date2j (tmp -> tm_year + 1900 ,tmp -> tm_mon + 1 ,tmp -> tm_mday )-
1558+ date2j (1970 ,1 ,1 ));
1559+ locsec = tmp -> tm_sec + (tmp -> tm_min + (day * 24 + tmp -> tm_hour )* 60 )* 60 ;
1560+ delta2 = mysec - locsec ;
1561+ if (delta2 != delta1 )
1562+ {
1563+ mysec += (delta2 - delta1 );
1564+ mytime = (time_t )mysec ;
1565+ tmp = localtime (& mytime );
1566+ day = (date2j (tmp -> tm_year + 1900 ,tmp -> tm_mon + 1 ,tmp -> tm_mday )-
1567+ date2j (1970 ,1 ,1 ));
1568+ locsec = tmp -> tm_sec + (tmp -> tm_min + (day * 24 + tmp -> tm_hour )* 60 )* 60 ;
1569+ delta2 = mysec - locsec ;
1570+ }
1571+ tm -> tm_isdst = tmp -> tm_isdst ;
1572+ tz = (int )delta2 ;
1573+ }
14991574#else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
15001575tm -> tm_isdst = 0 ;
15011576tz = CTimeZone ;