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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy