Skip to content

Commit dc39937

Browse files
committed
Rewrite identify_system_timezone() to give it better-than-chance odds
of correctly identifying the system's daylight-savings transition rules. This still begs the question of how to look through the zic database to find a matching zone definition, but at least now we'll have some chance of recognizing the match when we find it.
1 parent 82695df commit dc39937

File tree

1 file changed

+114
-78
lines changed

1 file changed

+114
-78
lines changed

src/timezone/pgtz.c

Lines changed: 114 additions & 78 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.13 2004/05/23 23:26:53 tgl Exp $
9+
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.14 2004/05/24 02:30:29 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -44,24 +44,21 @@ pg_TZDIR(void)
4444
* Try to determine the system timezone (as opposed to the timezone
4545
* set in our own library).
4646
*/
47-
#define T_YEAR (60*60*24*365)
48-
#define T_MONTH (60*60*24*30)
47+
#define T_DAY ((time_t) (60*60*24))
48+
#define T_MONTH ((time_t) (60*60*24*31))
4949

5050
struct tztry
5151
{
52-
time_t std_t,
53-
dst_t;
54-
char std_time[TZ_STRLEN_MAX + 1],
55-
dst_time[TZ_STRLEN_MAX + 1];
56-
int std_ofs,
57-
dst_ofs;
58-
struct tm std_tm,
59-
dst_tm;
52+
char std_zone_name[TZ_STRLEN_MAX + 1],
53+
dst_zone_name[TZ_STRLEN_MAX + 1];
54+
#define MAX_TEST_TIMES 5
55+
int n_test_times;
56+
time_t test_times[MAX_TEST_TIMES];
6057
};
6158

6259

