Skip to content

Commit 76c50c0

Browse files
committed
Add code to identify_system_timezone() to try all zones in the zic
database, not just ones that we cons up POSIX names for. This looks grim but it seems to take less than a second even on a relatively slow machine, and since it only happens once during postmaster startup, that seems acceptable.
1 parent 17ff181 commit 76c50c0

File tree

1 file changed

+216
-55
lines changed

1 file changed

+216
-55
lines changed

src/timezone/pgtz.c

Lines changed: 216 additions & 55 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.14 2004/05/24 02:30:29 tgl Exp $
9+
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.15 2004/05/25 18:08:59 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -15,18 +15,39 @@
1515
#include "postgres.h"
1616

1717
#include <ctype.h>
18+
#include <sys/stat.h>
1819

1920
#include "miscadmin.h"
2021
#include "pgtime.h"
2122
#include "pgtz.h"
23+
#include "storage/fd.h"
2224
#include "tzfile.h"
2325
#include "utils/elog.h"
2426
#include "utils/guc.h"
2527

2628

29+
#define T_DAY ((time_t) (60*60*24))
30+
#define T_MONTH ((time_t) (60*60*24*31))
31+
32+
struct tztry
33+
{
34+
char std_zone_name[TZ_STRLEN_MAX + 1],
35+
dst_zone_name[TZ_STRLEN_MAX + 1];
36+
#define MAX_TEST_TIMES 10
37+
int n_test_times;
38+
time_t test_times[MAX_TEST_TIMES];
39+
};
40+
2741
static char tzdir[MAXPGPATH];
2842
static int done_tzdir = 0;
2943

44+
static bool scan_available_timezones(char *tzdir, char *tzdirsub,
45+
struct tztry *tt);
46+
47+
48+
/*
49+
* Return full pathname of timezone data directory
50+
*/
3051
char *
3152
pg_TZDIR(void)
3253
{
@@ -41,22 +62,69 @@ pg_TZDIR(void)
4162
}
4263

4364
/*
44-
* Try to determine the system timezone (as opposed to the timezone
45-
* set in our own library).
65+
* Get GMT offset from a system struct tm
4666
*/
47-
#define T_DAY ((time_t) (60*60*24))
48-
#define T_MONTH ((time_t) (60*60*24*31))
67+
static int
68+
get_timezone_offset(struct tm *tm)
69+
{
70+
#if defined(HAVE_STRUCT_TM_TM_ZONE)
71+
return tm->tm_gmtoff;
72+
#elif defined(HAVE_INT_TIMEZONE)
73+
#ifdef HAVE_UNDERSCORE_TIMEZONE
74+
return -_timezone;
75+
#else
76+
return -timezone;
77+
#endif
78+
#else
79+
#error No way to determine TZ? Can this happen?
80+
#endif
81+
}
4982

50-
struct tztry
83+
/*
84+
* Grotty kluge for win32 ... do we really need this?
85+
*/
86+
#ifdef WIN32
87+
#define TZABBREV(tz) win32_get_timezone_abbrev(tz)
88+
89+
static char *
90+
win32_get_timezone_abbrev(const char *tz)
5191
{
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];
57-
};
92+
static char w32tzabbr[TZ_STRLEN_MAX + 1];
93+
int l = 0;
94+
const char *c;
95+
96+
for (c = tz; *c; c++)
97+
{
98+
if (isupper((unsigned char) *c))
99+
w32tzabbr[l++] = *c;
100+
}
101+
w32tzabbr[l] = '\0';
102+
return w32tzabbr;
103+
}
104+
105+
#else
106+
#define TZABBREV(tz) (tz)
107+
#endif
108+
109+
/*
110+
* Convenience subroutine to convert y/m/d to time_t
111+
*/
112+
static time_t
113+
build_time_t(int year, int month, int day)
114+
{
115+
struct tm tm;
58116

117+
memset(&tm, 0, sizeof(tm));
118+
tm.tm_mday = day;
119+
tm.tm_mon = month - 1;
120+
tm.tm_year = year - 1900;
121+
122+
return mktime(&tm);
123+
}
59124

