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

Commit 99b225c

Browse files
committed
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.
1 parent b9f698e commit 99b225c

File tree

1 file changed

+70
-77
lines changed

1 file changed

+70
-77
lines changed

src/timezone/pgtz.c

Lines changed: 70 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
77
*
88
* IDENTIFICATION
9-
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.17 2004/06/03 02:08:07 tgl Exp $
9+
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.18 2004/07/10 23:06:50 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -29,13 +29,13 @@
2929

3030

3131
#define T_DAY ((time_t) (60*60*24))
32+
#define T_WEEK ((time_t) (60*60*24*7))
3233
#define T_MONTH ((time_t) (60*60*24*31))
3334

35+
#define MAX_TEST_TIMES (52*35) /* 35 years, or 1970..2004 */
36+
3437
struct tztry
3538
{
36-
char std_zone_name[TZ_STRLEN_MAX + 1],
37-
dst_zone_name[TZ_STRLEN_MAX + 1];
38-
#define MAX_TEST_TIMES 10
3939
int n_test_times;
4040
time_t test_times[MAX_TEST_TIMES];
4141
};
@@ -219,27 +219,61 @@ identify_system_timezone(void)
219219
static char resultbuf[TZ_STRLEN_MAX + 1];
220220
time_t tnow;
221221
time_t t;
222-
int nowisdst,
223-
curisdst;
224-
int std_ofs = 0;
225222
struct tztry tt;
226223
struct tm *tm;
227224
char tmptzdir[MAXPGPATH];
225+
int std_ofs;
226+
char std_zone_name[TZ_STRLEN_MAX + 1],
227+
dst_zone_name[TZ_STRLEN_MAX + 1];
228228
char cbuf[TZ_STRLEN_MAX + 1];
229229

230230
/* Initialize OS timezone library */
231231
tzset();
232232

233-
/* No info yet */
234-
memset(&tt, 0, sizeof(tt));
233+
/*
234+
* Set up the list of dates to be probed to verify that our timezone
235+
* matches the system zone. We first probe January and July of 1970;
236+
* this serves to quickly eliminate the vast majority of the TZ database
237+
* entries. If those dates match, we probe every week from 1970 to
238+
* late 2004. This exhaustive test is intended to ensure that we have
239+
* the same DST transition rules as the system timezone. (Note: we
240+
* probe Thursdays, not Sundays, to avoid triggering DST-transition
241+
* bugs in localtime itself.)
242+
*
243+
* Ideally we'd probe some dates before 1970 too, but that is guaranteed
244+
* to fail if the system TZ library doesn't cope with DST before 1970.
245+
*/
246+
tt.n_test_times = 0;
247+
tt.test_times[tt.n_test_times++] = t = build_time_t(1970, 1, 15);
248+
tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15);
249+
while (tt.n_test_times < MAX_TEST_TIMES)
250+
{
251+
t += T_WEEK;
252+
tt.test_times[tt.n_test_times++] = t;
253+
}
254+
255+
/* Search for a matching timezone file */
256+
strcpy(tmptzdir, pg_TZDIR());
257+
if (scan_available_timezones(tmptzdir,
258+
tmptzdir + strlen(tmptzdir) + 1,
259+
&tt))
260+
{
261+
StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf));
262+
return resultbuf;
263+
}
235264

236265
/*
237-
* The idea here is to scan forward from today and try to locate the
238-
* next two daylight-savings transition boundaries. We will test for
239-
* correct results on the day before and after each boundary; this
240-
* gives at least some confidence that we've selected the right DST
241-
* rule set.
266+
* Couldn't find a match in the database, so next we try constructed zone
267+
* names (like "PST8PDT").
268+
*
269+
* First we need to determine the names of the local standard and daylight
270+
* zones. The idea here is to scan forward from today until we have
271+
* seen both zones, if both are in use.
242272
*/
273+
memset(std_zone_name, 0, sizeof(std_zone_name));
274+
memset(dst_zone_name, 0, sizeof(dst_zone_name));
275+
std_ofs = 0;
276+
243277
tnow = time(NULL);
244278

245279
/*
@@ -248,103 +282,62 @@ identify_system_timezone(void)
248282
*/
249283
tnow -= (tnow % T_DAY);
250284