6360
static bool
64-
compare_tm(struct tm * s, struct pg_tm * p)
61+
compare_tm(struct tm *s, struct pg_tm *p)
6562
{
6663
if (s->tm_sec != p->tm_sec ||
6764
s->tm_min != p->tm_min ||
@@ -77,36 +74,31 @@ compare_tm(struct tm * s, struct pg_tm * p)
7774
}
7875

7976
static bool
80-
try_timezone(char *tzname, struct tztry * tt, bool checkdst)
77+
try_timezone(char *tzname, struct tztry *tt)
8178
{
82-
struct pg_tm *pgtm;
79+
int i;
80+
struct tm *systm;
81+
struct pg_tm *pgtm;
8382

8483
if (!pg_tzset(tzname))
85-
return false; /* If this timezone couldn't be picked at
86-
* all */
84+
return false; /* can't handle the TZ name at all */
8785

88-
/* Verify standard time */
89-
pgtm = pg_localtime(&(tt->std_t));
90-
if (!pgtm)
91-
return false;
92-
if (!compare_tm(&(tt->std_tm), pgtm))
93-
return false;
94-
95-
if (!checkdst)
96-
return true;
97-
98-
/* Now check daylight time */
99-
pgtm = pg_localtime(&(tt->dst_t));
100-
if (!pgtm)
101-
return false;
102-
if (!compare_tm(&(tt->dst_tm), pgtm))
103-
return false;
86+
/* Check for match at all the test times */
87+
for (i = 0; i < tt->n_test_times; i++)
88+
{
89+
pgtm = pg_localtime(&(tt->test_times[i]));
90+
if (!pgtm)
91+
return false; /* probably shouldn't happen */
92+
systm = localtime(&(tt->test_times[i]));
93+
if (!compare_tm(systm, pgtm))
94+
return false;
95+
}
10496

10597
return true;
10698
}
10799

108100
static int
109-
get_timezone_offset(struct tm * tm)
101+
get_timezone_offset(struct tm *tm)
110102
{
111103
#if defined(HAVE_STRUCT_TM_TM_ZONE)
112104
return tm->tm_gmtoff;
@@ -150,88 +142,132 @@ win32_get_timezone_abbrev(char *tz)
150142
* Try to identify a timezone name (in our terminology) that matches the
151143
* observed behavior of the system timezone library. We cannot assume that
152144
* the system TZ environment setting (if indeed there is one) matches our
153-
* terminology, so ignore it and just look at what localtime() returns.
145+
* terminology, so we ignore it and just look at what localtime() returns.
154146
*/
155147
static char *
156148
identify_system_timezone(void)
157149
{
158-
static char __tzbuf[TZ_STRLEN_MAX + 1];
159-
bool std_found = false,
160-
dst_found = false;
161-
time_t tnow = time(NULL);
150+
static char resultbuf[TZ_STRLEN_MAX + 1];
151+
time_t tnow;
162152
time_t t;
153+
int nowisdst,
154+
curisdst;
155+
int std_ofs = 0;
163156
struct tztry tt;
157+
struct tm *tm;
164158
char cbuf[TZ_STRLEN_MAX + 1];
165159

166160
/* Initialize OS timezone library */
167161
tzset();
168162

163+
/* No info yet */
169164
memset(&tt, 0, sizeof(tt));
170165

171-
for (t = tnow; t < tnow + T_YEAR; t += T_MONTH)
166+
/*
167+
* The idea here is to scan forward from today and try to locate the
168+
* next two daylight-savings transition boundaries. We will test for
169+
* correct results on the day before and after each boundary; this
170+
* gives at least some confidence that we've selected the right DST
171+
* rule set.
172+
*/
173+
tnow = time(NULL);
174+
175+
/*
176+
* Round back to a GMT midnight so results don't depend on local time
177+
* of day
178+
*/
179+
tnow -= (tnow % T_DAY);
180+
181+
/* Always test today, so we have at least one test point */
182+
tt.test_times[tt.n_test_times++] = tnow;
183+
184+
tm = localtime(&tnow);
185+
nowisdst = tm->tm_isdst;
186+
curisdst = nowisdst;
187+
188+
if (curisdst == 0)
172189
{
173-
struct tm *tm = localtime(&t);
190+
/* Set up STD zone name, in case we are in a non-DST zone */
191+
memset(cbuf, 0, sizeof(cbuf));
192+
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
193+
strcpy(tt.std_zone_name, TZABBREV(cbuf));
194+
/* Also preset std_ofs */
195+
std_ofs = get_timezone_offset(tm);
196+
}
174197

175-
if (tm->tm_isdst == 0 && !std_found)
176-
{
177-
/* Standard time */
178-
memcpy(&tt.std_tm, tm, sizeof(struct tm));
179-
memset(cbuf, 0, sizeof(cbuf));
180-
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
181-
strcpy(tt.std_time, TZABBREV(cbuf));
182-
tt.std_ofs = get_timezone_offset(tm);
183-
tt.std_t = t;
184-
std_found = true;
185-
}
186-
else if (tm->tm_isdst == 1 && !dst_found)
198+
/*
199+
* We have to look a little further ahead than one year, in case today
200+
* is just past a DST boundary that falls earlier in the year than the
201+
* next similar boundary. Arbitrarily scan up to 14 months.
202+
*/
203+
for (t = tnow + T_DAY; t < tnow + T_MONTH * 14; t += T_DAY)
204+
{
205+
tm = localtime(&t);
206+
if (tm->tm_isdst >= 0 && tm->tm_isdst != curisdst)
187207
{
188-
/* Daylight time */
189-
memcpy(&tt.dst_tm, tm, sizeof(struct tm));
208+
/* Found a boundary */
209+
tt.test_times[tt.n_test_times++] = t - T_DAY;
210+
tt.test_times[tt.n_test_times++] = t;
211+
curisdst = tm->tm_isdst;
212+
/* Save STD or DST zone name, also std_ofs */
190213
memset(cbuf, 0, sizeof(cbuf));
191214
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
192-
strcpy(tt.dst_time, TZABBREV(cbuf));
193-
tt.dst_ofs = get_timezone_offset(tm);
194-
tt.dst_t = t;
195-
dst_found = true;
215+
if (curisdst == 0)
216+
{
217+
strcpy(tt.std_zone_name, TZABBREV(cbuf));
218+
std_ofs = get_timezone_offset(tm);
219+
}
220+
else
221+
strcpy(tt.dst_zone_name, TZABBREV(cbuf));
222+
/* Have we found two boundaries? */
223+
if (tt.n_test_times >= 5)
224+
break;
196225
}
197-
if (std_found && dst_found)
198-
break; /* Got both standard and daylight */
199226
}
200227

201-
if (!std_found)
228+
/* We should have found a STD zone name by now... */
229+
if (tt.std_zone_name[0] == '\0')
202230
{
203-
/* Failed to determine TZ! */
204231
ereport(LOG,
205232
(errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"),
206233
errhint("You can specify the correct timezone in postgresql.conf.")));
207234
return NULL; /* go to GMT */
208235
}
209236

210-
if (dst_found)
237+
/* If we found DST too then try STD<ofs>DST */
238+
if (tt.dst_zone_name[0] != '\0')
211239
{
212-
/* Try STD<ofs>DST */
213-
sprintf(__tzbuf, "%s%d%s", tt.std_time, -tt.std_ofs / 3600, tt.dst_time);
214-
if (try_timezone(__tzbuf, &tt, dst_found))
215-
return __tzbuf;
240+
snprintf(resultbuf, sizeof(resultbuf), "%s%d%s",
241+
tt.std_zone_name, -std_ofs / 3600, tt.dst_zone_name);
242+
if (try_timezone(resultbuf, &tt))
243+
return resultbuf;
216244
}
217-
/* Try just the STD timezone */
218-
strcpy(__tzbuf, tt.std_time);
219-
if (try_timezone(__tzbuf, &tt, dst_found))
220-
return __tzbuf;
245+
246+
/* Try just the STD timezone (works for GMT at least) */
247+
strcpy(resultbuf, tt.std_zone_name);
248+
if (try_timezone(resultbuf, &tt))
249+
return resultbuf;
250+
251+
/* Try STD<ofs> */
252+
snprintf(resultbuf, sizeof(resultbuf), "%s%d",
253+
tt.std_zone_name, -std_ofs / 3600);
254+
if (try_timezone(resultbuf, &tt))
255+
return resultbuf;
221256

222257
/*
223-
* Did not find the timezone. Fallback to try a GMT zone. Note that the
258+
* Did not find the timezone. Fallback to use a GMT zone. Note that the
224259
* zic timezone database names the GMT-offset zones in POSIX style: plus
225260
* is west of Greenwich. It's unfortunate that this is opposite of SQL
226261
* conventions. Should we therefore change the names? Probably not...
227262
*/
228-
sprintf(__tzbuf, "Etc/GMT%s%d",
229-
(-tt.std_ofs > 0) ? "+" : "", -tt.std_ofs / 3600);
263+
snprintf(resultbuf, sizeof(resultbuf), "Etc/GMT%s%d",
264+
(-std_ofs > 0) ? "+" : "", -std_ofs / 3600);
265+
230266
ereport(LOG,
231-
(errmsg("could not recognize system timezone, defaulting to \"%s\"",
232-
__tzbuf),
233-
errhint("You can specify the correct timezone in postgresql.conf.")));
234-
return __tzbuf;
267+
(errmsg("could not recognize system timezone, defaulting to \"%s\"",
268+
resultbuf),
269+
errhint("You can specify the correct timezone in postgresql.conf.")));
270+
return resultbuf;
235271
}
236272

237273
/*

0 commit comments

Comments
 (0)