1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * findtimezone.c |
4 | * Functions for determining the default timezone to use. |
5 | * |
6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
7 | * |
8 | * IDENTIFICATION |
9 | * src/bin/initdb/findtimezone.c |
10 | * |
11 | *------------------------------------------------------------------------- |
12 | */ |
13 | #include "postgres_fe.h" |
14 | |
15 | #include <fcntl.h> |
16 | #include <sys/stat.h> |
17 | #include <time.h> |
18 | #include <unistd.h> |
19 | |
20 | #include "pgtz.h" |
21 | |
22 | /* Ideally this would be in a .h file, but it hardly seems worth the trouble */ |
23 | extern const char *select_default_timezone(const char *share_path); |
24 | |
25 | |
26 | #ifndef SYSTEMTZDIR |
27 | static char tzdirpath[MAXPGPATH]; |
28 | #endif |
29 | |
30 | |
31 | /* |
32 | * Return full pathname of timezone data directory |
33 | * |
34 | * In this file, tzdirpath is assumed to be set up by select_default_timezone. |
35 | */ |
36 | static const char * |
37 | pg_TZDIR(void) |
38 | { |
39 | #ifndef SYSTEMTZDIR |
40 | /* normal case: timezone stuff is under our share dir */ |
41 | return tzdirpath; |
42 | #else |
43 | /* we're configured to use system's timezone database */ |
44 | return SYSTEMTZDIR; |
45 | #endif |
46 | } |
47 | |
48 | |
49 | /* |
50 | * Given a timezone name, open() the timezone data file. Return the |
51 | * file descriptor if successful, -1 if not. |
52 | * |
53 | * This is simpler than the backend function of the same name because |
54 | * we assume that the input string has the correct case already, so there |
55 | * is no need for case-folding. (This is obviously true if we got the file |
56 | * name from the filesystem to start with. The only other place it can come |
57 | * from is the environment variable TZ, and there seems no need to allow |
58 | * case variation in that; other programs aren't likely to.) |
59 | * |
60 | * If "canonname" is not NULL, then on success the canonical spelling of the |
61 | * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!). |
62 | * This is redundant but kept for compatibility with the backend code. |
63 | */ |
64 | int |
65 | pg_open_tzfile(const char *name, char *canonname) |
66 | { |
67 | char fullname[MAXPGPATH]; |
68 | |
69 | if (canonname) |
70 | strlcpy(canonname, name, TZ_STRLEN_MAX + 1); |
71 | |
72 | strlcpy(fullname, pg_TZDIR(), sizeof(fullname)); |
73 | if (strlen(fullname) + 1 + strlen(name) >= MAXPGPATH) |
74 | return -1; /* not gonna fit */ |
75 | strcat(fullname, "/" ); |
76 | strcat(fullname, name); |
77 | |
78 | return open(fullname, O_RDONLY | PG_BINARY, 0); |
79 | } |
80 | |
81 | |
82 | |
83 | /* |
84 | * Load a timezone definition. |
85 | * Does not verify that the timezone is acceptable! |
86 | * |
87 | * This corresponds to the backend's pg_tzset(), except that we only support |
88 | * one loaded timezone at a time. |
89 | */ |
90 | static pg_tz * |
91 | pg_load_tz(const char *name) |
92 | { |
93 | static pg_tz tz; |
94 | |
95 | if (strlen(name) > TZ_STRLEN_MAX) |
96 | return NULL; /* not going to fit */ |
97 | |
98 | /* |
99 | * "GMT" is always sent to tzparse(); see comments for pg_tzset(). |
100 | */ |
101 | if (strcmp(name, "GMT" ) == 0) |
102 | { |
103 | if (!tzparse(name, &tz.state, true)) |
104 | { |
105 | /* This really, really should not happen ... */ |
106 | return NULL; |
107 | } |
108 | } |
109 | else if (tzload(name, NULL, &tz.state, true) != 0) |
110 | { |
111 | if (name[0] == ':' || !tzparse(name, &tz.state, false)) |
112 | { |
113 | return NULL; /* unknown timezone */ |
114 | } |
115 | } |
116 | |
117 | strcpy(tz.TZname, name); |
118 | |
119 | return &tz; |
120 | } |
121 | |
122 | |
123 | /* |
124 | * The following block of code attempts to determine which timezone in our |
125 | * timezone database is the best match for the active system timezone. |
126 | * |
127 | * On most systems, we rely on trying to match the observable behavior of |
128 | * the C library's localtime() function. The database zone that matches |
129 | * furthest into the past is the one to use. Often there will be several |
130 | * zones with identical rankings (since the IANA database assigns multiple |
131 | * names to many zones). We break ties by first checking for "preferred" |
132 | * names (such as "UTC"), and then arbitrarily by preferring shorter, then |
133 | * alphabetically earlier zone names. (If we did not explicitly prefer |
134 | * "UTC", we would get the alias name "UCT" instead due to alphabetic |
135 | * ordering.) |
136 | * |
137 | * Many modern systems use the IANA database, so if we can determine the |
138 | * system's idea of which zone it is using and its behavior matches our zone |
139 | * of the same name, we can skip the rather-expensive search through all the |
140 | * zones in our database. This short-circuit path also ensures that we spell |
141 | * the zone name the same way the system setting does, even in the presence |
142 | * of multiple aliases for the same zone. |
143 | * |
144 | * Win32's native knowledge about timezones appears to be too incomplete |
145 | * and too different from the IANA database for the above matching strategy |
146 | * to be of any use. But there is just a limited number of timezones |
147 | * available, so we can rely on a handmade mapping table instead. |
148 | */ |
149 | |
150 | #ifndef WIN32 |
151 | |
152 | #define T_DAY ((time_t) (60*60*24)) |
153 | #define T_WEEK ((time_t) (60*60*24*7)) |
154 | #define T_MONTH ((time_t) (60*60*24*31)) |
155 | |
156 | #define MAX_TEST_TIMES (52*100) /* 100 years */ |
157 | |
158 | struct tztry |
159 | { |
160 | int n_test_times; |
161 | time_t test_times[MAX_TEST_TIMES]; |
162 | }; |
163 | |
164 | static bool check_system_link_file(const char *linkname, struct tztry *tt, |
165 | char *bestzonename); |
166 | static void scan_available_timezones(char *tzdir, char *tzdirsub, |
167 | struct tztry *tt, |
168 | int *bestscore, char *bestzonename); |
169 | |
170 | |
171 | /* |
172 | * Get GMT offset from a system struct tm |
173 | */ |
174 | static int |
175 | get_timezone_offset(struct tm *tm) |
176 | { |
177 | #if defined(HAVE_STRUCT_TM_TM_ZONE) |
178 | return tm->tm_gmtoff; |
179 | #elif defined(HAVE_INT_TIMEZONE) |
180 | return -TIMEZONE_GLOBAL; |
181 | #else |
182 | #error No way to determine TZ? Can this happen? |
183 | #endif |
184 | } |
185 | |
186 | /* |
187 | * Convenience subroutine to convert y/m/d to time_t (NOT pg_time_t) |
188 | */ |
189 | static time_t |
190 | build_time_t(int year, int month, int day) |
191 | { |
192 | struct tm tm; |
193 | |
194 | memset(&tm, 0, sizeof(tm)); |
195 | tm.tm_mday = day; |
196 | tm.tm_mon = month - 1; |
197 | tm.tm_year = year - 1900; |
198 | |
199 | return mktime(&tm); |
200 | } |
201 | |
202 | /* |
203 | * Does a system tm value match one we computed ourselves? |
204 | */ |
205 | static bool |
206 | compare_tm(struct tm *s, struct pg_tm *p) |
207 | { |
208 | if (s->tm_sec != p->tm_sec || |
209 | s->tm_min != p->tm_min || |
210 | s->tm_hour != p->tm_hour || |
211 | s->tm_mday != p->tm_mday || |
212 | s->tm_mon != p->tm_mon || |
213 | s->tm_year != p->tm_year || |
214 | s->tm_wday != p->tm_wday || |
215 | s->tm_yday != p->tm_yday || |
216 | s->tm_isdst != p->tm_isdst) |
217 | return false; |
218 | return true; |
219 | } |
220 | |
221 | /* |
222 | * See how well a specific timezone setting matches the system behavior |
223 | * |
224 | * We score a timezone setting according to the number of test times it |
225 | * matches. (The test times are ordered later-to-earlier, but this routine |
226 | * doesn't actually know that; it just scans until the first non-match.) |
227 | * |
228 | * We return -1 for a completely unusable setting; this is worse than the |
229 | * score of zero for a setting that works but matches not even the first |
230 | * test time. |
231 | */ |
232 | static int |
233 | score_timezone(const char *tzname, struct tztry *tt) |
234 | { |
235 | int i; |
236 | pg_time_t pgtt; |
237 | struct tm *systm; |
238 | struct pg_tm *pgtm; |
239 | char cbuf[TZ_STRLEN_MAX + 1]; |
240 | pg_tz *tz; |
241 | |
242 | /* Load timezone definition */ |
243 | tz = pg_load_tz(tzname); |
244 | if (!tz) |
245 | return -1; /* unrecognized zone name */ |
246 | |
247 | /* Reject if leap seconds involved */ |
248 | if (!pg_tz_acceptable(tz)) |
249 | { |
250 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
251 | fprintf(stderr, "Reject TZ \"%s\": uses leap seconds\n" , tzname); |
252 | #endif |
253 | return -1; |
254 | } |
255 | |
256 | /* Check for match at all the test times */ |
257 | for (i = 0; i < tt->n_test_times; i++) |
258 | { |
259 | pgtt = (pg_time_t) (tt->test_times[i]); |
260 | pgtm = pg_localtime(&pgtt, tz); |
261 | if (!pgtm) |
262 | return -1; /* probably shouldn't happen */ |
263 | systm = localtime(&(tt->test_times[i])); |
264 | if (!systm) |
265 | { |
266 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
267 | fprintf(stderr, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s, system had no data\n" , |
268 | tzname, i, (long) pgtt, |
269 | pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday, |
270 | pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec, |
271 | pgtm->tm_isdst ? "dst" : "std" ); |
272 | #endif |
273 | return i; |
274 | } |
275 | if (!compare_tm(systm, pgtm)) |
276 | { |
277 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
278 | fprintf(stderr, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s\n" , |
279 | tzname, i, (long) pgtt, |
280 | pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday, |
281 | pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec, |
282 | pgtm->tm_isdst ? "dst" : "std" , |
283 | systm->tm_year + 1900, systm->tm_mon + 1, systm->tm_mday, |
284 | systm->tm_hour, systm->tm_min, systm->tm_sec, |
285 | systm->tm_isdst ? "dst" : "std" ); |
286 | #endif |
287 | return i; |
288 | } |
289 | if (systm->tm_isdst >= 0) |
290 | { |
291 | /* Check match of zone names, too */ |
292 | if (pgtm->tm_zone == NULL) |
293 | return -1; /* probably shouldn't happen */ |
294 | memset(cbuf, 0, sizeof(cbuf)); |
295 | strftime(cbuf, sizeof(cbuf) - 1, "%Z" , systm); /* zone abbr */ |
296 | if (strcmp(cbuf, pgtm->tm_zone) != 0) |
297 | { |
298 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
299 | fprintf(stderr, "TZ \"%s\" scores %d: at %ld \"%s\" versus \"%s\"\n" , |
300 | tzname, i, (long) pgtt, |
301 | pgtm->tm_zone, cbuf); |
302 | #endif |
303 | return i; |
304 | } |
305 | } |
306 | } |
307 | |
308 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
309 | fprintf(stderr, "TZ \"%s\" gets max score %d\n" , tzname, i); |
310 | #endif |
311 | |
312 | return i; |
313 | } |
314 | |
315 | /* |
316 | * Test whether given zone name is a perfect match to localtime() behavior |
317 | */ |
318 | static bool |
319 | perfect_timezone_match(const char *tzname, struct tztry *tt) |
320 | { |
321 | return (score_timezone(tzname, tt) == tt->n_test_times); |
322 | } |
323 | |
324 | |
325 | /* |
326 | * Try to identify a timezone name (in our terminology) that best matches the |
327 | * observed behavior of the system localtime() function. |
328 | */ |
329 | static const char * |
330 | identify_system_timezone(void) |
331 | { |
332 | static char resultbuf[TZ_STRLEN_MAX + 1]; |
333 | time_t tnow; |
334 | time_t t; |
335 | struct tztry tt; |
336 | struct tm *tm; |
337 | int thisyear; |
338 | int bestscore; |
339 | char tmptzdir[MAXPGPATH]; |
340 | int std_ofs; |
341 | char std_zone_name[TZ_STRLEN_MAX + 1], |
342 | dst_zone_name[TZ_STRLEN_MAX + 1]; |
343 | char cbuf[TZ_STRLEN_MAX + 1]; |
344 | |
345 | /* Initialize OS timezone library */ |
346 | tzset(); |
347 | |
348 | /* |
349 | * Set up the list of dates to be probed to see how well our timezone |
350 | * matches the system zone. We first probe January and July of the |
351 | * current year; this serves to quickly eliminate the vast majority of the |
352 | * TZ database entries. If those dates match, we probe every week for 100 |
353 | * years backwards from the current July. (Weekly resolution is good |
354 | * enough to identify DST transition rules, since everybody switches on |
355 | * Sundays.) This is sufficient to cover most of the Unix time_t range, |
356 | * and we don't want to look further than that since many systems won't |
357 | * have sane TZ behavior further back anyway. The further back the zone |
358 | * matches, the better we score it. This may seem like a rather random |
359 | * way of doing things, but experience has shown that system-supplied |
360 | * timezone definitions are likely to have DST behavior that is right for |
361 | * the recent past and not so accurate further back. Scoring in this way |
362 | * allows us to recognize zones that have some commonality with the IANA |
363 | * database, without insisting on exact match. (Note: we probe Thursdays, |
364 | * not Sundays, to avoid triggering DST-transition bugs in localtime |
365 | * itself.) |
366 | */ |
367 | tnow = time(NULL); |
368 | tm = localtime(&tnow); |
369 | if (!tm) |
370 | return NULL; /* give up if localtime is broken... */ |
371 | thisyear = tm->tm_year + 1900; |
372 | |
373 | t = build_time_t(thisyear, 1, 15); |
374 | |
375 | /* |
376 | * Round back to GMT midnight Thursday. This depends on the knowledge |
377 | * that the time_t origin is Thu Jan 01 1970. (With a different origin |
378 | * we'd be probing some other day of the week, but it wouldn't matter |
379 | * anyway unless localtime() had DST-transition bugs.) |
380 | */ |
381 | t -= (t % T_WEEK); |
382 | |
383 | tt.n_test_times = 0; |
384 | tt.test_times[tt.n_test_times++] = t; |
385 | |
386 | t = build_time_t(thisyear, 7, 15); |
387 | t -= (t % T_WEEK); |
388 | |
389 | tt.test_times[tt.n_test_times++] = t; |
390 | |
391 | while (tt.n_test_times < MAX_TEST_TIMES) |
392 | { |
393 | t -= T_WEEK; |
394 | tt.test_times[tt.n_test_times++] = t; |
395 | } |
396 | |
397 | /* |
398 | * Try to avoid the brute-force search by seeing if we can recognize the |
399 | * system's timezone setting directly. |
400 | * |
401 | * Currently we just check /etc/localtime; there are other conventions for |
402 | * this, but that seems to be the only one used on enough platforms to be |
403 | * worth troubling over. |
404 | */ |
405 | if (check_system_link_file("/etc/localtime" , &tt, resultbuf)) |
406 | return resultbuf; |
407 | |
408 | /* No luck, so search for the best-matching timezone file */ |
409 | strlcpy(tmptzdir, pg_TZDIR(), sizeof(tmptzdir)); |
410 | bestscore = -1; |
411 | resultbuf[0] = '\0'; |
412 | scan_available_timezones(tmptzdir, tmptzdir + strlen(tmptzdir) + 1, |
413 | &tt, |
414 | &bestscore, resultbuf); |
415 | if (bestscore > 0) |
416 | { |
417 | /* Ignore IANA's rather silly "Factory" zone; use GMT instead */ |
418 | if (strcmp(resultbuf, "Factory" ) == 0) |
419 | return NULL; |
420 | return resultbuf; |
421 | } |
422 | |
423 | /* |
424 | * Couldn't find a match in the database, so next we try constructed zone |
425 | * names (like "PST8PDT"). |
426 | * |
427 | * First we need to determine the names of the local standard and daylight |
428 | * zones. The idea here is to scan forward from today until we have seen |
429 | * both zones, if both are in use. |
430 | */ |
431 | memset(std_zone_name, 0, sizeof(std_zone_name)); |
432 | memset(dst_zone_name, 0, sizeof(dst_zone_name)); |
433 | std_ofs = 0; |
434 | |
435 | tnow = time(NULL); |
436 | |
437 | /* |
438 | * Round back to a GMT midnight so results don't depend on local time of |
439 | * day |
440 | */ |
441 | tnow -= (tnow % T_DAY); |
442 | |
443 | /* |
444 | * We have to look a little further ahead than one year, in case today is |
445 | * just past a DST boundary that falls earlier in the year than the next |
446 | * similar boundary. Arbitrarily scan up to 14 months. |
447 | */ |
448 | for (t = tnow; t <= tnow + T_MONTH * 14; t += T_MONTH) |
449 | { |
450 | tm = localtime(&t); |
451 | if (!tm) |
452 | continue; |
453 | if (tm->tm_isdst < 0) |
454 | continue; |
455 | if (tm->tm_isdst == 0 && std_zone_name[0] == '\0') |
456 | { |
457 | /* found STD zone */ |
458 | memset(cbuf, 0, sizeof(cbuf)); |
459 | strftime(cbuf, sizeof(cbuf) - 1, "%Z" , tm); /* zone abbr */ |
460 | strcpy(std_zone_name, cbuf); |
461 | std_ofs = get_timezone_offset(tm); |
462 | } |
463 | if (tm->tm_isdst > 0 && dst_zone_name[0] == '\0') |
464 | { |
465 | /* found DST zone */ |
466 | memset(cbuf, 0, sizeof(cbuf)); |
467 | strftime(cbuf, sizeof(cbuf) - 1, "%Z" , tm); /* zone abbr */ |
468 | strcpy(dst_zone_name, cbuf); |
469 | } |
470 | /* Done if found both */ |
471 | if (std_zone_name[0] && dst_zone_name[0]) |
472 | break; |
473 | } |
474 | |
475 | /* We should have found a STD zone name by now... */ |
476 | if (std_zone_name[0] == '\0') |
477 | { |
478 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
479 | fprintf(stderr, "could not determine system time zone\n" ); |
480 | #endif |
481 | return NULL; /* go to GMT */ |
482 | } |
483 | |
484 | /* If we found DST then try STD<ofs>DST */ |
485 | if (dst_zone_name[0] != '\0') |
486 | { |
487 | snprintf(resultbuf, sizeof(resultbuf), "%s%d%s" , |
488 | std_zone_name, -std_ofs / 3600, dst_zone_name); |
489 | if (score_timezone(resultbuf, &tt) > 0) |
490 | return resultbuf; |
491 | } |
492 | |
493 | /* Try just the STD timezone (works for GMT at least) */ |
494 | strcpy(resultbuf, std_zone_name); |
495 | if (score_timezone(resultbuf, &tt) > 0) |
496 | return resultbuf; |
497 | |
498 | /* Try STD<ofs> */ |
499 | snprintf(resultbuf, sizeof(resultbuf), "%s%d" , |
500 | std_zone_name, -std_ofs / 3600); |
501 | if (score_timezone(resultbuf, &tt) > 0) |
502 | return resultbuf; |
503 | |
504 | /* |
505 | * Did not find the timezone. Fallback to use a GMT zone. Note that the |
506 | * IANA timezone database names the GMT-offset zones in POSIX style: plus |
507 | * is west of Greenwich. It's unfortunate that this is opposite of SQL |
508 | * conventions. Should we therefore change the names? Probably not... |
509 | */ |
510 | snprintf(resultbuf, sizeof(resultbuf), "Etc/GMT%s%d" , |
511 | (-std_ofs > 0) ? "+" : "" , -std_ofs / 3600); |
512 | |
513 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
514 | fprintf(stderr, "could not recognize system time zone, using \"%s\"\n" , |
515 | resultbuf); |
516 | #endif |
517 | return resultbuf; |
518 | } |
519 | |
520 | /* |
521 | * Examine a system-provided symlink file to see if it tells us the timezone. |
522 | * |
523 | * Unfortunately, there is little standardization of how the system default |
524 | * timezone is determined in the absence of a TZ environment setting. |
525 | * But a common strategy is to create a symlink at a well-known place. |
526 | * If "linkname" identifies a readable symlink, and the tail of its contents |
527 | * matches a zone name we know, and the actual behavior of localtime() agrees |
528 | * with what we think that zone means, then we may use that zone name. |
529 | * |
530 | * We insist on a perfect behavioral match, which might not happen if the |
531 | * system has a different IANA database version than we do; but in that case |
532 | * it seems best to fall back to the brute-force search. |
533 | * |
534 | * linkname is the symlink file location to probe. |
535 | * |
536 | * tt tells about the system timezone behavior we need to match. |
537 | * |
538 | * If we successfully identify a zone name, store it in *bestzonename and |
539 | * return true; else return false. bestzonename must be a buffer of length |
540 | * TZ_STRLEN_MAX + 1. |
541 | */ |
542 | static bool |
543 | check_system_link_file(const char *linkname, struct tztry *tt, |
544 | char *bestzonename) |
545 | { |
546 | #ifdef HAVE_READLINK |
547 | char link_target[MAXPGPATH]; |
548 | int len; |
549 | const char *cur_name; |
550 | |
551 | /* |
552 | * Try to read the symlink. If not there, not a symlink, etc etc, just |
553 | * quietly fail; the precise reason needn't concern us. |
554 | */ |
555 | len = readlink(linkname, link_target, sizeof(link_target)); |
556 | if (len < 0 || len >= sizeof(link_target)) |
557 | return false; |
558 | link_target[len] = '\0'; |
559 | |
560 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
561 | fprintf(stderr, "symbolic link \"%s\" contains \"%s\"\n" , |
562 | linkname, link_target); |
563 | #endif |
564 | |
565 | /* |
566 | * The symlink is probably of the form "/path/to/zones/zone/name", or |
567 | * possibly it is a relative path. Nobody puts their zone DB directly in |
568 | * the root directory, so we can definitely skip the first component; but |
569 | * after that it's trial-and-error to identify which path component begins |
570 | * the zone name. |
571 | */ |
572 | cur_name = link_target; |
573 | while (*cur_name) |
574 | { |
575 | /* Advance to next segment of path */ |
576 | cur_name = strchr(cur_name + 1, '/'); |
577 | if (cur_name == NULL) |
578 | break; |
579 | /* If there are consecutive slashes, skip all, as the kernel would */ |
580 | do |
581 | { |
582 | cur_name++; |
583 | } while (*cur_name == '/'); |
584 | |
585 | /* |
586 | * Test remainder of path to see if it is a matching zone name. |
587 | * Relative paths might contain ".."; we needn't bother testing if the |
588 | * first component is that. Also defend against overlength names. |
589 | */ |
590 | if (*cur_name && *cur_name != '.' && |
591 | strlen(cur_name) <= TZ_STRLEN_MAX && |
592 | perfect_timezone_match(cur_name, tt)) |
593 | { |
594 | /* Success! */ |
595 | strcpy(bestzonename, cur_name); |
596 | return true; |
597 | } |
598 | } |
599 | |
600 | /* Couldn't extract a matching zone name */ |
601 | return false; |
602 | #else |
603 | /* No symlinks? Forget it */ |
604 | return false; |
605 | #endif |
606 | } |
607 | |
608 | /* |
609 | * Given a timezone name, determine whether it should be preferred over other |
610 | * names which are equally good matches. The output is arbitrary but we will |
611 | * use 0 for "neutral" default preference; larger values are more preferred. |
612 | */ |
613 | static int |
614 | zone_name_pref(const char *zonename) |
615 | { |
616 | /* |
617 | * Prefer UTC over alternatives such as UCT. Also prefer Etc/UTC over |
618 | * Etc/UCT; but UTC is preferred to Etc/UTC. |
619 | */ |
620 | if (strcmp(zonename, "UTC" ) == 0) |
621 | return 50; |
622 | if (strcmp(zonename, "Etc/UTC" ) == 0) |
623 | return 40; |
624 | |
625 | /* |
626 | * We don't want to pick "localtime" or "posixrules", unless we can find |
627 | * no other name for the prevailing zone. Those aren't real zone names. |
628 | */ |
629 | if (strcmp(zonename, "localtime" ) == 0 || |
630 | strcmp(zonename, "posixrules" ) == 0) |
631 | return -50; |
632 | |
633 | return 0; |
634 | } |
635 | |
636 | /* |
637 | * Recursively scan the timezone database looking for the best match to |
638 | * the system timezone behavior. |
639 | * |
640 | * tzdir points to a buffer of size MAXPGPATH. On entry, it holds the |
641 | * pathname of a directory containing TZ files. We internally modify it |
642 | * to hold pathnames of sub-directories and files, but must restore it |
643 | * to its original contents before exit. |
644 | * |
645 | * tzdirsub points to the part of tzdir that represents the subfile name |
646 | * (ie, tzdir + the original directory name length, plus one for the |
647 | * first added '/'). |
648 | * |
649 | * tt tells about the system timezone behavior we need to match. |
650 | * |
651 | * *bestscore and *bestzonename on entry hold the best score found so far |
652 | * and the name of the best zone. We overwrite them if we find a better |
653 | * score. bestzonename must be a buffer of length TZ_STRLEN_MAX + 1. |
654 | */ |
655 | static void |
656 | scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt, |
657 | int *bestscore, char *bestzonename) |
658 | { |
659 | int tzdir_orig_len = strlen(tzdir); |
660 | char **names; |
661 | char **namep; |
662 | |
663 | names = pgfnames(tzdir); |
664 | if (!names) |
665 | return; |
666 | |
667 | for (namep = names; *namep; namep++) |
668 | { |
669 | char *name = *namep; |
670 | struct stat statbuf; |
671 | |
672 | /* Ignore . and .., plus any other "hidden" files */ |
673 | if (name[0] == '.') |
674 | continue; |
675 | |
676 | snprintf(tzdir + tzdir_orig_len, MAXPGPATH - tzdir_orig_len, |
677 | "/%s" , name); |
678 | |
679 | if (stat(tzdir, &statbuf) != 0) |
680 | { |
681 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
682 | fprintf(stderr, "could not stat \"%s\": %s\n" , |
683 | tzdir, strerror(errno)); |
684 | #endif |
685 | tzdir[tzdir_orig_len] = '\0'; |
686 | continue; |
687 | } |
688 | |
689 | if (S_ISDIR(statbuf.st_mode)) |
690 | { |
691 | /* Recurse into subdirectory */ |
692 | scan_available_timezones(tzdir, tzdirsub, tt, |
693 | bestscore, bestzonename); |
694 | } |
695 | else |
696 | { |
697 | /* Load and test this file */ |
698 | int score = score_timezone(tzdirsub, tt); |
699 | |
700 | if (score > *bestscore) |
701 | { |
702 | *bestscore = score; |
703 | strlcpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1); |
704 | } |
705 | else if (score == *bestscore) |
706 | { |
707 | /* Consider how to break a tie */ |
708 | int namepref = (zone_name_pref(tzdirsub) - |
709 | zone_name_pref(bestzonename)); |
710 | |
711 | if (namepref > 0 || |
712 | (namepref == 0 && |
713 | (strlen(tzdirsub) < strlen(bestzonename) || |
714 | (strlen(tzdirsub) == strlen(bestzonename) && |
715 | strcmp(tzdirsub, bestzonename) < 0)))) |
716 | strlcpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1); |
717 | } |
718 | } |
719 | |
720 | /* Restore tzdir */ |
721 | tzdir[tzdir_orig_len] = '\0'; |
722 | } |
723 | |
724 | pgfnames_cleanup(names); |
725 | } |
726 | #else /* WIN32 */ |
727 | |
728 | static const struct |
729 | { |
730 | const char *stdname; /* Windows name of standard timezone */ |
731 | const char *dstname; /* Windows name of daylight timezone */ |
732 | const char *pgtzname; /* Name of pgsql timezone to map to */ |
733 | } win32_tzmap[] = |
734 | |
735 | { |
736 | /* |
737 | * This list was built from the contents of the registry at |
738 | * HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time |
739 | * Zones on Windows 10 and Windows 7. |
740 | * |
741 | * The zones have been matched to IANA timezones by looking at the cities |
742 | * listed in the win32 display name (in the comment here) in most cases. |
743 | */ |
744 | { |
745 | "Afghanistan Standard Time" , "Afghanistan Daylight Time" , |
746 | "Asia/Kabul" |
747 | }, /* (UTC+04:30) Kabul */ |
748 | { |
749 | "Alaskan Standard Time" , "Alaskan Daylight Time" , |
750 | "US/Alaska" |
751 | }, /* (UTC-09:00) Alaska */ |
752 | { |
753 | "Aleutian Standard Time" , "Aleutian Daylight Time" , |
754 | "US/Aleutan" |
755 | }, /* (UTC-10:00) Aleutian Islands */ |
756 | { |
757 | "Altai Standard Time" , "Altai Daylight Time" , |
758 | "Asia/Barnaul" |
759 | }, /* (UTC+07:00) Barnaul, Gorno-Altaysk */ |
760 | { |
761 | "Arab Standard Time" , "Arab Daylight Time" , |
762 | "Asia/Kuwait" |
763 | }, /* (UTC+03:00) Kuwait, Riyadh */ |
764 | { |
765 | "Arabian Standard Time" , "Arabian Daylight Time" , |
766 | "Asia/Muscat" |
767 | }, /* (UTC+04:00) Abu Dhabi, Muscat */ |
768 | { |
769 | "Arabic Standard Time" , "Arabic Daylight Time" , |
770 | "Asia/Baghdad" |
771 | }, /* (UTC+03:00) Baghdad */ |
772 | { |
773 | "Argentina Standard Time" , "Argentina Daylight Time" , |
774 | "America/Buenos_Aires" |
775 | }, /* (UTC-03:00) City of Buenos Aires */ |
776 | { |
777 | "Armenian Standard Time" , "Armenian Daylight Time" , |
778 | "Asia/Yerevan" |
779 | }, /* (UTC+04:00) Baku, Tbilisi, Yerevan */ |
780 | { |
781 | "Astrakhan Standard Time" , "Astrakhan Daylight Time" , |
782 | "Europe/Astrakhan" |
783 | }, /* (UTC+04:00) Astrakhan, Ulyanovsk */ |
784 | { |
785 | "Atlantic Standard Time" , "Atlantic Daylight Time" , |
786 | "Canada/Atlantic" |
787 | }, /* (UTC-04:00) Atlantic Time (Canada) */ |
788 | { |
789 | "AUS Central Standard Time" , "AUS Central Daylight Time" , |
790 | "Australia/Darwin" |
791 | }, /* (UTC+09:30) Darwin */ |
792 | { |
793 | "Aus Central W. Standard Time" , "Aus Central W. Daylight Time" , |
794 | "Australia/Eucla" |
795 | }, /* (UTC+08:45) Eucla */ |
796 | { |
797 | "AUS Eastern Standard Time" , "AUS Eastern Daylight Time" , |
798 | "Australia/Canberra" |
799 | }, /* (UTC+10:00) Canberra, Melbourne, Sydney */ |
800 | { |
801 | "Azerbaijan Standard Time" , "Azerbaijan Daylight Time" , |
802 | "Asia/Baku" |
803 | }, /* (UTC+04:00) Baku */ |
804 | { |
805 | "Azores Standard Time" , "Azores Daylight Time" , |
806 | "Atlantic/Azores" |
807 | }, /* (UTC-01:00) Azores */ |
808 | { |
809 | "Bahia Standard Time" , "Bahia Daylight Time" , |
810 | "America/Salvador" |
811 | }, /* (UTC-03:00) Salvador */ |
812 | { |
813 | "Bangladesh Standard Time" , "Bangladesh Daylight Time" , |
814 | "Asia/Dhaka" |
815 | }, /* (UTC+06:00) Dhaka */ |
816 | { |
817 | "Bougainville Standard Time" , "Bougainville Daylight Time" , |
818 | "Pacific/Bougainville" |
819 | }, /* (UTC+11:00) Bougainville Island */ |
820 | { |
821 | "Belarus Standard Time" , "Belarus Daylight Time" , |
822 | "Europe/Minsk" |
823 | }, /* (UTC+03:00) Minsk */ |
824 | { |
825 | "Cabo Verde Standard Time" , "Cabo Verde Daylight Time" , |
826 | "Atlantic/Cape_Verde" |
827 | }, /* (UTC-01:00) Cabo Verde Is. */ |
828 | { |
829 | "Chatham Islands Standard Time" , "Chatham Islands Daylight Time" , |
830 | "Pacific/Chatham" |
831 | }, /* (UTC+12:45) Chatham Islands */ |
832 | { |
833 | "Canada Central Standard Time" , "Canada Central Daylight Time" , |
834 | "Canada/Saskatchewan" |
835 | }, /* (UTC-06:00) Saskatchewan */ |
836 | { |
837 | "Cape Verde Standard Time" , "Cape Verde Daylight Time" , |
838 | "Atlantic/Cape_Verde" |
839 | }, /* (UTC-01:00) Cape Verde Is. */ |
840 | { |
841 | "Caucasus Standard Time" , "Caucasus Daylight Time" , |
842 | "Asia/Baku" |
843 | }, /* (UTC+04:00) Yerevan */ |
844 | { |
845 | "Cen. Australia Standard Time" , "Cen. Australia Daylight Time" , |
846 | "Australia/Adelaide" |
847 | }, /* (UTC+09:30) Adelaide */ |
848 | /* Central America (other than Mexico) generally does not observe DST */ |
849 | { |
850 | "Central America Standard Time" , "Central America Daylight Time" , |
851 | "CST6" |
852 | }, /* (UTC-06:00) Central America */ |
853 | { |
854 | "Central Asia Standard Time" , "Central Asia Daylight Time" , |
855 | "Asia/Dhaka" |
856 | }, /* (UTC+06:00) Astana */ |
857 | { |
858 | "Central Brazilian Standard Time" , "Central Brazilian Daylight Time" , |
859 | "America/Cuiaba" |
860 | }, /* (UTC-04:00) Cuiaba */ |
861 | { |
862 | "Central Europe Standard Time" , "Central Europe Daylight Time" , |
863 | "Europe/Belgrade" |
864 | }, /* (UTC+01:00) Belgrade, Bratislava, Budapest, |
865 | * Ljubljana, Prague */ |
866 | { |
867 | "Central European Standard Time" , "Central European Daylight Time" , |
868 | "Europe/Sarajevo" |
869 | }, /* (UTC+01:00) Sarajevo, Skopje, Warsaw, |
870 | * Zagreb */ |
871 | { |
872 | "Central Pacific Standard Time" , "Central Pacific Daylight Time" , |
873 | "Pacific/Noumea" |
874 | }, /* (UTC+11:00) Solomon Is., New Caledonia */ |
875 | { |
876 | "Central Standard Time" , "Central Daylight Time" , |
877 | "US/Central" |
878 | }, /* (UTC-06:00) Central Time (US & Canada) */ |
879 | { |
880 | "Central Standard Time (Mexico)" , "Central Daylight Time (Mexico)" , |
881 | "America/Mexico_City" |
882 | }, /* (UTC-06:00) Guadalajara, Mexico City, |
883 | * Monterrey */ |
884 | { |
885 | "China Standard Time" , "China Daylight Time" , |
886 | "Asia/Hong_Kong" |
887 | }, /* (UTC+08:00) Beijing, Chongqing, Hong Kong, |
888 | * Urumqi */ |
889 | { |
890 | "Cuba Standard Time" , "Cuba Daylight Time" , |
891 | "America/Havana" |
892 | }, /* (UTC-05:00) Havana */ |
893 | { |
894 | "Dateline Standard Time" , "Dateline Daylight Time" , |
895 | "Etc/UTC+12" |
896 | }, /* (UTC-12:00) International Date Line West */ |
897 | { |
898 | "E. Africa Standard Time" , "E. Africa Daylight Time" , |
899 | "Africa/Nairobi" |
900 | }, /* (UTC+03:00) Nairobi */ |
901 | { |
902 | "E. Australia Standard Time" , "E. Australia Daylight Time" , |
903 | "Australia/Brisbane" |
904 | }, /* (UTC+10:00) Brisbane */ |
905 | { |
906 | "E. Europe Standard Time" , "E. Europe Daylight Time" , |
907 | "Europe/Bucharest" |
908 | }, /* (UTC+02:00) E. Europe */ |
909 | { |
910 | "E. South America Standard Time" , "E. South America Daylight Time" , |
911 | "America/Araguaina" |
912 | }, /* (UTC-03:00) Brasilia */ |
913 | { |
914 | "Eastern Standard Time" , "Eastern Daylight Time" , |
915 | "US/Eastern" |
916 | }, /* (UTC-05:00) Eastern Time (US & Canada) */ |
917 | { |
918 | "Eastern Standard Time (Mexico)" , "Eastern Daylight Time (Mexico)" , |
919 | "America/Mexico_City" |
920 | }, /* (UTC-05:00) Chetumal */ |
921 | { |
922 | "Easter Island Standard Time" , "Easter Island Daylight Time" , |
923 | "Pacific/Easter" |
924 | }, /* (UTC-06:00) Easter Island */ |
925 | { |
926 | "Egypt Standard Time" , "Egypt Daylight Time" , |
927 | "Africa/Cairo" |
928 | }, /* (UTC+02:00) Cairo */ |
929 | { |
930 | "Ekaterinburg Standard Time (RTZ 4)" , "Ekaterinburg Daylight Time" , |
931 | "Asia/Yekaterinburg" |
932 | }, /* (UTC+05:00) Ekaterinburg */ |
933 | { |
934 | "Fiji Standard Time" , "Fiji Daylight Time" , |
935 | "Pacific/Fiji" |
936 | }, /* (UTC+12:00) Fiji */ |
937 | { |
938 | "FLE Standard Time" , "FLE Daylight Time" , |
939 | "Europe/Helsinki" |
940 | }, /* (UTC+02:00) Helsinki, Kyiv, Riga, Sofia, |
941 | * Tallinn, Vilnius */ |
942 | { |
943 | "Georgian Standard Time" , "Georgian Daylight Time" , |
944 | "Asia/Tbilisi" |
945 | }, /* (UTC+04:00) Tbilisi */ |
946 | { |
947 | "GMT Standard Time" , "GMT Daylight Time" , |
948 | "Europe/London" |
949 | }, /* (UTC) Dublin, Edinburgh, Lisbon, London */ |
950 | { |
951 | "Greenland Standard Time" , "Greenland Daylight Time" , |
952 | "America/Godthab" |
953 | }, /* (UTC-03:00) Greenland */ |
954 | { |
955 | "Greenwich Standard Time" , "Greenwich Daylight Time" , |
956 | "Africa/Casablanca" |
957 | }, /* (UTC) Monrovia, Reykjavik */ |
958 | { |
959 | "GTB Standard Time" , "GTB Daylight Time" , |
960 | "Europe/Athens" |
961 | }, /* (UTC+02:00) Athens, Bucharest */ |
962 | { |
963 | "Haiti Standard Time" , "Haiti Daylight Time" , |
964 | "US/Eastern" |
965 | }, /* (UTC-05:00) Haiti */ |
966 | { |
967 | "Hawaiian Standard Time" , "Hawaiian Daylight Time" , |
968 | "US/Hawaii" |
969 | }, /* (UTC-10:00) Hawaii */ |
970 | { |
971 | "India Standard Time" , "India Daylight Time" , |
972 | "Asia/Calcutta" |
973 | }, /* (UTC+05:30) Chennai, Kolkata, Mumbai, New |
974 | * Delhi */ |
975 | { |
976 | "Iran Standard Time" , "Iran Daylight Time" , |
977 | "Asia/Tehran" |
978 | }, /* (UTC+03:30) Tehran */ |
979 | { |
980 | "Jerusalem Standard Time" , "Jerusalem Daylight Time" , |
981 | "Asia/Jerusalem" |
982 | }, /* (UTC+02:00) Jerusalem */ |
983 | { |
984 | "Jordan Standard Time" , "Jordan Daylight Time" , |
985 | "Asia/Amman" |
986 | }, /* (UTC+02:00) Amman */ |
987 | { |
988 | "Kamchatka Standard Time" , "Kamchatka Daylight Time" , |
989 | "Asia/Kamchatka" |
990 | }, /* (UTC+12:00) Petropavlovsk-Kamchatsky - Old */ |
991 | { |
992 | "Korea Standard Time" , "Korea Daylight Time" , |
993 | "Asia/Seoul" |
994 | }, /* (UTC+09:00) Seoul */ |
995 | { |
996 | "Libya Standard Time" , "Libya Daylight Time" , |
997 | "Africa/Tripoli" |
998 | }, /* (UTC+02:00) Tripoli */ |
999 | { |
1000 | "Line Islands Standard Time" , "Line Islands Daylight Time" , |
1001 | "Pacific/Kiritimati" |
1002 | }, /* (UTC+14:00) Kiritimati Island */ |
1003 | { |
1004 | "Lord Howe Standard Time" , "Lord Howe Daylight Time" , |
1005 | "Australia/Lord_Howe" |
1006 | }, /* (UTC+10:30) Lord Howe Island */ |
1007 | { |
1008 | "Magadan Standard Time" , "Magadan Daylight Time" , |
1009 | "Asia/Magadan" |
1010 | }, /* (UTC+10:00) Magadan */ |
1011 | { |
1012 | "Marquesas Standard Time" , "Marquesas Daylight Time" , |
1013 | "Pacific/Marquesas" |
1014 | }, /* (UTC-09:30) Marquesas Islands */ |
1015 | { |
1016 | "Mauritius Standard Time" , "Mauritius Daylight Time" , |
1017 | "Indian/Mauritius" |
1018 | }, /* (UTC+04:00) Port Louis */ |
1019 | { |
1020 | "Mexico Standard Time" , "Mexico Daylight Time" , |
1021 | "America/Mexico_City" |
1022 | }, /* (UTC-06:00) Guadalajara, Mexico City, |
1023 | * Monterrey */ |
1024 | { |
1025 | "Mexico Standard Time 2" , "Mexico Daylight Time 2" , |
1026 | "America/Chihuahua" |
1027 | }, /* (UTC-07:00) Chihuahua, La Paz, Mazatlan */ |
1028 | { |
1029 | "Mid-Atlantic Standard Time" , "Mid-Atlantic Daylight Time" , |
1030 | "Atlantic/South_Georgia" |
1031 | }, /* (UTC-02:00) Mid-Atlantic - Old */ |
1032 | { |
1033 | "Middle East Standard Time" , "Middle East Daylight Time" , |
1034 | "Asia/Beirut" |
1035 | }, /* (UTC+02:00) Beirut */ |
1036 | { |
1037 | "Montevideo Standard Time" , "Montevideo Daylight Time" , |
1038 | "America/Montevideo" |
1039 | }, /* (UTC-03:00) Montevideo */ |
1040 | { |
1041 | "Morocco Standard Time" , "Morocco Daylight Time" , |
1042 | "Africa/Casablanca" |
1043 | }, /* (UTC) Casablanca */ |
1044 | { |
1045 | "Mountain Standard Time" , "Mountain Daylight Time" , |
1046 | "US/Mountain" |
1047 | }, /* (UTC-07:00) Mountain Time (US & Canada) */ |
1048 | { |
1049 | "Mountain Standard Time (Mexico)" , "Mountain Daylight Time (Mexico)" , |
1050 | "America/Chihuahua" |
1051 | }, /* (UTC-07:00) Chihuahua, La Paz, Mazatlan */ |
1052 | { |
1053 | "Myanmar Standard Time" , "Myanmar Daylight Time" , |
1054 | "Asia/Rangoon" |
1055 | }, /* (UTC+06:30) Yangon (Rangoon) */ |
1056 | { |
1057 | "N. Central Asia Standard Time" , "N. Central Asia Daylight Time" , |
1058 | "Asia/Novosibirsk" |
1059 | }, /* (UTC+06:00) Novosibirsk (RTZ 5) */ |
1060 | { |
1061 | "Namibia Standard Time" , "Namibia Daylight Time" , |
1062 | "Africa/Windhoek" |
1063 | }, /* (UTC+01:00) Windhoek */ |
1064 | { |
1065 | "Nepal Standard Time" , "Nepal Daylight Time" , |
1066 | "Asia/Katmandu" |
1067 | }, /* (UTC+05:45) Kathmandu */ |
1068 | { |
1069 | "New Zealand Standard Time" , "New Zealand Daylight Time" , |
1070 | "Pacific/Auckland" |
1071 | }, /* (UTC+12:00) Auckland, Wellington */ |
1072 | { |
1073 | "Newfoundland Standard Time" , "Newfoundland Daylight Time" , |
1074 | "Canada/Newfoundland" |
1075 | }, /* (UTC-03:30) Newfoundland */ |
1076 | { |
1077 | "Norfolk Standard Time" , "Norfolk Daylight Time" , |
1078 | "Pacific/Norfolk" |
1079 | }, /* (UTC+11:00) Norfolk Island */ |
1080 | { |
1081 | "North Asia East Standard Time" , "North Asia East Daylight Time" , |
1082 | "Asia/Irkutsk" |
1083 | }, /* (UTC+08:00) Irkutsk, Ulaan Bataar */ |
1084 | { |
1085 | "North Asia Standard Time" , "North Asia Daylight Time" , |
1086 | "Asia/Krasnoyarsk" |
1087 | }, /* (UTC+07:00) Krasnoyarsk */ |
1088 | { |
1089 | "North Korea Standard Time" , "North Korea Daylight Time" , |
1090 | "Asia/Pyongyang" |
1091 | }, /* (UTC+08:30) Pyongyang */ |
1092 | { |
1093 | "Pacific SA Standard Time" , "Pacific SA Daylight Time" , |
1094 | "America/Santiago" |
1095 | }, /* (UTC-03:00) Santiago */ |
1096 | { |
1097 | "Pacific Standard Time" , "Pacific Daylight Time" , |
1098 | "US/Pacific" |
1099 | }, /* (UTC-08:00) Pacific Time (US & Canada) */ |
1100 | { |
1101 | "Pacific Standard Time (Mexico)" , "Pacific Daylight Time (Mexico)" , |
1102 | "America/Tijuana" |
1103 | }, /* (UTC-08:00) Baja California */ |
1104 | { |
1105 | "Pakistan Standard Time" , "Pakistan Daylight Time" , |
1106 | "Asia/Karachi" |
1107 | }, /* (UTC+05:00) Islamabad, Karachi */ |
1108 | { |
1109 | "Paraguay Standard Time" , "Paraguay Daylight Time" , |
1110 | "America/Asuncion" |
1111 | }, /* (UTC-04:00) Asuncion */ |
1112 | { |
1113 | "Romance Standard Time" , "Romance Daylight Time" , |
1114 | "Europe/Brussels" |
1115 | }, /* (UTC+01:00) Brussels, Copenhagen, Madrid, |
1116 | * Paris */ |
1117 | { |
1118 | "Russia TZ 1 Standard Time" , "Russia TZ 1 Daylight Time" , |
1119 | "Europe/Kaliningrad" |
1120 | }, /* (UTC+02:00) Kaliningrad (RTZ 1) */ |
1121 | { |
1122 | "Russia TZ 2 Standard Time" , "Russia TZ 2 Daylight Time" , |
1123 | "Europe/Moscow" |
1124 | }, /* (UTC+03:00) Moscow, St. Petersburg, |
1125 | * Volgograd (RTZ 2) */ |
1126 | { |
1127 | "Russia TZ 3 Standard Time" , "Russia TZ 3 Daylight Time" , |
1128 | "Europe/Samara" |
1129 | }, /* (UTC+04:00) Izhevsk, Samara (RTZ 3) */ |
1130 | { |
1131 | "Russia TZ 4 Standard Time" , "Russia TZ 4 Daylight Time" , |
1132 | "Asia/Yekaterinburg" |
1133 | }, /* (UTC+05:00) Ekaterinburg (RTZ 4) */ |
1134 | { |
1135 | "Russia TZ 5 Standard Time" , "Russia TZ 5 Daylight Time" , |
1136 | "Asia/Novosibirsk" |
1137 | }, /* (UTC+06:00) Novosibirsk (RTZ 5) */ |
1138 | { |
1139 | "Russia TZ 6 Standard Time" , "Russia TZ 6 Daylight Time" , |
1140 | "Asia/Krasnoyarsk" |
1141 | }, /* (UTC+07:00) Krasnoyarsk (RTZ 6) */ |
1142 | { |
1143 | "Russia TZ 7 Standard Time" , "Russia TZ 7 Daylight Time" , |
1144 | "Asia/Irkutsk" |
1145 | }, /* (UTC+08:00) Irkutsk (RTZ 7) */ |
1146 | { |
1147 | "Russia TZ 8 Standard Time" , "Russia TZ 8 Daylight Time" , |
1148 | "Asia/Yakutsk" |
1149 | }, /* (UTC+09:00) Yakutsk (RTZ 8) */ |
1150 | { |
1151 | "Russia TZ 9 Standard Time" , "Russia TZ 9 Daylight Time" , |
1152 | "Asia/Vladivostok" |
1153 | }, /* (UTC+10:00) Vladivostok, Magadan (RTZ 9) */ |
1154 | { |
1155 | "Russia TZ 10 Standard Time" , "Russia TZ 10 Daylight Time" , |
1156 | "Asia/Magadan" |
1157 | }, /* (UTC+11:00) Chokurdakh (RTZ 10) */ |
1158 | { |
1159 | "Russia TZ 11 Standard Time" , "Russia TZ 11 Daylight Time" , |
1160 | "Asia/Anadyr" |
1161 | }, /* (UTC+12:00) Anadyr, |
1162 | * Petropavlovsk-Kamchatsky (RTZ 11) */ |
1163 | { |
1164 | "Russian Standard Time" , "Russian Daylight Time" , |
1165 | "Europe/Moscow" |
1166 | }, /* (UTC+03:00) Moscow, St. Petersburg, |
1167 | * Volgograd */ |
1168 | { |
1169 | "SA Eastern Standard Time" , "SA Eastern Daylight Time" , |
1170 | "America/Buenos_Aires" |
1171 | }, /* (UTC-03:00) Cayenne, Fortaleza */ |
1172 | { |
1173 | "SA Pacific Standard Time" , "SA Pacific Daylight Time" , |
1174 | "America/Bogota" |
1175 | }, /* (UTC-05:00) Bogota, Lima, Quito, Rio Branco */ |
1176 | { |
1177 | "SA Western Standard Time" , "SA Western Daylight Time" , |
1178 | "America/Caracas" |
1179 | }, /* (UTC-04:00) Georgetown, La Paz, Manaus, San |
1180 | * Juan */ |
1181 | { |
1182 | "Saint Pierre Standard Time" , "Saint Pierre Daylight Time" , |
1183 | "America/Miquelon" |
1184 | }, /* (UTC-03:00) Saint Pierre and Miquelon */ |
1185 | { |
1186 | "Samoa Standard Time" , "Samoa Daylight Time" , |
1187 | "Pacific/Samoa" |
1188 | }, /* (UTC+13:00) Samoa */ |
1189 | { |
1190 | "SE Asia Standard Time" , "SE Asia Daylight Time" , |
1191 | "Asia/Bangkok" |
1192 | }, /* (UTC+07:00) Bangkok, Hanoi, Jakarta */ |
1193 | { |
1194 | "Malay Peninsula Standard Time" , "Malay Peninsula Daylight Time" , |
1195 | "Asia/Kuala_Lumpur" |
1196 | }, /* (UTC+08:00) Kuala Lumpur, Singapore */ |
1197 | { |
1198 | "Sakhalin Standard Time" , "Sakhalin Daylight Time" , |
1199 | "Asia/Sakhalin" |
1200 | }, /* (UTC+11:00) Sakhalin */ |
1201 | { |
1202 | "South Africa Standard Time" , "South Africa Daylight Time" , |
1203 | "Africa/Harare" |
1204 | }, /* (UTC+02:00) Harare, Pretoria */ |
1205 | { |
1206 | "Sri Lanka Standard Time" , "Sri Lanka Daylight Time" , |
1207 | "Asia/Colombo" |
1208 | }, /* (UTC+05:30) Sri Jayawardenepura */ |
1209 | { |
1210 | "Syria Standard Time" , "Syria Daylight Time" , |
1211 | "Asia/Damascus" |
1212 | }, /* (UTC+02:00) Damascus */ |
1213 | { |
1214 | "Taipei Standard Time" , "Taipei Daylight Time" , |
1215 | "Asia/Taipei" |
1216 | }, /* (UTC+08:00) Taipei */ |
1217 | { |
1218 | "Tasmania Standard Time" , "Tasmania Daylight Time" , |
1219 | "Australia/Hobart" |
1220 | }, /* (UTC+10:00) Hobart */ |
1221 | { |
1222 | "Tocantins Standard Time" , "Tocantins Daylight Time" , |
1223 | "America/Araguaina" |
1224 | }, /* (UTC-03:00) Araguaina */ |
1225 | { |
1226 | "Tokyo Standard Time" , "Tokyo Daylight Time" , |
1227 | "Asia/Tokyo" |
1228 | }, /* (UTC+09:00) Osaka, Sapporo, Tokyo */ |
1229 | { |
1230 | "Tonga Standard Time" , "Tonga Daylight Time" , |
1231 | "Pacific/Tongatapu" |
1232 | }, /* (UTC+13:00) Nuku'alofa */ |
1233 | { |
1234 | "Tomsk Standard Time" , "Tomsk Daylight Time" , |
1235 | "Asia/Tomsk" |
1236 | }, /* (UTC+07:00) Tomsk */ |
1237 | { |
1238 | "Transbaikal Standard Time" , "Transbaikal Daylight Time" , |
1239 | "Asia/Chita" |
1240 | }, /* (UTC+09:00) Chita */ |
1241 | { |
1242 | "Turkey Standard Time" , "Turkey Daylight Time" , |
1243 | "Europe/Istanbul" |
1244 | }, /* (UTC+02:00) Istanbul */ |
1245 | { |
1246 | "Turks and Caicos Standard Time" , "Turks and Caicos Daylight Time" , |
1247 | "America/Grand_Turk" |
1248 | }, /* (UTC-04:00) Turks and Caicos */ |
1249 | { |
1250 | "Ulaanbaatar Standard Time" , "Ulaanbaatar Daylight Time" , |
1251 | "Asia/Ulaanbaatar" , |
1252 | }, /* (UTC+08:00) Ulaanbaatar */ |
1253 | { |
1254 | "US Eastern Standard Time" , "US Eastern Daylight Time" , |
1255 | "US/Eastern" |
1256 | }, /* (UTC-05:00) Indiana (East) */ |
1257 | { |
1258 | "US Mountain Standard Time" , "US Mountain Daylight Time" , |
1259 | "US/Arizona" |
1260 | }, /* (UTC-07:00) Arizona */ |
1261 | { |
1262 | "Coordinated Universal Time" , "Coordinated Universal Time" , |
1263 | "UTC" |
1264 | }, /* (UTC) Coordinated Universal Time */ |
1265 | { |
1266 | "UTC+12" , "UTC+12" , |
1267 | "Etc/GMT+12" |
1268 | }, /* (UTC+12:00) Coordinated Universal Time+12 */ |
1269 | { |
1270 | "UTC-02" , "UTC-02" , |
1271 | "Etc/GMT-02" |
1272 | }, /* (UTC-02:00) Coordinated Universal Time-02 */ |
1273 | { |
1274 | "UTC-08" , "UTC-08" , |
1275 | "Etc/GMT-08" |
1276 | }, /* (UTC-08:00) Coordinated Universal Time-08 */ |
1277 | { |
1278 | "UTC-09" , "UTC-09" , |
1279 | "Etc/GMT-09" |
1280 | }, /* (UTC-09:00) Coordinated Universal Time-09 */ |
1281 | { |
1282 | "UTC-11" , "UTC-11" , |
1283 | "Etc/GMT-11" |
1284 | }, /* (UTC-11:00) Coordinated Universal Time-11 */ |
1285 | { |
1286 | "Venezuela Standard Time" , "Venezuela Daylight Time" , |
1287 | "America/Caracas" , |
1288 | }, /* (UTC-04:30) Caracas */ |
1289 | { |
1290 | "Vladivostok Standard Time" , "Vladivostok Daylight Time" , |
1291 | "Asia/Vladivostok" |
1292 | }, /* (UTC+10:00) Vladivostok (RTZ 9) */ |
1293 | { |
1294 | "W. Australia Standard Time" , "W. Australia Daylight Time" , |
1295 | "Australia/Perth" |
1296 | }, /* (UTC+08:00) Perth */ |
1297 | #ifdef NOT_USED |
1298 | /* Could not find a match for this one (just a guess). Excluded for now. */ |
1299 | { |
1300 | "W. Central Africa Standard Time" , "W. Central Africa Daylight Time" , |
1301 | "WAT" |
1302 | }, /* (UTC+01:00) West Central Africa */ |
1303 | #endif |
1304 | { |
1305 | "W. Europe Standard Time" , "W. Europe Daylight Time" , |
1306 | "CET" |
1307 | }, /* (UTC+01:00) Amsterdam, Berlin, Bern, Rome, |
1308 | * Stockholm, Vienna */ |
1309 | { |
1310 | "W. Mongolia Standard Time" , "W. Mongolia Daylight Time" , |
1311 | "Asia/Hovd" |
1312 | }, /* (UTC+07:00) Hovd */ |
1313 | { |
1314 | "West Asia Standard Time" , "West Asia Daylight Time" , |
1315 | "Asia/Karachi" |
1316 | }, /* (UTC+05:00) Ashgabat, Tashkent */ |
1317 | { |
1318 | "West Bank Gaza Standard Time" , "West Bank Gaza Daylight Time" , |
1319 | "Asia/Gaza" |
1320 | }, /* (UTC+02:00) Gaza, Hebron */ |
1321 | { |
1322 | "West Pacific Standard Time" , "West Pacific Daylight Time" , |
1323 | "Pacific/Guam" |
1324 | }, /* (UTC+10:00) Guam, Port Moresby */ |
1325 | { |
1326 | "Yakutsk Standard Time" , "Yakutsk Daylight Time" , |
1327 | "Asia/Yakutsk" |
1328 | }, /* (UTC+09:00) Yakutsk */ |
1329 | { |
1330 | NULL, NULL, NULL |
1331 | } |
1332 | }; |
1333 | |
1334 | static const char * |
1335 | identify_system_timezone(void) |
1336 | { |
1337 | int i; |
1338 | char tzname[128]; |
1339 | char localtzname[256]; |
1340 | time_t t = time(NULL); |
1341 | struct tm *tm = localtime(&t); |
1342 | HKEY rootKey; |
1343 | int idx; |
1344 | |
1345 | if (!tm) |
1346 | { |
1347 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1348 | fprintf(stderr, "could not identify system time zone: localtime() failed\n" ); |
1349 | #endif |
1350 | return NULL; /* go to GMT */ |
1351 | } |
1352 | |
1353 | memset(tzname, 0, sizeof(tzname)); |
1354 | strftime(tzname, sizeof(tzname) - 1, "%Z" , tm); |
1355 | |
1356 | for (i = 0; win32_tzmap[i].stdname != NULL; i++) |
1357 | { |
1358 | if (strcmp(tzname, win32_tzmap[i].stdname) == 0 || |
1359 | strcmp(tzname, win32_tzmap[i].dstname) == 0) |
1360 | { |
1361 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1362 | fprintf(stderr, "TZ \"%s\" matches system time zone \"%s\"\n" , |
1363 | win32_tzmap[i].pgtzname, tzname); |
1364 | #endif |
1365 | return win32_tzmap[i].pgtzname; |
1366 | } |
1367 | } |
1368 | |
1369 | /* |
1370 | * Localized Windows versions return localized names for the timezone. |
1371 | * Scan the registry to find the English name, and then try matching |
1372 | * against our table again. |
1373 | */ |
1374 | memset(localtzname, 0, sizeof(localtzname)); |
1375 | if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, |
1376 | "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" , |
1377 | 0, |
1378 | KEY_READ, |
1379 | &rootKey) != ERROR_SUCCESS) |
1380 | { |
1381 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1382 | fprintf(stderr, "could not open registry key to identify system time zone: error code %lu\n" , |
1383 | GetLastError()); |
1384 | #endif |
1385 | return NULL; /* go to GMT */ |
1386 | } |
1387 | |
1388 | for (idx = 0;; idx++) |
1389 | { |
1390 | char keyname[256]; |
1391 | char zonename[256]; |
1392 | DWORD namesize; |
1393 | FILETIME lastwrite; |
1394 | HKEY key; |
1395 | LONG r; |
1396 | |
1397 | memset(keyname, 0, sizeof(keyname)); |
1398 | namesize = sizeof(keyname); |
1399 | if ((r = RegEnumKeyEx(rootKey, |
1400 | idx, |
1401 | keyname, |
1402 | &namesize, |
1403 | NULL, |
1404 | NULL, |
1405 | NULL, |
1406 | &lastwrite)) != ERROR_SUCCESS) |
1407 | { |
1408 | if (r == ERROR_NO_MORE_ITEMS) |
1409 | break; |
1410 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1411 | fprintf(stderr, "could not enumerate registry subkeys to identify system time zone: %d\n" , |
1412 | (int) r); |
1413 | #endif |
1414 | break; |
1415 | } |
1416 | |
1417 | if ((r = RegOpenKeyEx(rootKey, keyname, 0, KEY_READ, &key)) != ERROR_SUCCESS) |
1418 | { |
1419 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1420 | fprintf(stderr, "could not open registry subkey to identify system time zone: %d\n" , |
1421 | (int) r); |
1422 | #endif |
1423 | break; |
1424 | } |
1425 | |
1426 | memset(zonename, 0, sizeof(zonename)); |
1427 | namesize = sizeof(zonename); |
1428 | if ((r = RegQueryValueEx(key, "Std" , NULL, NULL, (unsigned char *) zonename, &namesize)) != ERROR_SUCCESS) |
1429 | { |
1430 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1431 | fprintf(stderr, "could not query value for key \"std\" to identify system time zone \"%s\": %d\n" , |
1432 | keyname, (int) r); |
1433 | #endif |
1434 | RegCloseKey(key); |
1435 | continue; /* Proceed to look at the next timezone */ |
1436 | } |
1437 | if (strcmp(tzname, zonename) == 0) |
1438 | { |
1439 | /* Matched zone */ |
1440 | strcpy(localtzname, keyname); |
1441 | RegCloseKey(key); |
1442 | break; |
1443 | } |
1444 | memset(zonename, 0, sizeof(zonename)); |
1445 | namesize = sizeof(zonename); |
1446 | if ((r = RegQueryValueEx(key, "Dlt" , NULL, NULL, (unsigned char *) zonename, &namesize)) != ERROR_SUCCESS) |
1447 | { |
1448 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1449 | fprintf(stderr, "could not query value for key \"dlt\" to identify system time zone \"%s\": %d\n" , |
1450 | keyname, (int) r); |
1451 | #endif |
1452 | RegCloseKey(key); |
1453 | continue; /* Proceed to look at the next timezone */ |
1454 | } |
1455 | if (strcmp(tzname, zonename) == 0) |
1456 | { |
1457 | /* Matched DST zone */ |
1458 | strcpy(localtzname, keyname); |
1459 | RegCloseKey(key); |
1460 | break; |
1461 | } |
1462 | |
1463 | RegCloseKey(key); |
1464 | } |
1465 | |
1466 | RegCloseKey(rootKey); |
1467 | |
1468 | if (localtzname[0]) |
1469 | { |
1470 | /* Found a localized name, so scan for that one too */ |
1471 | for (i = 0; win32_tzmap[i].stdname != NULL; i++) |
1472 | { |
1473 | if (strcmp(localtzname, win32_tzmap[i].stdname) == 0 || |
1474 | strcmp(localtzname, win32_tzmap[i].dstname) == 0) |
1475 | { |
1476 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1477 | fprintf(stderr, "TZ \"%s\" matches localized system time zone \"%s\" (\"%s\")\n" , |
1478 | win32_tzmap[i].pgtzname, tzname, localtzname); |
1479 | #endif |
1480 | return win32_tzmap[i].pgtzname; |
1481 | } |
1482 | } |
1483 | } |
1484 | |
1485 | #ifdef DEBUG_IDENTIFY_TIMEZONE |
1486 | fprintf(stderr, "could not find a match for system time zone \"%s\"\n" , |
1487 | tzname); |
1488 | #endif |
1489 | return NULL; /* go to GMT */ |
1490 | } |
1491 | #endif /* WIN32 */ |
1492 | |
1493 | |
1494 | /* |
1495 | * Return true if the given zone name is valid and is an "acceptable" zone. |
1496 | */ |
1497 | static bool |
1498 | validate_zone(const char *tzname) |
1499 | { |
1500 | pg_tz *tz; |
1501 | |
1502 | if (!tzname || !tzname[0]) |
1503 | return false; |
1504 | |
1505 | tz = pg_load_tz(tzname); |
1506 | if (!tz) |
1507 | return false; |
1508 | |
1509 | if (!pg_tz_acceptable(tz)) |
1510 | return false; |
1511 | |
1512 | return true; |
1513 | } |
1514 | |
1515 | /* |
1516 | * Identify a suitable default timezone setting based on the environment. |
1517 | * |
1518 | * The installation share_path must be passed in, as that is the default |
1519 | * location for the timezone database directory. |
1520 | * |
1521 | * We first look to the TZ environment variable. If not found or not |
1522 | * recognized by our own code, we see if we can identify the timezone |
1523 | * from the behavior of the system timezone library. When all else fails, |
1524 | * return NULL, indicating that we should default to GMT. |
1525 | */ |
1526 | const char * |
1527 | select_default_timezone(const char *share_path) |
1528 | { |
1529 | const char *tzname; |
1530 | |
1531 | /* Initialize timezone directory path, if needed */ |
1532 | #ifndef SYSTEMTZDIR |
1533 | snprintf(tzdirpath, sizeof(tzdirpath), "%s/timezone" , share_path); |
1534 | #endif |
1535 | |
1536 | /* Check TZ environment variable */ |
1537 | tzname = getenv("TZ" ); |
1538 | if (validate_zone(tzname)) |
1539 | return tzname; |
1540 | |
1541 | /* Nope, so try to identify the system timezone */ |
1542 | tzname = identify_system_timezone(); |
1543 | if (validate_zone(tzname)) |
1544 | return tzname; |
1545 | |
1546 | return NULL; |
1547 | } |
1548 | |