1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
|
/*-------------------------------------------------------------------------
*
* pgtz.c
* Timezone Library Integration Functions
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.12 2004/05/23 22:24:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#define NO_REDEFINE_TIMEFUNCS
#include "postgres.h"
#include <ctype.h>
#include "miscadmin.h"
#include "pgtime.h"
#include "pgtz.h"
#include "tzfile.h"
#include "utils/elog.h"
#include "utils/guc.h"
static char tzdir[MAXPGPATH];
static int done_tzdir = 0;
char *
pg_TZDIR(void)
{
if (done_tzdir)
return tzdir;
get_share_path(my_exec_path, tzdir);
strcat(tzdir, "/timezone");
done_tzdir = 1;
return tzdir;
}
/*
* Try to determine the system timezone (as opposed to the timezone
* set in our own library).
*/
#define T_YEAR (60*60*24*365)
#define T_MONTH (60*60*24*30)
struct tztry
{
time_t std_t,
dst_t;
char std_time[TZ_STRLEN_MAX + 1],
dst_time[TZ_STRLEN_MAX + 1];
int std_ofs,
dst_ofs;
struct tm std_tm,
dst_tm;
};
static bool
compare_tm(struct tm * s, struct pg_tm * p)
{
if (s->tm_sec != p->tm_sec ||
s->tm_min != p->tm_min ||
s->tm_hour != p->tm_hour ||
s->tm_mday != p->tm_mday ||
s->tm_mon != p->tm_mon ||
s->tm_year != p->tm_year ||
s->tm_wday != p->tm_wday ||
s->tm_yday != p->tm_yday ||
s->tm_isdst != p->tm_isdst)
return false;
return true;
}
static bool
try_timezone(char *tzname, struct tztry * tt, bool checkdst)
{
struct pg_tm *pgtm;
if (!pg_tzset(tzname))
return false; /* If this timezone couldn't be picked at
* all */
/* Verify standard time */
pgtm = pg_localtime(&(tt->std_t));
if (!pgtm)
return false;
if (!compare_tm(&(tt->std_tm), pgtm))
return false;
if (!checkdst)
return true;
/* Now check daylight time */
pgtm = pg_localtime(&(tt->dst_t));
if (!pgtm)
return false;
if (!compare_tm(&(tt->dst_tm), pgtm))
return false;
return true;
}
static int
get_timezone_offset(struct tm * tm)
{
#if defined(HAVE_STRUCT_TM_TM_ZONE)
return tm->tm_gmtoff;
#elif defined(HAVE_INT_TIMEZONE)
#ifdef HAVE_UNDERSCORE_TIMEZONE
return -_timezone;
#else
return -timezone;
#endif
#else
#error No way to determine TZ? Can this happen?
#endif
}
#ifdef WIN32
#define TZABBREV(tz) win32_get_timezone_abbrev(tz)
static char *
win32_get_timezone_abbrev(char *tz)
{
static char w32tzabbr[TZ_STRLEN_MAX + 1];
int l = 0;
char *c;
for (c = tz; *c; c++)
{
if (isupper(*c))
w32tzabbr[l++] = *c;
}
w32tzabbr[l] = '\0';
return w32tzabbr;
}
#else
#define TZABBREV(tz) tz
#endif
/*
* Try to identify a timezone name (in our terminology) that matches the
* observed behavior of the system timezone library. We cannot assume that
* the system TZ environment setting (if indeed there is one) matches our
* terminology, so ignore it and just look at what localtime() returns.
*/
static char *
identify_system_timezone(void)
{
static char __tzbuf[TZ_STRLEN_MAX + 1];
bool std_found = false,
dst_found = false;
time_t tnow = time(NULL);
time_t t;
struct tztry tt;
char cbuf[TZ_STRLEN_MAX + 1];
/* Initialize OS timezone library */
tzset();
memset(&tt, 0, sizeof(tt));
for (t = tnow; t < tnow + T_YEAR; t += T_MONTH)
{
struct tm *tm = localtime(&t);
if (tm->tm_isdst == 0 && !std_found)
{
/* Standard time */
memcpy(&tt.std_tm, tm, sizeof(struct tm));
memset(cbuf, 0, sizeof(cbuf));
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
strcpy(tt.std_time, TZABBREV(cbuf));
tt.std_ofs = get_timezone_offset(tm);
tt.std_t = t;
std_found = true;
}
else if (tm->tm_isdst == 1 && !dst_found)
{
/* Daylight time */
memcpy(&tt.dst_tm, tm, sizeof(struct tm));
memset(cbuf, 0, sizeof(cbuf));
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
strcpy(tt.dst_time, TZABBREV(cbuf));
tt.dst_ofs = get_timezone_offset(tm);
tt.dst_t = t;
dst_found = true;
}
if (std_found && dst_found)
break; /* Got both standard and daylight */
}
if (!std_found)
{
/* Failed to determine TZ! */
ereport(LOG,
(errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"),
errhint("You can specify the correct timezone in postgresql.conf.")));
return NULL; /* go to GMT */
}
if (dst_found)
{
/* Try STD<ofs>DST */
sprintf(__tzbuf, "%s%d%s", tt.std_time, -tt.std_ofs / 3600, tt.dst_time);
if (try_timezone(__tzbuf, &tt, dst_found))
return __tzbuf;
}
/* Try just the STD timezone */
strcpy(__tzbuf, tt.std_time);
if (try_timezone(__tzbuf, &tt, dst_found))
return __tzbuf;
/* Did not find the timezone. Fallback to try a GMT zone. */
sprintf(__tzbuf, "Etc/GMT%s%d",
(-tt.std_ofs < 0) ? "+" : "", tt.std_ofs / 3600);
ereport(LOG,
(errmsg("could not recognize system timezone, defaulting to \"%s\"",
__tzbuf),
errhint("You can specify the correct timezone in postgresql.conf.")));
return __tzbuf;
}
/*
* Check whether timezone is acceptable.
*
* What we are doing here is checking for leap-second-aware timekeeping.
* We need to reject such TZ settings because they'll wreak havoc with our
* date/time arithmetic.
*
* NB: this must NOT ereport(ERROR). The caller must get control back so that
* it can restore the old value of TZ if we don't like the new one.
*/
bool
tz_acceptable(void)
{
struct pg_tm tt;
time_t time2000;
/*
* To detect leap-second timekeeping, compute the time_t value for
* local midnight, 2000-01-01. Insist that this be a multiple of 60;
* any partial-minute offset has to be due to leap seconds.
*/
MemSet(&tt, 0, sizeof(tt));
tt.tm_year = 100;
tt.tm_mon = 0;
tt.tm_mday = 1;
tt.tm_isdst = -1;
time2000 = pg_mktime(&tt);
if ((time2000 % 60) != 0)
return false;
return true;
}
/*
* Identify a suitable default timezone setting based on the environment,
* and make it active.
*
* We first look to the TZ environment variable. If not found or not
* recognized by our own code, we see if we can identify the timezone
* from the behavior of the system timezone library. When all else fails,
* fall back to GMT.
*/
const char *
select_default_timezone(void)
{
char *def_tz;
def_tz = getenv("TZ");
if (def_tz && pg_tzset(def_tz) && tz_acceptable())
return def_tz;
def_tz = identify_system_timezone();
if (def_tz && pg_tzset(def_tz) && tz_acceptable())
return def_tz;
if (pg_tzset("GMT") && tz_acceptable())
return "GMT";
ereport(FATAL,
(errmsg("could not select a suitable default timezone"),
errdetail("It appears that your GMT time zone uses leap seconds. PostgreSQL does not support leap seconds.")));
return NULL; /* keep compiler quiet */
}
/*
* Initialize timezone library
*
* This is called after initial loading of postgresql.conf. If no TimeZone
* setting was found therein, we try to derive one from the environment.
*/
void
pg_timezone_initialize(void)
{
/* Do we need to try to figure the timezone? */
if (pg_strcasecmp(GetConfigOption("timezone"), "UNKNOWN") == 0)
{
const char *def_tz;
/* Select setting */
def_tz = select_default_timezone();
/* Tell GUC about the value. Will redundantly call pg_tzset() */
SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ENV_VAR);
}
}
|