66 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
77 *
88 * IDENTIFICATION
9- * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.17 2004/06/03 02:08:07 tgl Exp $
9+ * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.18 2004/07/10 23:06:50 tgl Exp $
1010 *
1111 *-------------------------------------------------------------------------
1212 */
2929
3030
3131#define T_DAY ((time_t) (60*60*24))
32+ #define T_WEEK ((time_t) (60*60*24*7))
3233#define T_MONTH ((time_t) (60*60*24*31))
3334
35+ #define MAX_TEST_TIMES (52*35)/* 35 years, or 1970..2004 */
36+
3437struct tztry
3538{
36- char std_zone_name [TZ_STRLEN_MAX + 1 ],
37- dst_zone_name [TZ_STRLEN_MAX + 1 ];
38- #define MAX_TEST_TIMES 10
3939int n_test_times ;
4040time_t test_times [MAX_TEST_TIMES ];
4141};
@@ -219,27 +219,61 @@ identify_system_timezone(void)
219219static char resultbuf [TZ_STRLEN_MAX + 1 ];
220220time_t tnow ;
221221time_t t ;
222- int nowisdst ,
223- curisdst ;
224- int std_ofs = 0 ;
225222struct tztry tt ;
226223struct tm * tm ;
227224char tmptzdir [MAXPGPATH ];
225+ int std_ofs ;
226+ char std_zone_name [TZ_STRLEN_MAX + 1 ],
227+ dst_zone_name [TZ_STRLEN_MAX + 1 ];
228228char cbuf [TZ_STRLEN_MAX + 1 ];
229229
230230/* Initialize OS timezone library */
231231tzset ();
232232
233- /* No info yet */
234- memset (& tt ,0 ,sizeof (tt ));
233+ /*
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;
236+ * 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.
245+ */
246+ 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 );
249+ while (tt .n_test_times < MAX_TEST_TIMES )
250+ {
251+ t += T_WEEK ;
252+ tt .test_times [tt .n_test_times ++ ]= t ;
253+ }
254+
255+ /* Search for a matching timezone file */
256+ 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 ));
262+ return resultbuf ;
263+ }
235264
236265/*
237- * The idea here is to scan forward from today and try to locate the
238- * next two daylight-savings transition boundaries. We will test for
239- * correct results on the day before and after each boundary; this
240- * gives at least some confidence that we've selected the right DST
241- * rule set.
266+ * Couldn't find a match in the database, so next we try constructed zone
267+ * names (like "PST8PDT").
268+ *
269+ * First we need to determine the names of the local standard and daylight
270+ * zones. The idea here is to scan forward from today until we have
271+ * seen both zones, if both are in use.
242272 */
273+ memset (std_zone_name ,0 ,sizeof (std_zone_name ));
274+ memset (dst_zone_name ,0 ,sizeof (dst_zone_name ));
275+ std_ofs = 0 ;
276+
243277tnow = time (NULL );
244278
245279/*
@@ -248,103 +282,62 @@ identify_system_timezone(void)
248282 */
249283tnow -= (tnow %T_DAY );
250284
251- /* Always test today, so we have at least one test point */
252- tt .test_times [tt .n_test_times ++ ]= tnow ;
253-
254- tm = localtime (& tnow );
255- nowisdst = tm -> tm_isdst ;
256- curisdst = nowisdst ;
257-
258- if (curisdst == 0 )
259- {
260- /* Set up STD zone name, in case we are in a non-DST zone */
261- memset (cbuf ,0 ,sizeof (cbuf ));
262- strftime (cbuf ,sizeof (cbuf )- 1 ,"%Z" ,tm );/* zone abbr */
263- strcpy (tt .std_zone_name ,TZABBREV (cbuf ));
264- /* Also preset std_ofs */
265- std_ofs = get_timezone_offset (tm );
266- }
267-
268285/*
269286 * We have to look a little further ahead than one year, in case today
270287 * is just past a DST boundary that falls earlier in the year than the
271288 * next similar boundary. Arbitrarily scan up to 14 months.
272289 */
273- for (t = tnow + T_DAY ;t < tnow + T_MONTH * 14 ;t += T_DAY )
290+ for (t = tnow ;t <= tnow + T_MONTH * 14 ;t += T_MONTH )
274291{
275292tm = localtime (& t );
276- if (tm -> tm_isdst >=0 && tm -> tm_isdst != curisdst )
293+ if (tm -> tm_isdst < 0 )
294+ continue ;
295+ if (tm -> tm_isdst == 0 && std_zone_name [0 ]== '\0' )
277296{
278- /* Found a boundary */
279- tt .test_times [tt .n_test_times ++ ]= t - T_DAY ;
280- tt .test_times [tt .n_test_times ++ ]= t ;
281- curisdst = tm -> tm_isdst ;
282- /* Save STD or DST zone name, also std_ofs */
297+ /* found STD zone */
283298memset (cbuf ,0 ,sizeof (cbuf ));
284299strftime (cbuf ,sizeof (cbuf )- 1 ,"%Z" ,tm );/* zone abbr */
285- if (curisdst == 0 )
286- {
287- strcpy (tt .std_zone_name ,TZABBREV (cbuf ));
288- std_ofs = get_timezone_offset (tm );
289- }
290- else
291- strcpy (tt .dst_zone_name ,TZABBREV (cbuf ));
292- /* Have we found two boundaries? */
293- if (tt .n_test_times >=5 )
294- break ;
300+ strcpy (std_zone_name ,TZABBREV (cbuf ));
301+ std_ofs = get_timezone_offset (tm );
295302}
303+ if (tm -> tm_isdst > 0 && dst_zone_name [0 ]== '\0' )
304+ {
305+ /* found DST zone */
306+ memset (cbuf ,0 ,sizeof (cbuf ));
307+ strftime (cbuf ,sizeof (cbuf )- 1 ,"%Z" ,tm );/* zone abbr */
308+ strcpy (dst_zone_name ,TZABBREV (cbuf ));
309+ }
310+ /* Done if found both */
311+ if (std_zone_name [0 ]&& dst_zone_name [0 ])
312+ break ;
296313}
297314
298- /*
299- * Add a couple of historical dates as well; without this we are likely
300- * to choose an accidental match, such as Antartica/Palmer when we
301- * really want America/Santiago. Ideally we'd probe some dates before
302- * 1970 too, but that is guaranteed to fail if the system TZ library
303- * doesn't cope with DST before 1970.
304- */
305- tt .test_times [tt .n_test_times ++ ]= build_time_t (1970 ,1 ,15 );
306- tt .test_times [tt .n_test_times ++ ]= build_time_t (1970 ,7 ,15 );
307- tt .test_times [tt .n_test_times ++ ]= build_time_t (1990 ,4 ,1 );
308- tt .test_times [tt .n_test_times ++ ]= build_time_t (1990 ,10 ,1 );
309-
310- Assert (tt .n_test_times <=MAX_TEST_TIMES );
311-
312315/* We should have found a STD zone name by now... */
313- if (tt . std_zone_name [0 ]== '\0' )
316+ if (std_zone_name [0 ]== '\0' )
314317{
315318ereport (LOG ,
316319(errmsg ("unable to determine system timezone, defaulting to \"%s\"" ,"GMT" ),
317320errhint ("You can specify the correct timezone in postgresql.conf." )));
318321return NULL ;/* go to GMT */
319322}
320323
321- /* Search for a matching timezone file */
322- strcpy (tmptzdir ,pg_TZDIR ());
323- if (scan_available_timezones (tmptzdir ,
324- tmptzdir + strlen (tmptzdir )+ 1 ,
325- & tt ))
326- {
327- StrNCpy (resultbuf ,pg_get_current_timezone (),sizeof (resultbuf ));
328- return resultbuf ;
329- }
330-
331324/* If we found DST then try STD<ofs>DST */
332- if (tt . dst_zone_name [0 ]!= '\0' )
325+ if (dst_zone_name [0 ]!= '\0' )
333326{
334327snprintf (resultbuf ,sizeof (resultbuf ),"%s%d%s" ,
335- tt . std_zone_name ,- std_ofs /3600 ,tt . dst_zone_name );
328+ std_zone_name ,- std_ofs /3600 ,dst_zone_name );
336329if (try_timezone (resultbuf ,& tt ))
337330return resultbuf ;
338331}
339332
340333/* Try just the STD timezone (works for GMT at least) */
341- strcpy (resultbuf ,tt . std_zone_name );
334+ strcpy (resultbuf ,std_zone_name );
342335if (try_timezone (resultbuf ,& tt ))
343336return resultbuf ;
344337
345338/* Try STD<ofs> */
346339snprintf (resultbuf ,sizeof (resultbuf ),"%s%d" ,
347- tt . std_zone_name ,- std_ofs /3600 );
340+ std_zone_name ,- std_ofs /3600 );
348341if (try_timezone (resultbuf ,& tt ))
349342return resultbuf ;
350343