From 99b225c528a3f082f60fd5974e1b44ee0462a6fd Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 10 Jul 2004 23:06:50 +0000 Subject: [PATCH] Check more test points (in fact, every week in 1970..2004) to get a more accurate matching of our time zone to the system's zone. This method is able to distinguish Antarctica/Casey from Australia/Perth, as in Chris K-L's recent example; and it is not materially slower than before, because the extra checks generally don't get done against very many time zones. It seems possible that with this test we'd be able to correctly identify Windows timezones without looking at the timezone name, but I do not have the ability to try it. --- src/timezone/pgtz.c | 147 +++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 77 deletions(-) diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c index 3da41363d5..19379d66f1 100644 --- a/src/timezone/pgtz.c +++ b/src/timezone/pgtz.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.17 2004/06/03 02:08:07 tgl Exp $ + * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.18 2004/07/10 23:06:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,13 +29,13 @@ #define T_DAY ((time_t) (60*60*24)) +#define T_WEEK ((time_t) (60*60*24*7)) #define T_MONTH ((time_t) (60*60*24*31)) +#define MAX_TEST_TIMES (52*35) /* 35 years, or 1970..2004 */ + struct tztry { - char std_zone_name[TZ_STRLEN_MAX + 1], - dst_zone_name[TZ_STRLEN_MAX + 1]; -#define MAX_TEST_TIMES 10 int n_test_times; time_t test_times[MAX_TEST_TIMES]; }; @@ -219,27 +219,61 @@ identify_system_timezone(void) static char resultbuf[TZ_STRLEN_MAX + 1]; time_t tnow; time_t t; - int nowisdst, - curisdst; - int std_ofs = 0; struct tztry tt; struct tm *tm; char tmptzdir[MAXPGPATH]; + int std_ofs; + char std_zone_name[TZ_STRLEN_MAX + 1], + dst_zone_name[TZ_STRLEN_MAX + 1]; char cbuf[TZ_STRLEN_MAX + 1]; /* Initialize OS timezone library */ tzset(); - /* No info yet */ - memset(&tt, 0, sizeof(tt)); + /* + * Set up the list of dates to be probed to verify that our timezone + * matches the system zone. We first probe January and July of 1970; + * this serves to quickly eliminate the vast majority of the TZ database + * entries. If those dates match, we probe every week from 1970 to + * late 2004. This exhaustive test is intended to ensure that we have + * the same DST transition rules as the system timezone. (Note: we + * probe Thursdays, not Sundays, to avoid triggering DST-transition + * bugs in localtime itself.) + * + * Ideally we'd probe some dates before 1970 too, but that is guaranteed + * to fail if the system TZ library doesn't cope with DST before 1970. + */ + tt.n_test_times = 0; + tt.test_times[tt.n_test_times++] = t = build_time_t(1970, 1, 15); + tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15); + while (tt.n_test_times < MAX_TEST_TIMES) + { + t += T_WEEK; + tt.test_times[tt.n_test_times++] = t; + } + + /* Search for a matching timezone file */ + strcpy(tmptzdir, pg_TZDIR()); + if (scan_available_timezones(tmptzdir, + tmptzdir + strlen(tmptzdir) + 1, + &tt)) + { + StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf)); + return resultbuf; + } /* - * The idea here is to scan forward from today and try to locate the - * next two daylight-savings transition boundaries. We will test for - * correct results on the day before and after each boundary; this - * gives at least some confidence that we've selected the right DST - * rule set. + * Couldn't find a match in the database, so next we try constructed zone + * names (like "PST8PDT"). + * + * First we need to determine the names of the local standard and daylight + * zones. The idea here is to scan forward from today until we have + * seen both zones, if both are in use. */ + memset(std_zone_name, 0, sizeof(std_zone_name)); + memset(dst_zone_name, 0, sizeof(dst_zone_name)); + std_ofs = 0; + tnow = time(NULL); /* @@ -248,69 +282,38 @@ identify_system_timezone(void) */ tnow -= (tnow % T_DAY); - /* Always test today, so we have at least one test point */ - tt.test_times[tt.n_test_times++] = tnow; - - tm = localtime(&tnow); - nowisdst = tm->tm_isdst; - curisdst = nowisdst; - - if (curisdst == 0) - { - /* Set up STD zone name, in case we are in a non-DST zone */ - memset(cbuf, 0, sizeof(cbuf)); - strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */ - strcpy(tt.std_zone_name, TZABBREV(cbuf)); - /* Also preset std_ofs */ - std_ofs = get_timezone_offset(tm); - } - /* * We have to look a little further ahead than one year, in case today * is just past a DST boundary that falls earlier in the year than the * next similar boundary. Arbitrarily scan up to 14 months. */ - for (t = tnow + T_DAY; t < tnow + T_MONTH * 14; t += T_DAY) + for (t = tnow; t <= tnow + T_MONTH * 14; t += T_MONTH) { tm = localtime(&t); - if (tm->tm_isdst >= 0 && tm->tm_isdst != curisdst) + if (tm->tm_isdst < 0) + continue; + if (tm->tm_isdst == 0 && std_zone_name[0] == '\0') { - /* Found a boundary */ - tt.test_times[tt.n_test_times++] = t - T_DAY; - tt.test_times[tt.n_test_times++] = t; - curisdst = tm->tm_isdst; - /* Save STD or DST zone name, also std_ofs */ + /* found STD zone */ memset(cbuf, 0, sizeof(cbuf)); strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */ - if (curisdst == 0) - { - strcpy(tt.std_zone_name, TZABBREV(cbuf)); - std_ofs = get_timezone_offset(tm); - } - else - strcpy(tt.dst_zone_name, TZABBREV(cbuf)); - /* Have we found two boundaries? */ - if (tt.n_test_times >= 5) - break; + strcpy(std_zone_name, TZABBREV(cbuf)); + std_ofs = get_timezone_offset(tm); } + if (tm->tm_isdst > 0 && dst_zone_name[0] == '\0') + { + /* found DST zone */ + memset(cbuf, 0, sizeof(cbuf)); + strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */ + strcpy(dst_zone_name, TZABBREV(cbuf)); + } + /* Done if found both */ + if (std_zone_name[0] && dst_zone_name[0]) + break; } - /* - * Add a couple of historical dates as well; without this we are likely - * to choose an accidental match, such as Antartica/Palmer when we - * really want America/Santiago. Ideally we'd probe some dates before - * 1970 too, but that is guaranteed to fail if the system TZ library - * doesn't cope with DST before 1970. - */ - tt.test_times[tt.n_test_times++] = build_time_t(1970, 1, 15); - tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15); - tt.test_times[tt.n_test_times++] = build_time_t(1990, 4, 1); - tt.test_times[tt.n_test_times++] = build_time_t(1990, 10, 1); - - Assert(tt.n_test_times <= MAX_TEST_TIMES); - /* We should have found a STD zone name by now... */ - if (tt.std_zone_name[0] == '\0') + if (std_zone_name[0] == '\0') { ereport(LOG, (errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"), @@ -318,33 +321,23 @@ identify_system_timezone(void) return NULL; /* go to GMT */ } - /* Search for a matching timezone file */ - strcpy(tmptzdir, pg_TZDIR()); - if (scan_available_timezones(tmptzdir, - tmptzdir + strlen(tmptzdir) + 1, - &tt)) - { - StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf)); - return resultbuf; - } - /* If we found DST then try STDDST */ - if (tt.dst_zone_name[0] != '\0') + if (dst_zone_name[0] != '\0') { snprintf(resultbuf, sizeof(resultbuf), "%s%d%s", - tt.std_zone_name, -std_ofs / 3600, tt.dst_zone_name); + std_zone_name, -std_ofs / 3600, dst_zone_name); if (try_timezone(resultbuf, &tt)) return resultbuf; } /* Try just the STD timezone (works for GMT at least) */ - strcpy(resultbuf, tt.std_zone_name); + strcpy(resultbuf, std_zone_name); if (try_timezone(resultbuf, &tt)) return resultbuf; /* Try STD */ snprintf(resultbuf, sizeof(resultbuf), "%s%d", - tt.std_zone_name, -std_ofs / 3600); + std_zone_name, -std_ofs / 3600); if (try_timezone(resultbuf, &tt)) return resultbuf; -- 2.40.0