125+
/*
126+
* Does a system tm value match one we computed ourselves?
127+
*/
60128
static bool
61129
compare_tm(struct tm *s, struct pg_tm *p)
62130
{
@@ -73,12 +141,16 @@ compare_tm(struct tm *s, struct pg_tm *p)
73141
return true;
74142
}
75143

144+
/*
145+
* See if a specific timezone setting matches the system behavior
146+
*/
76147
static bool
77-
try_timezone(char *tzname, struct tztry *tt)
148+
try_timezone(const char *tzname, struct tztry *tt)
78149
{
79150
int i;
80151
struct tm *systm;
81152
struct pg_tm *pgtm;
153+
char cbuf[TZ_STRLEN_MAX + 1];
82154

83155
if (!pg_tzset(tzname))
84156
return false; /* can't handle the TZ name at all */
@@ -92,51 +164,25 @@ try_timezone(char *tzname, struct tztry *tt)
92164
systm = localtime(&(tt->test_times[i]));
93165
if (!compare_tm(systm, pgtm))
94166
return false;
167+
if (systm->tm_isdst >= 0)
168+
{
169+
/* Check match of zone names, too */
170+
if (pgtm->tm_zone == NULL)
171+
return false;
172+
memset(cbuf, 0, sizeof(cbuf));
173+
strftime(cbuf, sizeof(cbuf) - 1, "%Z", systm); /* zone abbr */
174+
if (strcmp(TZABBREV(cbuf), pgtm->tm_zone) != 0)
175+
return false;
176+
}
95177
}
96178

97-
return true;
98-
}
99-
100-
static int
101-
get_timezone_offset(struct tm *tm)
102-
{
103-
#if defined(HAVE_STRUCT_TM_TM_ZONE)
104-
return tm->tm_gmtoff;
105-
#elif defined(HAVE_INT_TIMEZONE)
106-
#ifdef HAVE_UNDERSCORE_TIMEZONE
107-
return -_timezone;
108-
#else
109-
return -timezone;
110-
#endif
111-
#else
112-
#error No way to determine TZ? Can this happen?
113-
#endif
114-
}
115-
116-
117-
#ifdef WIN32
118-
#define TZABBREV(tz) win32_get_timezone_abbrev(tz)
119-
120-
static char *
121-
win32_get_timezone_abbrev(char *tz)
122-
{
123-
static char w32tzabbr[TZ_STRLEN_MAX + 1];
124-
int l = 0;
125-
char *c;
179+
/* Reject if leap seconds involved */
180+
if (!tz_acceptable())
181+
return false;
126182

127-
for (c = tz; *c; c++)
128-
{
129-
if (isupper(*c))
130-
w32tzabbr[l++] = *c;
131-
}
132-
w32tzabbr[l] = '\0';
133-
return w32tzabbr;
183+
return true;
134184
}
135185

136-
#else
137-
#define TZABBREV(tz) tz
138-
#endif
139-
140186

141187
/*
142188
* Try to identify a timezone name (in our terminology) that matches the
@@ -155,6 +201,7 @@ identify_system_timezone(void)
155201
int std_ofs = 0;
156202
struct tztry tt;
157203
struct tm *tm;
204+
char tmptzdir[MAXPGPATH];
158205
char cbuf[TZ_STRLEN_MAX + 1];
159206

160207
/* Initialize OS timezone library */
@@ -225,6 +272,20 @@ identify_system_timezone(void)
225272
}
226273
}
227274

275+
/*
276+
* Add a couple of historical dates as well; without this we are likely
277+
* to choose an accidental match, such as Antartica/Palmer when we
278+
* really want America/Santiago. Ideally we'd probe some dates before
279+
* 1970 too, but that is guaranteed to fail if the system TZ library
280+
* doesn't cope with DST before 1970.
281+
*/
282+
tt.test_times[tt.n_test_times++] = build_time_t(1970, 1, 15);
283+
tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15);
284+
tt.test_times[tt.n_test_times++] = build_time_t(1990, 4, 1);
285+
tt.test_times[tt.n_test_times++] = build_time_t(1990, 10, 1);
286+
287+
Assert(tt.n_test_times <= MAX_TEST_TIMES);
288+
228289
/* We should have found a STD zone name by now... */
229290
if (tt.std_zone_name[0] == '\0')
230291
{
@@ -234,7 +295,17 @@ identify_system_timezone(void)
234295
return NULL; /* go to GMT */
235296
}
236297

