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 */
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+
2741static char tzdir [MAXPGPATH ];
2842static 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+ */
3051char *
3152pg_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+ */
60128static bool
61129compare_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+ */
76147static 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