251-
/* Always test today, so we have at least one test point */
252-
tt.test_times[tt.n_test_times++] = tnow;
253-
254-
tm = localtime(&tnow);
255-
nowisdst = tm->tm_isdst;
256-
curisdst = nowisdst;
257-
258-
if (curisdst == 0)
259-
{
260-
/* Set up STD zone name, in case we are in a non-DST zone */
261-
memset(cbuf, 0, sizeof(cbuf));
262-
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
263-
strcpy(tt.std_zone_name, TZABBREV(cbuf));
264-
/* Also preset std_ofs */
265-
std_ofs = get_timezone_offset(tm);
266-
}
267-
268285
/*
269286
* We have to look a little further ahead than one year, in case today
270287
* is just past a DST boundary that falls earlier in the year than the
271288
* next similar boundary. Arbitrarily scan up to 14 months.
272289
*/
273-
for (t = tnow + T_DAY; t < tnow + T_MONTH * 14; t += T_DAY)
290+
for (t = tnow; t <= tnow + T_MONTH * 14; t += T_MONTH)
274291
{
275292
tm = localtime(&t);
276-
if (tm->tm_isdst >= 0 && tm->tm_isdst != curisdst)
293+
if (tm->tm_isdst < 0)
294+
continue;
295+
if (tm->tm_isdst == 0 && std_zone_name[0] == '\0')
277296
{
278-
/* Found a boundary */
279-
tt.test_times[tt.n_test_times++] = t - T_DAY;
280-
tt.test_times[tt.n_test_times++] = t;
281-
curisdst = tm->tm_isdst;
282-
/* Save STD or DST zone name, also std_ofs */
297+
/* found STD zone */
283298
memset(cbuf, 0, sizeof(cbuf));
284299
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
285-
if (curisdst == 0)
286-
{
287-
strcpy(tt.std_zone_name, TZABBREV(cbuf));
288-
std_ofs = get_timezone_offset(tm);
289-
}
290-
else
291-
strcpy(tt.dst_zone_name, TZABBREV(cbuf));
292-
/* Have we found two boundaries? */
293-
if (tt.n_test_times >= 5)
294-
break;
300+
strcpy(std_zone_name, TZABBREV(cbuf));
301+
std_ofs = get_timezone_offset(tm);
295302
}
303+
if (tm->tm_isdst > 0 && dst_zone_name[0] == '\0')
304+
{
305+
/* found DST zone */
306+
memset(cbuf, 0, sizeof(cbuf));
307+
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
308+
strcpy(dst_zone_name, TZABBREV(cbuf));
309+
}
310+
/* Done if found both */
311+
if (std_zone_name[0] && dst_zone_name[0])
312+
break;
296313
}
297314

298-
/*
299-
* Add a couple of historical dates as well; without this we are likely
300-
* to choose an accidental match, such as Antartica/Palmer when we
301-
* really want America/Santiago. Ideally we'd probe some dates before
302-
* 1970 too, but that is guaranteed to fail if the system TZ library
303-
* doesn't cope with DST before 1970.
304-
*/
305-
tt.test_times[tt.n_test_times++] = build_time_t(1970, 1, 15);
306-
tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15);
307-
tt.test_times[tt.n_test_times++] = build_time_t(1990, 4, 1);
308-
tt.test_times[tt.n_test_times++] = build_time_t(1990, 10, 1);
309-
310-
Assert(tt.n_test_times <= MAX_TEST_TIMES);
311-
312315
/* We should have found a STD zone name by now... */
313-
if (tt.std_zone_name[0] == '\0')
316+
if (std_zone_name[0] == '\0')
314317
{
315318
ereport(LOG,
316319
(errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"),
317320
errhint("You can specify the correct timezone in postgresql.conf.")));
318321
return NULL; /* go to GMT */
319322
}
320323

321-
/* Search for a matching timezone file */
322-
strcpy(tmptzdir, pg_TZDIR());
323-
if (scan_available_timezones(tmptzdir,
324-
tmptzdir + strlen(tmptzdir) + 1,
325-
&tt))
326-
{
327-
StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf));
328-
return resultbuf;
329-
}
330-
331324
/* If we found DST then try STD<ofs>DST */
332-
if (tt.dst_zone_name[0] != '\0')
325+
if (dst_zone_name[0] != '\0')
333326
{
334327
snprintf(resultbuf, sizeof(resultbuf), "%s%d%s",
335-
tt.std_zone_name, -std_ofs / 3600, tt.dst_zone_name);
328+
std_zone_name, -std_ofs / 3600, dst_zone_name);
336329
if (try_timezone(resultbuf, &tt))
337330
return resultbuf;
338331
}
339332

340333
/* Try just the STD timezone (works for GMT at least) */
341-
strcpy(resultbuf, tt.std_zone_name);
334+
strcpy(resultbuf, std_zone_name);
342335
if (try_timezone(resultbuf, &tt))
343336
return resultbuf;
344337

345338
/* Try STD<ofs> */
346339
snprintf(resultbuf, sizeof(resultbuf), "%s%d",
347-
tt.std_zone_name, -std_ofs / 3600);
340+
std_zone_name, -std_ofs / 3600);
348341
if (try_timezone(resultbuf, &tt))
349342
return resultbuf;
350343

0 commit comments

Comments
 (0)