237-
/* If we found DST too then try STD<ofs>DST */
298+
/* Search for a matching timezone file */
299+
strcpy(tmptzdir, pg_TZDIR());
300+
if (scan_available_timezones(tmptzdir,
301+
tmptzdir + strlen(tmptzdir) + 1,
302+
&tt))
303+
{
304+
StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf));
305+
return resultbuf;
306+
}
307+
308+
/* If we found DST then try STD<ofs>DST */
238309
if (tt.dst_zone_name[0] != '\0')
239310
{
240311
snprintf(resultbuf, sizeof(resultbuf), "%s%d%s",
@@ -270,6 +341,96 @@ identify_system_timezone(void)
270341
return resultbuf;
271342
}
272343

344+
/*
345+
* Recursively scan the timezone database looking for a usable match to
346+
* the system timezone behavior.
347+
*
348+
* tzdir points to a buffer of size MAXPGPATH. On entry, it holds the
349+
* pathname of a directory containing TZ files. We internally modify it
350+
* to hold pathnames of sub-directories and files, but must restore it
351+
* to its original contents before exit.
352+
*
353+
* tzdirsub points to the part of tzdir that represents the subfile name
354+
* (ie, tzdir + the original directory name length, plus one for the
355+
* first added '/').
356+
*
357+
* tt tells about the system timezone behavior we need to match.
358+
*
359+
* On success, returns TRUE leaving the proper timezone selected.
360+
* On failure, returns FALSE with a random timezone selected.
361+
*/
362+
static bool
363+
scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
364+
{
365+
int tzdir_orig_len = strlen(tzdir);
366+
bool found = false;
367+
DIR *dirdesc;
368+
369+
dirdesc = AllocateDir(tzdir);
370+
if (!dirdesc)
371+
{
372+
ereport(LOG,
373+
(errcode_for_file_access(),
374+
errmsg("could not open directory \"%s\": %m", tzdir)));
375+
return false;
376+
}
377+
378+
for (;;)
379+
{
380+
struct dirent *direntry;
381+
struct stat statbuf;
382+
383+
errno = 0;
384+
direntry = readdir(dirdesc);
385+
if (!direntry)
386+
{
387+
if (errno)
388+
ereport(LOG,
389+
(errcode_for_file_access(),
390+
errmsg("error reading directory: %m")));
391+
break;
392+
}
393+
394+
/* Ignore . and .., plus any other "hidden" files */
395+
if (direntry->d_name[0] == '.')
396+
continue;
397+
398+
snprintf(tzdir + tzdir_orig_len, MAXPGPATH - tzdir_orig_len,
399+
"/%s", direntry->d_name);
400+
401+
if (stat(tzdir, &statbuf) != 0)
402+
{
403+
ereport(LOG,
404+
(errcode_for_file_access(),
405+
errmsg("could not stat \"%s\": %m", tzdir)));
406+
continue;
407+
}
408+
409+
if (S_ISDIR(statbuf.st_mode))
410+
{
411+
/* Recurse into subdirectory */
412+
found = scan_available_timezones(tzdir, tzdirsub, tt);
413+
if (found)
414+
break;
415+
}
416+
else
417+
{
418+
/* Load and test this file */
419+
found = try_timezone(tzdirsub, tt);
420+
if (found)
421+
break;
422+
}
423+
}
424+
425+
FreeDir(dirdesc);
426+
427+
/* Restore tzdir */
428+
tzdir[tzdir_orig_len] = '\0';
429+
430+
return found;
431+
}
432+
433+
273434
/*
274435
* Check whether timezone is acceptable.
275436
*
@@ -351,6 +512,6 @@ pg_timezone_initialize(void)
351512
/* Select setting */
352513
def_tz = select_default_timezone();
353514
/* Tell GUC about the value. Will redundantly call pg_tzset() */
354-
SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ENV_VAR);
515+
SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ARGV);
355516
}
356517
}

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