66 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
77 *
88 * IDENTIFICATION
9- * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.13 2004/05/23 23:26:53 tgl Exp $
9+ * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.14 2004/05/24 02:30:29 tgl Exp $
1010 *
1111 *-------------------------------------------------------------------------
1212 */
@@ -44,24 +44,21 @@ pg_TZDIR(void)
4444 * Try to determine the system timezone (as opposed to the timezone
4545 * set in our own library).
4646 */
47- #define T_YEAR ( 60*60*24*365 )
48- #define T_MONTH (60*60*24*30 )
47+ #define T_DAY ((time_t) ( 60*60*24) )
48+ #define T_MONTH ((time_t) ( 60*60*24*31) )
4949
5050struct tztry
5151{
52- time_t std_t ,
53- dst_t ;
54- char std_time [TZ_STRLEN_MAX + 1 ],
55- dst_time [TZ_STRLEN_MAX + 1 ];
56- int std_ofs ,
57- dst_ofs ;
58- struct tm std_tm ,
59- dst_tm ;
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 ];
6057};
6158
6259
6360static bool
64- compare_tm (struct tm * s , struct pg_tm * p )
61+ compare_tm (struct tm * s , struct pg_tm * p )
6562{
6663 if (s -> tm_sec != p -> tm_sec ||
6764 s -> tm_min != p -> tm_min ||
@@ -77,36 +74,31 @@ compare_tm(struct tm * s, struct pg_tm * p)
7774}
7875
7976static bool
80- try_timezone (char * tzname , struct tztry * tt , bool checkdst )
77+ try_timezone (char * tzname , struct tztry * tt )
8178{
82- struct pg_tm * pgtm ;
79+ int i ;
80+ struct tm * systm ;
81+ struct pg_tm * pgtm ;
8382
8483 if (!pg_tzset (tzname ))
85- return false; /* If this timezone couldn't be picked at
86- * all */
84+ return false; /* can't handle the TZ name at all */
8785
88- /* Verify standard time */
89- pgtm = pg_localtime (& (tt -> std_t ));
90- if (!pgtm )
91- return false;
92- if (!compare_tm (& (tt -> std_tm ), pgtm ))
93- return false;
94-
95- if (!checkdst )
96- return true;
97-
98- /* Now check daylight time */
99- pgtm = pg_localtime (& (tt -> dst_t ));
100- if (!pgtm )
101- return false;
102- if (!compare_tm (& (tt -> dst_tm ), pgtm ))
103- return false;
86+ /* Check for match at all the test times */
87+ for (i = 0 ; i < tt -> n_test_times ; i ++ )
88+ {
89+ pgtm = pg_localtime (& (tt -> test_times [i ]));
90+ if (!pgtm )
91+ return false; /* probably shouldn't happen */
92+ systm = localtime (& (tt -> test_times [i ]));
93+ if (!compare_tm (systm , pgtm ))
94+ return false;
95+ }
10496
10597 return true;
10698}
10799
108100static int
109- get_timezone_offset (struct tm * tm )
101+ get_timezone_offset (struct tm * tm )
110102{
111103#if defined(HAVE_STRUCT_TM_TM_ZONE )
112104 return tm -> tm_gmtoff ;
@@ -150,88 +142,132 @@ win32_get_timezone_abbrev(char *tz)
150142 * Try to identify a timezone name (in our terminology) that matches the
151143 * observed behavior of the system timezone library. We cannot assume that
152144 * the system TZ environment setting (if indeed there is one) matches our
153- * terminology, so ignore it and just look at what localtime() returns.
145+ * terminology, so we ignore it and just look at what localtime() returns.
154146 */
155147static char *
156148identify_system_timezone (void )
157149{
158- static char __tzbuf [TZ_STRLEN_MAX + 1 ];
159- bool std_found = false,
160- dst_found = false;
161- time_t tnow = time (NULL );
150+ static char resultbuf [TZ_STRLEN_MAX + 1 ];
151+ time_t tnow ;
162152 time_t t ;
153+ int nowisdst ,
154+ curisdst ;
155+ int std_ofs = 0 ;
163156 struct tztry tt ;
157+ struct tm * tm ;
164158 char cbuf [TZ_STRLEN_MAX + 1 ];
165159
166160 /* Initialize OS timezone library */
167161 tzset ();
168162
163+ /* No info yet */
169164 memset (& tt , 0 , sizeof (tt ));
170165
171- for (t = tnow ; t < tnow + T_YEAR ; t += T_MONTH )
166+ /*
167+ * The idea here is to scan forward from today and try to locate the
168+ * next two daylight-savings transition boundaries. We will test for
169+ * correct results on the day before and after each boundary; this
170+ * gives at least some confidence that we've selected the right DST
171+ * rule set.
172+ */
173+ tnow = time (NULL );
174+
175+ /*
176+ * Round back to a GMT midnight so results don't depend on local time
177+ * of day
178+ */
179+ tnow -= (tnow % T_DAY );
180+
181+ /* Always test today, so we have at least one test point */
182+ tt .test_times [tt .n_test_times ++ ] = tnow ;
183+
184+ tm = localtime (& tnow );
185+ nowisdst = tm -> tm_isdst ;
186+ curisdst = nowisdst ;
187+
188+ if (curisdst == 0 )
172189 {
173- struct tm * tm = localtime (& t );
190+ /* Set up STD zone name, in case we are in a non-DST zone */
191+ memset (cbuf , 0 , sizeof (cbuf ));
192+ strftime (cbuf , sizeof (cbuf ) - 1 , "%Z" , tm ); /* zone abbr */
193+ strcpy (tt .std_zone_name , TZABBREV (cbuf ));
194+ /* Also preset std_ofs */
195+ std_ofs = get_timezone_offset (tm );
196+ }
174197
175- if (tm -> tm_isdst == 0 && !std_found )
176- {
177- /* Standard time */
178- memcpy (& tt .std_tm , tm , sizeof (struct tm ));
179- memset (cbuf , 0 , sizeof (cbuf ));
180- strftime (cbuf , sizeof (cbuf ) - 1 , "%Z" , tm ); /* zone abbr */
181- strcpy (tt .std_time , TZABBREV (cbuf ));
182- tt .std_ofs = get_timezone_offset (tm );
183- tt .std_t = t ;
184- std_found = true;
185- }
186- else if (tm -> tm_isdst == 1 && !dst_found )
198+ /*
199+ * We have to look a little further ahead than one year, in case today
200+ * is just past a DST boundary that falls earlier in the year than the
201+ * next similar boundary. Arbitrarily scan up to 14 months.
202+ */
203+ for (t = tnow + T_DAY ; t < tnow + T_MONTH * 14 ; t += T_DAY )
204+ {
205+ tm = localtime (& t );
206+ if (tm -> tm_isdst >= 0 && tm -> tm_isdst != curisdst )
187207 {
188- /* Daylight time */
189- memcpy (& tt .dst_tm , tm , sizeof (struct tm ));
208+ /* Found a boundary */
209+ tt .test_times [tt .n_test_times ++ ] = t - T_DAY ;
210+ tt .test_times [tt .n_test_times ++ ] = t ;
211+ curisdst = tm -> tm_isdst ;
212+ /* Save STD or DST zone name, also std_ofs */
190213 memset (cbuf , 0 , sizeof (cbuf ));
191214 strftime (cbuf , sizeof (cbuf ) - 1 , "%Z" , tm ); /* zone abbr */
192- strcpy (tt .dst_time , TZABBREV (cbuf ));
193- tt .dst_ofs = get_timezone_offset (tm );
194- tt .dst_t = t ;
195- dst_found = true;
215+ if (curisdst == 0 )
216+ {
217+ strcpy (tt .std_zone_name , TZABBREV (cbuf ));
218+ std_ofs = get_timezone_offset (tm );
219+ }
220+ else
221+ strcpy (tt .dst_zone_name , TZABBREV (cbuf ));
222+ /* Have we found two boundaries? */
223+ if (tt .n_test_times >= 5 )
224+ break ;
196225 }
197- if (std_found && dst_found )
198- break ; /* Got both standard and daylight */
199226 }
200227
201- if (!std_found )
228+ /* We should have found a STD zone name by now... */
229+ if (tt .std_zone_name [0 ] == '\0' )
202230 {
203- /* Failed to determine TZ! */
204231 ereport (LOG ,
205232 (errmsg ("unable to determine system timezone, defaulting to \"%s\"" , "GMT" ),
206233 errhint ("You can specify the correct timezone in postgresql.conf." )));
207234 return NULL ; /* go to GMT */
208235 }
209236
210- if (dst_found )
237+ /* If we found DST too then try STD<ofs>DST */
238+ if (tt .dst_zone_name [0 ] != '\0' )
211239 {
212- /* Try STD<ofs>DST */
213- sprintf ( __tzbuf , "%s%d%s" , tt .std_time , - tt . std_ofs / 3600 , tt .dst_time );
214- if (try_timezone (__tzbuf , & tt , dst_found ))
215- return __tzbuf ;
240+ snprintf ( resultbuf , sizeof ( resultbuf ), "%s%d%s" ,
241+ tt .std_zone_name , - std_ofs / 3600 , tt .dst_zone_name );
242+ if (try_timezone (resultbuf , & tt ))
243+ return resultbuf ;
216244 }
217- /* Try just the STD timezone */
218- strcpy (__tzbuf , tt .std_time );
219- if (try_timezone (__tzbuf , & tt , dst_found ))
220- return __tzbuf ;
245+
246+ /* Try just the STD timezone (works for GMT at least) */
247+ strcpy (resultbuf , tt .std_zone_name );
248+ if (try_timezone (resultbuf , & tt ))
249+ return resultbuf ;
250+
251+ /* Try STD<ofs> */
252+ snprintf (resultbuf , sizeof (resultbuf ), "%s%d" ,
253+ tt .std_zone_name , - std_ofs / 3600 );
254+ if (try_timezone (resultbuf , & tt ))
255+ return resultbuf ;
221256
222257 /*
223- * Did not find the timezone. Fallback to try a GMT zone. Note that the
258+ * Did not find the timezone. Fallback to use a GMT zone. Note that the
224259 * zic timezone database names the GMT-offset zones in POSIX style: plus
225260 * is west of Greenwich. It's unfortunate that this is opposite of SQL
226261 * conventions. Should we therefore change the names? Probably not...
227262 */
228- sprintf (__tzbuf , "Etc/GMT%s%d" ,
229- (- tt .std_ofs > 0 ) ? "+" : "" , - tt .std_ofs / 3600 );
263+ snprintf (resultbuf , sizeof (resultbuf ), "Etc/GMT%s%d" ,
264+ (- std_ofs > 0 ) ? "+" : "" , - std_ofs / 3600 );
265+
230266 ereport (LOG ,
231- (errmsg ("could not recognize system timezone, defaulting to \"%s\"" ,
232- __tzbuf ),
233- errhint ("You can specify the correct timezone in postgresql.conf." )));
234- return __tzbuf ;
267+ (errmsg ("could not recognize system timezone, defaulting to \"%s\"" ,
268+ resultbuf ),
269+ errhint ("You can specify the correct timezone in postgresql.conf." )));
270+ return resultbuf ;
235271}
236272
237273/*
0 commit comments