6
6
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
7
7
*
8
8
* 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 $
10
10
*
11
11
*-------------------------------------------------------------------------
12
12
*/
15
15
#include "postgres.h"
16
16
17
17
#include <ctype.h>
18
+ #include <sys/stat.h>
18
19
19
20
#include "miscadmin.h"
20
21
#include "pgtime.h"
21
22
#include "pgtz.h"
23
+ #include "storage/fd.h"
22
24
#include "tzfile.h"
23
25
#include "utils/elog.h"
24
26
#include "utils/guc.h"
25
27
26
28
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
+
27
41
static char tzdir [MAXPGPATH ];
28
42
static int done_tzdir = 0 ;
29
43
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
+ */
30
51
char *
31
52
pg_TZDIR (void )
32
53
{
@@ -41,22 +62,69 @@ pg_TZDIR(void)
41
62
}
42
63
43
64
/*
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
46
66
*/
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
+ }
49
82
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 )
51
91
{
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 ;
58
116
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
+ }
59
124
125
+ /*
126
+ * Does a system tm value match one we computed ourselves?
127
+ */
60
128
static bool
61
129
compare_tm (struct tm * s , struct pg_tm * p )
62
130
{
@@ -73,12 +141,16 @@ compare_tm(struct tm *s, struct pg_tm *p)
73
141
return true;
74
142
}
75
143
144
+ /*
145
+ * See if a specific timezone setting matches the system behavior
146
+ */
76
147
static bool
77
- try_timezone (char * tzname , struct tztry * tt )
148
+ try_timezone (const char * tzname , struct tztry * tt )
78
149
{
79
150
int i ;
80
151
struct tm * systm ;
81
152
struct pg_tm * pgtm ;
153
+ char cbuf [TZ_STRLEN_MAX + 1 ];
82
154
83
155
if (!pg_tzset (tzname ))
84
156
return false; /* can't handle the TZ name at all */
@@ -92,51 +164,25 @@ try_timezone(char *tzname, struct tztry *tt)
92
164
systm = localtime (& (tt -> test_times [i ]));
93
165
if (!compare_tm (systm , pgtm ))
94
166
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
+ }
95
177
}
96
178
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;
126
182
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;
134
184
}
135
185
136
- #else
137
- #define TZABBREV (tz ) tz
138
- #endif
139
-
140
186
141
187
/*
142
188
* Try to identify a timezone name (in our terminology) that matches the
@@ -155,6 +201,7 @@ identify_system_timezone(void)
155
201
int std_ofs = 0 ;
156
202
struct tztry tt ;
157
203
struct tm * tm ;
204
+ char tmptzdir [MAXPGPATH ];
158
205
char cbuf [TZ_STRLEN_MAX + 1 ];
159
206
160
207
/* Initialize OS timezone library */
@@ -225,6 +272,20 @@ identify_system_timezone(void)
225
272
}
226
273
}
227
274
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
+
228
289
/* We should have found a STD zone name by now... */
229
290
if (tt .std_zone_name [0 ] == '\0' )
230
291
{
@@ -234,7 +295,17 @@ identify_system_timezone(void)
234
295
return NULL ; /* go to GMT */
235
296
}
236
297
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 */
238
309
if (tt .dst_zone_name [0 ] != '\0' )
239
310
{
240
311
snprintf (resultbuf , sizeof (resultbuf ), "%s%d%s" ,
@@ -270,6 +341,96 @@ identify_system_timezone(void)
270
341
return resultbuf ;
271
342
}
272
343
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
+
273
434
/*
274
435
* Check whether timezone is acceptable.
275
436
*
@@ -351,6 +512,6 @@ pg_timezone_initialize(void)
351
512
/* Select setting */
352
513
def_tz = select_default_timezone ();
353
514
/* 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 );
355
516
}
356
517
}
0 commit comments