66 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
77 *
88 * IDENTIFICATION
9- * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.18 2004/07/10 23:06:50 tgl Exp $
9+ * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.19 2004/07/22 05:28:30 tgl Exp $
1010 *
1111 *-------------------------------------------------------------------------
1212 */
3232#define T_WEEK ((time_t) (60*60*24*7))
3333#define T_MONTH ((time_t) (60*60*24*31))
3434
35- #define MAX_TEST_TIMES (52*35 ) /* 35 years, or 1970 ..2004 */
35+ #define MAX_TEST_TIMES (52*40 ) /* 40 years, or 1964 ..2004 */
3636
3737struct tztry
3838{
@@ -43,8 +43,9 @@ struct tztry
4343static char tzdir [MAXPGPATH ];
4444static int done_tzdir = 0 ;
4545
46- static bool scan_available_timezones (char * tzdir , char * tzdirsub ,
47- struct tztry * tt );
46+ static void scan_available_timezones (char * tzdir , char * tzdirsub ,
47+ struct tztry * tt ,
48+ int * bestscore , char * bestzonename );
4849
4950
5051/*
@@ -144,10 +145,18 @@ compare_tm(struct tm *s, struct pg_tm *p)
144145}
145146
146147/*
147- * See if a specific timezone setting matches the system behavior
148+ * See how well a specific timezone setting matches the system behavior
149+ *
150+ * We score a timezone setting according to the number of test times it
151+ * matches. (The test times are ordered later-to-earlier, but this routine
152+ * doesn't actually know that; it just scans until the first non-match.)
153+ *
154+ * We return -1 for a completely unusable setting; this is worse than the
155+ * score of zero for a setting that works but matches not even the first
156+ * test time.
148157 */
149- static bool
150- try_timezone (const char * tzname , struct tztry * tt )
158+ static int
159+ score_timezone (const char * tzname , struct tztry * tt )
151160{
152161 int i ;
153162 pg_time_t pgtt ;
@@ -156,59 +165,59 @@ try_timezone(const char *tzname, struct tztry *tt)
156165 char cbuf [TZ_STRLEN_MAX + 1 ];
157166
158167 if (!pg_tzset (tzname ))
159- return false; /* can't handle the TZ name at all */
168+ return -1 ; /* can't handle the TZ name at all */
169+
170+ /* Reject if leap seconds involved */
171+ if (!tz_acceptable ())
172+ {
173+ elog (DEBUG4 , "Reject TZ \"%s\": uses leap seconds" , tzname );
174+ return -1 ;
175+ }
160176
161177 /* Check for match at all the test times */
162178 for (i = 0 ; i < tt -> n_test_times ; i ++ )
163179 {
164180 pgtt = (pg_time_t ) (tt -> test_times [i ]);
165181 pgtm = pg_localtime (& pgtt );
166182 if (!pgtm )
167- return false ; /* probably shouldn't happen */
183+ return -1 ; /* probably shouldn't happen */
168184 systm = localtime (& (tt -> test_times [i ]));
169185 if (!compare_tm (systm , pgtm ))
170186 {
171- elog (DEBUG4 , "Reject TZ \"%s\": at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s" ,
172- tzname , (long ) pgtt ,
187+ elog (DEBUG4 , "TZ \"%s\" scores %d : at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s" ,
188+ tzname , i , (long ) pgtt ,
173189 pgtm -> tm_year + 1900 , pgtm -> tm_mon + 1 , pgtm -> tm_mday ,
174190 pgtm -> tm_hour , pgtm -> tm_min , pgtm -> tm_sec ,
175191 pgtm -> tm_isdst ? "dst" : "std" ,
176192 systm -> tm_year + 1900 , systm -> tm_mon + 1 , systm -> tm_mday ,
177193 systm -> tm_hour , systm -> tm_min , systm -> tm_sec ,
178194 systm -> tm_isdst ? "dst" : "std" );
179- return false ;
195+ return i ;
180196 }
181197 if (systm -> tm_isdst >= 0 )
182198 {
183199 /* Check match of zone names, too */
184200 if (pgtm -> tm_zone == NULL )
185- return false;
201+ return -1 ; /* probably shouldn't happen */
186202 memset (cbuf , 0 , sizeof (cbuf ));
187203 strftime (cbuf , sizeof (cbuf ) - 1 , "%Z" , systm ); /* zone abbr */
188204 if (strcmp (TZABBREV (cbuf ), pgtm -> tm_zone ) != 0 )
189205 {
190- elog (DEBUG4 , "Reject TZ \"%s\": at %ld \"%s\" versus \"%s\"" ,
191- tzname , (long ) pgtt ,
206+ elog (DEBUG4 , "TZ \"%s\" scores %d : at %ld \"%s\" versus \"%s\"" ,
207+ tzname , i , (long ) pgtt ,
192208 pgtm -> tm_zone , cbuf );
193- return false ;
209+ return i ;
194210 }
195211 }
196212 }
197213
198- /* Reject if leap seconds involved */
199- if (!tz_acceptable ())
200- {
201- elog (DEBUG4 , "Reject TZ \"%s\": uses leap seconds" , tzname );
202- return false;
203- }
204-
205- elog (DEBUG4 , "Accept TZ \"%s\"" , tzname );
206- return true;
214+ elog (DEBUG4 , "TZ \"%s\" gets max score %d" , tzname , i );
215+ return i ;
207216}
208217
209218
210219/*
211- * Try to identify a timezone name (in our terminology) that matches the
220+ * Try to identify a timezone name (in our terminology) that best matches the
212221 * observed behavior of the system timezone library. We cannot assume that
213222 * the system TZ environment setting (if indeed there is one) matches our
214223 * terminology, so we ignore it and just look at what localtime() returns.
@@ -221,6 +230,7 @@ identify_system_timezone(void)
221230 time_t t ;
222231 struct tztry tt ;
223232 struct tm * tm ;
233+ int bestscore ;
224234 char tmptzdir [MAXPGPATH ];
225235 int std_ofs ;
226236 char std_zone_name [TZ_STRLEN_MAX + 1 ],
@@ -231,36 +241,38 @@ identify_system_timezone(void)
231241 tzset ();
232242
233243 /*
234- * Set up the list of dates to be probed to verify that our timezone
235- * matches the system zone. We first probe January and July of 1970 ;
244+ * Set up the list of dates to be probed to see how well our timezone
245+ * matches the system zone. We first probe January and July of 2004 ;
236246 * this serves to quickly eliminate the vast majority of the TZ database
237- * entries. If those dates match, we probe every week from 1970 to
238- * late 2004. This exhaustive test is intended to ensure that we have
239- * the same DST transition rules as the system timezone. (Note: we
240- * probe Thursdays, not Sundays, to avoid triggering DST-transition
241- * bugs in localtime itself.)
242- *
243- * Ideally we'd probe some dates before 1970 too, but that is guaranteed
244- * to fail if the system TZ library doesn't cope with DST before 1970.
247+ * entries. If those dates match, we probe every week from 2004 backwards
248+ * to late 1964. (Weekly resolution is good enough to identify DST
249+ * transition rules, since everybody switches on Sundays.) The further
250+ * back the zone matches, the better we score it. This may seem like
251+ * a rather random way of doing things, but experience has shown that
252+ * system-supplied timezone definitions are likely to have DST behavior
253+ * that is right for the recent past and not so accurate further back.
254+ * Scoring in this way allows us to recognize zones that have some
255+ * commonality with the zic database, without insisting on exact match.
256+ * (Note: we probe Thursdays, not Sundays, to avoid triggering
257+ * DST-transition bugs in localtime itself.)
245258 */
246259 tt .n_test_times = 0 ;
247- tt .test_times [tt .n_test_times ++ ] = t = build_time_t (1970 , 1 , 15 );
248- tt .test_times [tt .n_test_times ++ ] = build_time_t (1970 , 7 , 15 );
260+ tt .test_times [tt .n_test_times ++ ] = build_time_t (2004 , 1 , 15 );
261+ tt .test_times [tt .n_test_times ++ ] = t = build_time_t (2004 , 7 , 15 );
249262 while (tt .n_test_times < MAX_TEST_TIMES )
250263 {
251- t + = T_WEEK ;
264+ t - = T_WEEK ;
252265 tt .test_times [tt .n_test_times ++ ] = t ;
253266 }
254267
255- /* Search for a matching timezone file */
268+ /* Search for the best- matching timezone file */
256269 strcpy (tmptzdir , pg_TZDIR ());
257- if ( scan_available_timezones ( tmptzdir ,
258- tmptzdir + strlen (tmptzdir ) + 1 ,
259- & tt ))
260- {
261- StrNCpy ( resultbuf , pg_get_current_timezone (), sizeof ( resultbuf ));
270+ bestscore = 0 ;
271+ scan_available_timezones ( tmptzdir , tmptzdir + strlen (tmptzdir ) + 1 ,
272+ & tt ,
273+ & bestscore , resultbuf );
274+ if ( bestscore > 0 )
262275 return resultbuf ;
263- }
264276
265277 /*
266278 * Couldn't find a match in the database, so next we try constructed zone
@@ -326,19 +338,19 @@ identify_system_timezone(void)
326338 {
327339 snprintf (resultbuf , sizeof (resultbuf ), "%s%d%s" ,
328340 std_zone_name , - std_ofs / 3600 , dst_zone_name );
329- if (try_timezone (resultbuf , & tt ))
341+ if (score_timezone (resultbuf , & tt ) > 0 )
330342 return resultbuf ;
331343 }
332344
333345 /* Try just the STD timezone (works for GMT at least) */
334346 strcpy (resultbuf , std_zone_name );
335- if (try_timezone (resultbuf , & tt ))
347+ if (score_timezone (resultbuf , & tt ) > 0 )
336348 return resultbuf ;
337349
338350 /* Try STD<ofs> */
339351 snprintf (resultbuf , sizeof (resultbuf ), "%s%d" ,
340352 std_zone_name , - std_ofs / 3600 );
341- if (try_timezone (resultbuf , & tt ))
353+ if (score_timezone (resultbuf , & tt ) > 0 )
342354 return resultbuf ;
343355
344356 /*
@@ -358,7 +370,7 @@ identify_system_timezone(void)
358370}
359371
360372/*
361- * Recursively scan the timezone database looking for a usable match to
373+ * Recursively scan the timezone database looking for the best match to
362374 * the system timezone behavior.
363375 *
364376 * tzdir points to a buffer of size MAXPGPATH. On entry, it holds the
@@ -372,14 +384,15 @@ identify_system_timezone(void)
372384 *
373385 * tt tells about the system timezone behavior we need to match.
374386 *
375- * On success, returns TRUE leaving the proper timezone selected.
376- * On failure, returns FALSE with a random timezone selected.
387+ * *bestscore and *bestzonename on entry hold the best score found so far
388+ * and the name of the best zone. We overwrite them if we find a better
389+ * score. bestzonename must be a buffer of length TZ_STRLEN_MAX + 1.
377390 */
378- static bool
379- scan_available_timezones (char * tzdir , char * tzdirsub , struct tztry * tt )
391+ static void
392+ scan_available_timezones (char * tzdir , char * tzdirsub , struct tztry * tt ,
393+ int * bestscore , char * bestzonename )
380394{
381395 int tzdir_orig_len = strlen (tzdir );
382- bool found = false;
383396 DIR * dirdesc ;
384397
385398 dirdesc = AllocateDir (tzdir );
@@ -388,7 +401,7 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
388401 ereport (LOG ,
389402 (errcode_for_file_access (),
390403 errmsg ("could not open directory \"%s\": %m" , tzdir )));
391- return false ;
404+ return ;
392405 }
393406
394407 for (;;)
@@ -432,25 +445,26 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
432445 if (S_ISDIR (statbuf .st_mode ))
433446 {
434447 /* Recurse into subdirectory */
435- found = scan_available_timezones (tzdir , tzdirsub , tt );
436- if (found )
437- break ;
448+ scan_available_timezones (tzdir , tzdirsub , tt ,
449+ bestscore , bestzonename );
438450 }
439451 else
440452 {
441453 /* Load and test this file */
442- found = try_timezone (tzdirsub , tt );
443- if (found )
444- break ;
454+ int score = score_timezone (tzdirsub , tt );
455+
456+ if (score > * bestscore )
457+ {
458+ * bestscore = score ;
459+ StrNCpy (bestzonename , tzdirsub , TZ_STRLEN_MAX + 1 );
460+ }
445461 }
446462 }
447463
448464 FreeDir (dirdesc );
449465
450466 /* Restore tzdir */
451467 tzdir [tzdir_orig_len ] = '\0' ;
452-
453- return found ;
454468}
455469
456470
0 commit comments