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, or1970 ..2004 */
35+ #define MAX_TEST_TIMES (52*40 )/*40 years, or1964 ..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{
152161int i ;
153162pg_time_t pgtt ;
@@ -156,59 +165,59 @@ try_timezone(const char *tzname, struct tztry *tt)
156165char cbuf [TZ_STRLEN_MAX + 1 ];
157166
158167if (!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 */
162178for (i = 0 ;i < tt -> n_test_times ;i ++ )
163179{
164180pgtt = (pg_time_t ) (tt -> test_times [i ]);
165181pgtm = pg_localtime (& pgtt );
166182if (!pgtm )
167- return false ;/* probably shouldn't happen */
183+ return -1 ;/* probably shouldn't happen */
168184systm = localtime (& (tt -> test_times [i ]));
169185if (!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 ,
173189pgtm -> tm_year + 1900 ,pgtm -> tm_mon + 1 ,pgtm -> tm_mday ,
174190pgtm -> tm_hour ,pgtm -> tm_min ,pgtm -> tm_sec ,
175191pgtm -> tm_isdst ?"dst" :"std" ,
176192systm -> tm_year + 1900 ,systm -> tm_mon + 1 ,systm -> tm_mday ,
177193systm -> tm_hour ,systm -> tm_min ,systm -> tm_sec ,
178194systm -> tm_isdst ?"dst" :"std" );
179- return false ;
195+ return i ;
180196}
181197if (systm -> tm_isdst >=0 )
182198{
183199/* Check match of zone names, too */
184200if (pgtm -> tm_zone == NULL )
185- return false;
201+ return -1 ; /* probably shouldn't happen */
186202memset (cbuf ,0 ,sizeof (cbuf ));
187203strftime (cbuf ,sizeof (cbuf )- 1 ,"%Z" ,systm );/* zone abbr */
188204if (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 ,
192208pgtm -> 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) thatbest 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)
221230time_t t ;
222231struct tztry tt ;
223232struct tm * tm ;
233+ int bestscore ;
224234char tmptzdir [MAXPGPATH ];
225235int std_ofs ;
226236char std_zone_name [TZ_STRLEN_MAX + 1 ],
@@ -231,36 +241,38 @@ identify_system_timezone(void)
231241tzset ();
232242
233243/*
234- * Set up the list of dates to be probed toverify that our timezone
235- * matches the system zone. We first probe January and July of1970 ;
244+ * Set up the list of dates to be probed tosee how well our timezone
245+ * matches the system zone. We first probe January and July of2004 ;
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 */
246259tt .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 );
249262while (tt .n_test_times < MAX_TEST_TIMES )
250263{
251- t + =T_WEEK ;
264+ t - =T_WEEK ;
252265tt .test_times [tt .n_test_times ++ ]= t ;
253266}
254267
255- /* Search fora matching timezone file */
268+ /* Search forthe best- matching timezone file */
256269strcpy (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 )
262275return 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{
327339snprintf (resultbuf ,sizeof (resultbuf ),"%s%d%s" ,
328340std_zone_name ,- std_ofs /3600 ,dst_zone_name );
329- if (try_timezone (resultbuf ,& tt ))
341+ if (score_timezone (resultbuf ,& tt )> 0 )
330342return resultbuf ;
331343}
332344
333345/* Try just the STD timezone (works for GMT at least) */
334346strcpy (resultbuf ,std_zone_name );
335- if (try_timezone (resultbuf ,& tt ))
347+ if (score_timezone (resultbuf ,& tt )> 0 )
336348return resultbuf ;
337349
338350/* Try STD<ofs> */
339351snprintf (resultbuf ,sizeof (resultbuf ),"%s%d" ,
340352std_zone_name ,- std_ofs /3600 );
341- if (try_timezone (resultbuf ,& tt ))
353+ if (score_timezone (resultbuf ,& tt )> 0 )
342354return resultbuf ;
343355
344356/*
@@ -358,7 +370,7 @@ identify_system_timezone(void)
358370}
359371
360372/*
361- * Recursively scan the timezone database looking fora usable match to
373+ * Recursively scan the timezone database looking forthe 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{
381395int tzdir_orig_len = strlen (tzdir );
382- bool found = false;
383396DIR * dirdesc ;
384397
385398dirdesc = AllocateDir (tzdir );
@@ -388,7 +401,7 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
388401ereport (LOG ,
389402(errcode_for_file_access (),
390403errmsg ("could not open directory \"%s\": %m" ,tzdir )));
391- return false ;
404+ return ;
392405}
393406
394407for (;;)
@@ -432,25 +445,26 @@ scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
432445if (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}
439451else
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
448464FreeDir (dirdesc );
449465
450466/* Restore tzdir */
451467tzdir [tzdir_orig_len ]= '\0' ;
452-
453- return found ;
454468}
455469
456470