1 /*-------------------------------------------------------------------------
4 * Timezone Library Integration Functions
6 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
11 *-------------------------------------------------------------------------
13 #define NO_REDEFINE_TIMEFUNCS
22 #include "miscadmin.h"
24 #include "storage/fd.h"
26 #include "utils/datetime.h"
27 #include "utils/guc.h"
28 #include "utils/hsearch.h"
30 /* Current session timezone (controlled by TimeZone GUC) */
31 pg_tz
*session_timezone
= NULL
;
33 /* Current log timezone (controlled by log_timezone GUC) */
34 pg_tz
*log_timezone
= NULL
;
36 /* Fallback GMT timezone for last-ditch error message formatting */
37 pg_tz
*gmt_timezone
= NULL
;
38 static pg_tz gmt_timezone_data
;
41 static bool scan_directory_ci(const char *dirname
,
42 const char *fname
, int fnamelen
,
43 char *canonname
, int canonnamelen
);
44 static const char *identify_system_timezone(void);
45 static pg_tz
*get_pg_tz_for_zone(const char *tzname
);
46 static pg_tz
*select_default_timezone(void);
50 * Return full pathname of timezone data directory
56 /* normal case: timezone stuff is under our share dir */
57 static bool done_tzdir
= false;
58 static char tzdir
[MAXPGPATH
];
63 get_share_path(my_exec_path
, tzdir
);
64 strlcpy(tzdir
+ strlen(tzdir
), "/timezone", MAXPGPATH
- strlen(tzdir
));
69 /* we're configured to use system's timezone database */
76 * Given a timezone name, open() the timezone data file. Return the
77 * file descriptor if successful, -1 if not.
79 * The input name is searched for case-insensitively (we assume that the
80 * timezone database does not contain case-equivalent names).
82 * If "canonname" is not NULL, then on success the canonical spelling of the
83 * given name is stored there (it is assumed to be > TZ_STRLEN_MAX bytes!).
86 pg_open_tzfile(const char *name
, char *canonname
)
89 char fullname
[MAXPGPATH
];
94 * Loop to split the given name into directory levels; for each level,
95 * search using scan_directory_ci().
97 strcpy(fullname
, pg_TZDIR());
98 orignamelen
= fullnamelen
= strlen(fullname
);
102 const char *slashptr
;
105 slashptr
= strchr(fname
, '/');
107 fnamelen
= slashptr
- fname
;
109 fnamelen
= strlen(fname
);
110 if (fullnamelen
+ 1 + fnamelen
>= MAXPGPATH
)
111 return -1; /* not gonna fit */
112 if (!scan_directory_ci(fullname
, fname
, fnamelen
,
113 fullname
+ fullnamelen
+ 1,
114 MAXPGPATH
- fullnamelen
- 1))
116 fullname
[fullnamelen
++] = '/';
117 fullnamelen
+= strlen(fullname
+ fullnamelen
);
119 fname
= slashptr
+ 1;
125 strlcpy(canonname
, fullname
+ orignamelen
+ 1, TZ_STRLEN_MAX
+ 1);
127 return open(fullname
, O_RDONLY
| PG_BINARY
, 0);
132 * Scan specified directory for a case-insensitive match to fname
133 * (of length fnamelen --- fname may not be null terminated!). If found,
134 * copy the actual filename into canonname and return true.
137 scan_directory_ci(const char *dirname
, const char *fname
, int fnamelen
,
138 char *canonname
, int canonnamelen
)
142 struct dirent
*direntry
;
144 dirdesc
= AllocateDir(dirname
);
148 (errcode_for_file_access(),
149 errmsg("could not open directory \"%s\": %m", dirname
)));
153 while ((direntry
= ReadDir(dirdesc
, dirname
)) != NULL
)
156 * Ignore . and .., plus any other "hidden" files. This is a security
157 * measure to prevent access to files outside the timezone directory.
159 if (direntry
->d_name
[0] == '.')
162 if (strlen(direntry
->d_name
) == fnamelen
&&
163 pg_strncasecmp(direntry
->d_name
, fname
, fnamelen
) == 0)
165 /* Found our match */
166 strlcpy(canonname
, direntry
->d_name
, canonnamelen
);
179 * The following block of code attempts to determine which timezone in our
180 * timezone database is the best match for the active system timezone.
182 * On most systems, we rely on trying to match the observable behavior of
183 * the C library's localtime() function. The database zone that matches
184 * furthest into the past is the one to use. Often there will be several
185 * zones with identical rankings (since the zic database assigns multiple
186 * names to many zones). We break ties arbitrarily by preferring shorter,
187 * then alphabetically earlier zone names.
189 * Win32's native knowledge about timezones appears to be too incomplete
190 * and too different from the zic database for the above matching strategy
191 * to be of any use. But there is just a limited number of timezones
192 * available, so we can rely on a handmade mapping table instead.
197 #define T_DAY ((time_t) (60*60*24))
198 #define T_WEEK ((time_t) (60*60*24*7))
199 #define T_MONTH ((time_t) (60*60*24*31))
201 #define MAX_TEST_TIMES (52*100) /* 100 years */
206 time_t test_times
[MAX_TEST_TIMES
];
209 static void scan_available_timezones(char *tzdir
, char *tzdirsub
,
211 int *bestscore
, char *bestzonename
);
215 * Get GMT offset from a system struct tm
218 get_timezone_offset(struct tm
* tm
)
220 #if defined(HAVE_STRUCT_TM_TM_ZONE)
221 return tm
->tm_gmtoff
;
222 #elif defined(HAVE_INT_TIMEZONE)
223 return -TIMEZONE_GLOBAL
;
225 #error No way to determine TZ? Can this happen?
230 * Convenience subroutine to convert y/m/d to time_t (NOT pg_time_t)
233 build_time_t(int year
, int month
, int day
)
237 memset(&tm
, 0, sizeof(tm
));
239 tm
.tm_mon
= month
- 1;
240 tm
.tm_year
= year
- 1900;
246 * Does a system tm value match one we computed ourselves?
249 compare_tm(struct tm
* s
, struct pg_tm
* p
)
251 if (s
->tm_sec
!= p
->tm_sec
||
252 s
->tm_min
!= p
->tm_min
||
253 s
->tm_hour
!= p
->tm_hour
||
254 s
->tm_mday
!= p
->tm_mday
||
255 s
->tm_mon
!= p
->tm_mon
||
256 s
->tm_year
!= p
->tm_year
||
257 s
->tm_wday
!= p
->tm_wday
||
258 s
->tm_yday
!= p
->tm_yday
||
259 s
->tm_isdst
!= p
->tm_isdst
)
265 * See how well a specific timezone setting matches the system behavior
267 * We score a timezone setting according to the number of test times it
268 * matches. (The test times are ordered later-to-earlier, but this routine
269 * doesn't actually know that; it just scans until the first non-match.)
271 * We return -1 for a completely unusable setting; this is worse than the
272 * score of zero for a setting that works but matches not even the first
276 score_timezone(const char *tzname
, struct tztry
* tt
)
282 char cbuf
[TZ_STRLEN_MAX
+ 1];
287 * Load timezone directly. Don't use pg_tzset, because we don't want all
288 * timezones loaded in the cache at startup.
290 if (tzload(tzname
, NULL
, &tz
.state
, TRUE
) != 0)
292 if (tzname
[0] == ':' || tzparse(tzname
, &tz
.state
, FALSE
) != 0)
294 return -1; /* can't handle the TZ name at all */
298 /* Reject if leap seconds involved */
299 if (!tz_acceptable(&tz
))
301 elog(DEBUG4
, "Reject TZ \"%s\": uses leap seconds", tzname
);
305 /* Check for match at all the test times */
306 for (i
= 0; i
< tt
->n_test_times
; i
++)
308 pgtt
= (pg_time_t
) (tt
->test_times
[i
]);
309 pgtm
= pg_localtime(&pgtt
, &tz
);
311 return -1; /* probably shouldn't happen */
312 systm
= localtime(&(tt
->test_times
[i
]));
315 elog(DEBUG4
, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s, system had no data",
316 tzname
, i
, (long) pgtt
,
317 pgtm
->tm_year
+ 1900, pgtm
->tm_mon
+ 1, pgtm
->tm_mday
,
318 pgtm
->tm_hour
, pgtm
->tm_min
, pgtm
->tm_sec
,
319 pgtm
->tm_isdst
? "dst" : "std");
322 if (!compare_tm(systm
, pgtm
))
324 elog(DEBUG4
, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s",
325 tzname
, i
, (long) pgtt
,
326 pgtm
->tm_year
+ 1900, pgtm
->tm_mon
+ 1, pgtm
->tm_mday
,
327 pgtm
->tm_hour
, pgtm
->tm_min
, pgtm
->tm_sec
,
328 pgtm
->tm_isdst
? "dst" : "std",
329 systm
->tm_year
+ 1900, systm
->tm_mon
+ 1, systm
->tm_mday
,
330 systm
->tm_hour
, systm
->tm_min
, systm
->tm_sec
,
331 systm
->tm_isdst
? "dst" : "std");
334 if (systm
->tm_isdst
>= 0)
336 /* Check match of zone names, too */
337 if (pgtm
->tm_zone
== NULL
)
338 return -1; /* probably shouldn't happen */
339 memset(cbuf
, 0, sizeof(cbuf
));
340 strftime(cbuf
, sizeof(cbuf
) - 1, "%Z", systm
); /* zone abbr */
341 if (strcmp(cbuf
, pgtm
->tm_zone
) != 0)
343 elog(DEBUG4
, "TZ \"%s\" scores %d: at %ld \"%s\" versus \"%s\"",
344 tzname
, i
, (long) pgtt
,
345 pgtm
->tm_zone
, cbuf
);
351 elog(DEBUG4
, "TZ \"%s\" gets max score %d", tzname
, i
);
357 * Try to identify a timezone name (in our terminology) that best matches the
358 * observed behavior of the system timezone library. We cannot assume that
359 * the system TZ environment setting (if indeed there is one) matches our
360 * terminology, so we ignore it and just look at what localtime() returns.
363 identify_system_timezone(void)
365 static char resultbuf
[TZ_STRLEN_MAX
+ 1];
372 char tmptzdir
[MAXPGPATH
];
374 char std_zone_name
[TZ_STRLEN_MAX
+ 1],
375 dst_zone_name
[TZ_STRLEN_MAX
+ 1];
376 char cbuf
[TZ_STRLEN_MAX
+ 1];
378 /* Initialize OS timezone library */
382 * Set up the list of dates to be probed to see how well our timezone
383 * matches the system zone. We first probe January and July of the
384 * current year; this serves to quickly eliminate the vast majority of the
385 * TZ database entries. If those dates match, we probe every week for 100
386 * years backwards from the current July. (Weekly resolution is good
387 * enough to identify DST transition rules, since everybody switches on
388 * Sundays.) This is sufficient to cover most of the Unix time_t range,
389 * and we don't want to look further than that since many systems won't
390 * have sane TZ behavior further back anyway. The further
391 * back the zone matches, the better we score it. This may seem like a
392 * rather random way of doing things, but experience has shown that
393 * system-supplied timezone definitions are likely to have DST behavior
394 * that is right for the recent past and not so accurate further back.
395 * Scoring in this way allows us to recognize zones that have some
396 * commonality with the zic database, without insisting on exact match.
397 * (Note: we probe Thursdays, not Sundays, to avoid triggering
398 * DST-transition bugs in localtime itself.)
401 tm
= localtime(&tnow
);
403 return NULL
; /* give up if localtime is broken... */
404 thisyear
= tm
->tm_year
+ 1900;
406 t
= build_time_t(thisyear
, 1, 15);
408 * Round back to GMT midnight Thursday. This depends on the knowledge
409 * that the time_t origin is Thu Jan 01 1970. (With a different origin
410 * we'd be probing some other day of the week, but it wouldn't matter
411 * anyway unless localtime() had DST-transition bugs.)
416 tt
.test_times
[tt
.n_test_times
++] = t
;
418 t
= build_time_t(thisyear
, 7, 15);
421 tt
.test_times
[tt
.n_test_times
++] = t
;
423 while (tt
.n_test_times
< MAX_TEST_TIMES
)
426 tt
.test_times
[tt
.n_test_times
++] = t
;
429 /* Search for the best-matching timezone file */
430 strcpy(tmptzdir
, pg_TZDIR());
433 scan_available_timezones(tmptzdir
, tmptzdir
+ strlen(tmptzdir
) + 1,
435 &bestscore
, resultbuf
);
438 /* Ignore zic's rather silly "Factory" time zone; use GMT instead */
439 if (strcmp(resultbuf
, "Factory") == 0)
445 * Couldn't find a match in the database, so next we try constructed zone
446 * names (like "PST8PDT").
448 * First we need to determine the names of the local standard and daylight
449 * zones. The idea here is to scan forward from today until we have seen
450 * both zones, if both are in use.
452 memset(std_zone_name
, 0, sizeof(std_zone_name
));
453 memset(dst_zone_name
, 0, sizeof(dst_zone_name
));
459 * Round back to a GMT midnight so results don't depend on local time of
462 tnow
-= (tnow
% T_DAY
);
465 * We have to look a little further ahead than one year, in case today is
466 * just past a DST boundary that falls earlier in the year than the next
467 * similar boundary. Arbitrarily scan up to 14 months.
469 for (t
= tnow
; t
<= tnow
+ T_MONTH
* 14; t
+= T_MONTH
)
474 if (tm
->tm_isdst
< 0)
476 if (tm
->tm_isdst
== 0 && std_zone_name
[0] == '\0')
479 memset(cbuf
, 0, sizeof(cbuf
));
480 strftime(cbuf
, sizeof(cbuf
) - 1, "%Z", tm
); /* zone abbr */
481 strcpy(std_zone_name
, cbuf
);
482 std_ofs
= get_timezone_offset(tm
);
484 if (tm
->tm_isdst
> 0 && dst_zone_name
[0] == '\0')
487 memset(cbuf
, 0, sizeof(cbuf
));
488 strftime(cbuf
, sizeof(cbuf
) - 1, "%Z", tm
); /* zone abbr */
489 strcpy(dst_zone_name
, cbuf
);
491 /* Done if found both */
492 if (std_zone_name
[0] && dst_zone_name
[0])
496 /* We should have found a STD zone name by now... */
497 if (std_zone_name
[0] == '\0')
500 (errmsg("could not determine system time zone, defaulting to \"%s\"", "GMT"),
501 errhint("You can specify the correct timezone in postgresql.conf.")));
502 return NULL
; /* go to GMT */
505 /* If we found DST then try STD<ofs>DST */
506 if (dst_zone_name
[0] != '\0')
508 snprintf(resultbuf
, sizeof(resultbuf
), "%s%d%s",
509 std_zone_name
, -std_ofs
/ 3600, dst_zone_name
);
510 if (score_timezone(resultbuf
, &tt
) > 0)
514 /* Try just the STD timezone (works for GMT at least) */
515 strcpy(resultbuf
, std_zone_name
);
516 if (score_timezone(resultbuf
, &tt
) > 0)
520 snprintf(resultbuf
, sizeof(resultbuf
), "%s%d",
521 std_zone_name
, -std_ofs
/ 3600);
522 if (score_timezone(resultbuf
, &tt
) > 0)
526 * Did not find the timezone. Fallback to use a GMT zone. Note that the
527 * zic timezone database names the GMT-offset zones in POSIX style: plus
528 * is west of Greenwich. It's unfortunate that this is opposite of SQL
529 * conventions. Should we therefore change the names? Probably not...
531 snprintf(resultbuf
, sizeof(resultbuf
), "Etc/GMT%s%d",
532 (-std_ofs
> 0) ? "+" : "", -std_ofs
/ 3600);
535 (errmsg("could not recognize system timezone, defaulting to \"%s\"",
537 errhint("You can specify the correct timezone in postgresql.conf.")));
542 * Recursively scan the timezone database looking for the best match to
543 * the system timezone behavior.
545 * tzdir points to a buffer of size MAXPGPATH. On entry, it holds the
546 * pathname of a directory containing TZ files. We internally modify it
547 * to hold pathnames of sub-directories and files, but must restore it
548 * to its original contents before exit.
550 * tzdirsub points to the part of tzdir that represents the subfile name
551 * (ie, tzdir + the original directory name length, plus one for the
554 * tt tells about the system timezone behavior we need to match.
556 * *bestscore and *bestzonename on entry hold the best score found so far
557 * and the name of the best zone. We overwrite them if we find a better
558 * score. bestzonename must be a buffer of length TZ_STRLEN_MAX + 1.
561 scan_available_timezones(char *tzdir
, char *tzdirsub
, struct tztry
* tt
,
562 int *bestscore
, char *bestzonename
)
564 int tzdir_orig_len
= strlen(tzdir
);
566 struct dirent
*direntry
;
568 dirdesc
= AllocateDir(tzdir
);
572 (errcode_for_file_access(),
573 errmsg("could not open directory \"%s\": %m", tzdir
)));
577 while ((direntry
= ReadDir(dirdesc
, tzdir
)) != NULL
)
581 /* Ignore . and .., plus any other "hidden" files */
582 if (direntry
->d_name
[0] == '.')
585 snprintf(tzdir
+ tzdir_orig_len
, MAXPGPATH
- tzdir_orig_len
,
586 "/%s", direntry
->d_name
);
588 if (stat(tzdir
, &statbuf
) != 0)
591 (errcode_for_file_access(),
592 errmsg("could not stat \"%s\": %m", tzdir
)));
593 tzdir
[tzdir_orig_len
] = '\0';
597 if (S_ISDIR(statbuf
.st_mode
))
599 /* Recurse into subdirectory */
600 scan_available_timezones(tzdir
, tzdirsub
, tt
,
601 bestscore
, bestzonename
);
605 /* Load and test this file */
606 int score
= score_timezone(tzdirsub
, tt
);
608 if (score
> *bestscore
)
611 strlcpy(bestzonename
, tzdirsub
, TZ_STRLEN_MAX
+ 1);
613 else if (score
== *bestscore
)
615 /* Consider how to break a tie */
616 if (strlen(tzdirsub
) < strlen(bestzonename
) ||
617 (strlen(tzdirsub
) == strlen(bestzonename
) &&
618 strcmp(tzdirsub
, bestzonename
) < 0))
619 strlcpy(bestzonename
, tzdirsub
, TZ_STRLEN_MAX
+ 1);
624 tzdir
[tzdir_orig_len
] = '\0';
633 const char *stdname
; /* Windows name of standard timezone */
634 const char *dstname
; /* Windows name of daylight timezone */
635 const char *pgtzname
; /* Name of pgsql timezone to map to */
640 * This list was built from the contents of the registry at
641 * HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time
642 * Zones on Windows XP Professional SP2
644 * The zones have been matched to zic timezones by looking at the cities
645 * listed in the win32 display name (in the comment here) in most cases.
648 "Afghanistan Standard Time", "Afghanistan Daylight Time",
650 }, /* (GMT+04:30) Kabul */
652 "Alaskan Standard Time", "Alaskan Daylight Time",
654 }, /* (GMT-09:00) Alaska */
656 "Arab Standard Time", "Arab Daylight Time",
658 }, /* (GMT+03:00) Kuwait, Riyadh */
660 "Arabian Standard Time", "Arabian Daylight Time",
662 }, /* (GMT+04:00) Abu Dhabi, Muscat */
664 "Arabic Standard Time", "Arabic Daylight Time",
666 }, /* (GMT+03:00) Baghdad */
668 "Armenian Standard Time", "Armenian Daylight Time",
670 }, /* (GMT+04:00) Yerevan */
672 "Atlantic Standard Time", "Atlantic Daylight Time",
674 }, /* (GMT-04:00) Atlantic Time (Canada) */
676 "AUS Central Standard Time", "AUS Central Daylight Time",
678 }, /* (GMT+09:30) Darwin */
680 "AUS Eastern Standard Time", "AUS Eastern Daylight Time",
682 }, /* (GMT+10:00) Canberra, Melbourne, Sydney */
684 "Azores Standard Time", "Azores Daylight Time",
686 }, /* (GMT-01:00) Azores */
688 "Canada Central Standard Time", "Canada Central Daylight Time",
689 "Canada/Saskatchewan"
690 }, /* (GMT-06:00) Saskatchewan */
692 "Cape Verde Standard Time", "Cape Verde Daylight Time",
693 "Atlantic/Cape_Verde"
694 }, /* (GMT-01:00) Cape Verde Is. */
696 "Caucasus Standard Time", "Caucasus Daylight Time",
698 }, /* (GMT+04:00) Baku, Tbilisi, Yerevan */
700 "Cen. Australia Standard Time", "Cen. Australia Daylight Time",
702 }, /* (GMT+09:30) Adelaide */
704 "Central America Standard Time", "Central America Daylight Time",
706 }, /* (GMT-06:00) Central America */
708 "Central Asia Standard Time", "Central Asia Daylight Time",
710 }, /* (GMT+06:00) Astana, Dhaka */
712 "Central Europe Standard Time", "Central Europe Daylight Time",
714 }, /* (GMT+01:00) Belgrade, Bratislava, Budapest,
715 * Ljubljana, Prague */
717 "Central European Standard Time", "Central European Daylight Time",
719 }, /* (GMT+01:00) Sarajevo, Skopje, Warsaw,
722 "Central Pacific Standard Time", "Central Pacific Daylight Time",
724 }, /* (GMT+11:00) Magadan, Solomon Is., New
727 "Central Standard Time", "Central Daylight Time",
729 }, /* (GMT-06:00) Central Time (US & Canada) */
731 "Central Standard Time (Mexico)", "Central Daylight Time (Mexico)",
732 "America/Mexico_City"
733 }, /* (GMT-06:00) Guadalajara, Mexico City,
736 "China Standard Time", "China Daylight Time",
738 }, /* (GMT+08:00) Beijing, Chongqing, Hong Kong,
741 "Dateline Standard Time", "Dateline Daylight Time",
743 }, /* (GMT-12:00) International Date Line West */
745 "E. Africa Standard Time", "E. Africa Daylight Time",
747 }, /* (GMT+03:00) Nairobi */
749 "E. Australia Standard Time", "E. Australia Daylight Time",
751 }, /* (GMT+10:00) Brisbane */
753 "E. Europe Standard Time", "E. Europe Daylight Time",
755 }, /* (GMT+02:00) Bucharest */
757 "E. South America Standard Time", "E. South America Daylight Time",
759 }, /* (GMT-03:00) Brasilia */
761 "Eastern Standard Time", "Eastern Daylight Time",
763 }, /* (GMT-05:00) Eastern Time (US & Canada) */
765 "Egypt Standard Time", "Egypt Daylight Time",
767 }, /* (GMT+02:00) Cairo */
769 "Ekaterinburg Standard Time", "Ekaterinburg Daylight Time",
771 }, /* (GMT+05:00) Ekaterinburg */
773 "Fiji Standard Time", "Fiji Daylight Time",
775 }, /* (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
777 "FLE Standard Time", "FLE Daylight Time",
779 }, /* (GMT+02:00) Helsinki, Kyiv, Riga, Sofia,
780 * Tallinn, Vilnius */
782 "Georgian Standard Time", "Georgian Daylight Time",
784 }, /* (GMT+03:00) Tbilisi */
786 "GMT Standard Time", "GMT Daylight Time",
788 }, /* (GMT) Greenwich Mean Time : Dublin,
789 * Edinburgh, Lisbon, London */
791 "Greenland Standard Time", "Greenland Daylight Time",
793 }, /* (GMT-03:00) Greenland */
795 "Greenwich Standard Time", "Greenwich Daylight Time",
797 }, /* (GMT) Casablanca, Monrovia */
799 "GTB Standard Time", "GTB Daylight Time",
801 }, /* (GMT+02:00) Athens, Istanbul, Minsk */
803 "Hawaiian Standard Time", "Hawaiian Daylight Time",
805 }, /* (GMT-10:00) Hawaii */
807 "India Standard Time", "India Daylight Time",
809 }, /* (GMT+05:30) Chennai, Kolkata, Mumbai, New
812 "Iran Standard Time", "Iran Daylight Time",
814 }, /* (GMT+03:30) Tehran */
816 "Jerusalem Standard Time", "Jerusalem Daylight Time",
818 }, /* (GMT+02:00) Jerusalem */
820 "Jordan Standard Time", "Jordan Daylight Time",
822 }, /* (GMT+02:00) Amman */
824 "Korea Standard Time", "Korea Daylight Time",
826 }, /* (GMT+09:00) Seoul */
828 "Mexico Standard Time", "Mexico Daylight Time",
829 "America/Mexico_City"
830 }, /* (GMT-06:00) Guadalajara, Mexico City,
833 "Mexico Standard Time 2", "Mexico Daylight Time 2",
835 }, /* (GMT-07:00) Chihuahua, La Paz, Mazatlan */
837 "Mid-Atlantic Standard Time", "Mid-Atlantic Daylight Time",
838 "Atlantic/South_Georgia"
839 }, /* (GMT-02:00) Mid-Atlantic */
841 "Middle East Standard Time", "Middle East Daylight Time",
843 }, /* (GMT+02:00) Beirut */
845 "Montevideo Standard Time", "Montevideo Daylight Time",
847 }, /* (GMT-03:00) Montevideo */
849 "Mountain Standard Time", "Mountain Daylight Time",
851 }, /* (GMT-07:00) Mountain Time (US & Canada) */
853 "Mountain Standard Time (Mexico)", "Mountain Daylight Time (Mexico)",
855 }, /* (GMT-07:00) Chihuahua, La Paz,
858 "Myanmar Standard Time", "Myanmar Daylight Time",
860 }, /* (GMT+06:30) Rangoon */
862 "N. Central Asia Standard Time", "N. Central Asia Daylight Time",
864 }, /* (GMT+06:00) Almaty, Novosibirsk */
866 "Namibia Standard Time", "Namibia Daylight Time",
868 }, /* (GMT+02:00) Windhoek */
870 "Nepal Standard Time", "Nepal Daylight Time",
872 }, /* (GMT+05:45) Kathmandu */
874 "New Zealand Standard Time", "New Zealand Daylight Time",
876 }, /* (GMT+12:00) Auckland, Wellington */
878 "Newfoundland Standard Time", "Newfoundland Daylight Time",
879 "Canada/Newfoundland"
880 }, /* (GMT-03:30) Newfoundland */
882 "North Asia East Standard Time", "North Asia East Daylight Time",
884 }, /* (GMT+08:00) Irkutsk, Ulaan Bataar */
886 "North Asia Standard Time", "North Asia Daylight Time",
888 }, /* (GMT+07:00) Krasnoyarsk */
890 "Pacific SA Standard Time", "Pacific SA Daylight Time",
892 }, /* (GMT-04:00) Santiago */
894 "Pacific Standard Time", "Pacific Daylight Time",
896 }, /* (GMT-08:00) Pacific Time (US & Canada);
899 "Pacific Standard Time (Mexico)", "Pacific Daylight Time (Mexico)",
901 }, /* (GMT-08:00) Tijuana, Baja California */
903 "Romance Standard Time", "Romance Daylight Time",
905 }, /* (GMT+01:00) Brussels, Copenhagen, Madrid,
908 "Russian Standard Time", "Russian Daylight Time",
910 }, /* (GMT+03:00) Moscow, St. Petersburg,
913 "SA Eastern Standard Time", "SA Eastern Daylight Time",
914 "America/Buenos_Aires"
915 }, /* (GMT-03:00) Buenos Aires, Georgetown */
917 "SA Pacific Standard Time", "SA Pacific Daylight Time",
919 }, /* (GMT-05:00) Bogota, Lima, Quito */
921 "SA Western Standard Time", "SA Western Daylight Time",
923 }, /* (GMT-04:00) Caracas, La Paz */
925 "Samoa Standard Time", "Samoa Daylight Time",
927 }, /* (GMT-11:00) Midway Island, Samoa */
929 "SE Asia Standard Time", "SE Asia Daylight Time",
931 }, /* (GMT+07:00) Bangkok, Hanoi, Jakarta */
933 "Malay Peninsula Standard Time", "Malay Peninsula Daylight Time",
935 }, /* (GMT+08:00) Kuala Lumpur, Singapore */
937 "South Africa Standard Time", "South Africa Daylight Time",
939 }, /* (GMT+02:00) Harare, Pretoria */
941 "Sri Lanka Standard Time", "Sri Lanka Daylight Time",
943 }, /* (GMT+06:00) Sri Jayawardenepura */
945 "Taipei Standard Time", "Taipei Daylight Time",
947 }, /* (GMT+08:00) Taipei */
949 "Tasmania Standard Time", "Tasmania Daylight Time",
951 }, /* (GMT+10:00) Hobart */
953 "Tokyo Standard Time", "Tokyo Daylight Time",
955 }, /* (GMT+09:00) Osaka, Sapporo, Tokyo */
957 "Tonga Standard Time", "Tonga Daylight Time",
959 }, /* (GMT+13:00) Nuku'alofa */
961 "US Eastern Standard Time", "US Eastern Daylight Time",
963 }, /* (GMT-05:00) Indiana (East) */
965 "US Mountain Standard Time", "US Mountain Daylight Time",
967 }, /* (GMT-07:00) Arizona */
969 "Vladivostok Standard Time", "Vladivostok Daylight Time",
971 }, /* (GMT+10:00) Vladivostok */
973 "W. Australia Standard Time", "W. Australia Daylight Time",
975 }, /* (GMT+08:00) Perth */
976 /* {"W. Central Africa Standard Time", "W. Central Africa Daylight Time",
977 * * * * * * * ""}, Could not find a match for this one. Excluded for now. *//* (
978 * G MT+01:00) West Central Africa */
980 "W. Europe Standard Time", "W. Europe Daylight Time",
982 }, /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome,
983 * Stockholm, Vienna */
985 "West Asia Standard Time", "West Asia Daylight Time",
987 }, /* (GMT+05:00) Islamabad, Karachi, Tashkent */
989 "West Pacific Standard Time", "West Pacific Daylight Time",
991 }, /* (GMT+10:00) Guam, Port Moresby */
993 "Yakutsk Standard Time", "Yakutsk Daylight Time",
995 }, /* (GMT+09:00) Yakutsk */
1002 identify_system_timezone(void)
1006 char localtzname
[256];
1007 time_t t
= time(NULL
);
1008 struct tm
*tm
= localtime(&t
);
1015 (errmsg_internal("could not determine current date/time: localtime failed")));
1019 memset(tzname
, 0, sizeof(tzname
));
1020 strftime(tzname
, sizeof(tzname
) - 1, "%Z", tm
);
1022 for (i
= 0; win32_tzmap
[i
].stdname
!= NULL
; i
++)
1024 if (strcmp(tzname
, win32_tzmap
[i
].stdname
) == 0 ||
1025 strcmp(tzname
, win32_tzmap
[i
].dstname
) == 0)
1027 elog(DEBUG4
, "TZ \"%s\" matches Windows timezone \"%s\"",
1028 win32_tzmap
[i
].pgtzname
, tzname
);
1029 return win32_tzmap
[i
].pgtzname
;
1034 * Localized Windows versions return localized names for the timezone.
1035 * Scan the registry to find the English name, and then try matching
1036 * against our table again.
1038 memset(localtzname
, 0, sizeof(localtzname
));
1039 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE
,
1040 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
1043 &rootKey
) != ERROR_SUCCESS
)
1046 (errmsg_internal("could not open registry key to identify Windows timezone: %i", (int) GetLastError())));
1050 for (idx
= 0;; idx
++)
1059 memset(keyname
, 0, sizeof(keyname
));
1060 namesize
= sizeof(keyname
);
1061 if ((r
= RegEnumKeyEx(rootKey
,
1068 &lastwrite
)) != ERROR_SUCCESS
)
1070 if (r
== ERROR_NO_MORE_ITEMS
)
1073 (errmsg_internal("could not enumerate registry subkeys to identify Windows timezone: %i", (int) r
)));
1077 if ((r
= RegOpenKeyEx(rootKey
, keyname
, 0, KEY_READ
, &key
)) != ERROR_SUCCESS
)
1080 (errmsg_internal("could not open registry subkey to identify Windows timezone: %i", (int) r
)));
1084 memset(zonename
, 0, sizeof(zonename
));
1085 namesize
= sizeof(zonename
);
1086 if ((r
= RegQueryValueEx(key
, "Std", NULL
, NULL
, zonename
, &namesize
)) != ERROR_SUCCESS
)
1089 (errmsg_internal("could not query value for 'std' to identify Windows timezone: %i", (int) r
)));
1093 if (strcmp(tzname
, zonename
) == 0)
1096 strcpy(localtzname
, keyname
);
1100 memset(zonename
, 0, sizeof(zonename
));
1101 namesize
= sizeof(zonename
);
1102 if ((r
= RegQueryValueEx(key
, "Dlt", NULL
, NULL
, zonename
, &namesize
)) != ERROR_SUCCESS
)
1105 (errmsg_internal("could not query value for 'dlt' to identify Windows timezone: %i", (int) r
)));
1109 if (strcmp(tzname
, zonename
) == 0)
1111 /* Matched DST zone */
1112 strcpy(localtzname
, keyname
);
1120 RegCloseKey(rootKey
);
1124 /* Found a localized name, so scan for that one too */
1125 for (i
= 0; win32_tzmap
[i
].stdname
!= NULL
; i
++)
1127 if (strcmp(localtzname
, win32_tzmap
[i
].stdname
) == 0 ||
1128 strcmp(localtzname
, win32_tzmap
[i
].dstname
) == 0)
1130 elog(DEBUG4
, "TZ \"%s\" matches localized Windows timezone \"%s\" (\"%s\")",
1131 win32_tzmap
[i
].pgtzname
, tzname
, localtzname
);
1132 return win32_tzmap
[i
].pgtzname
;
1138 (errmsg("could not find a match for Windows timezone \"%s\"",
1147 * We keep loaded timezones in a hashtable so we don't have to
1148 * load and parse the TZ definition file every time one is selected.
1149 * Because we want timezone names to be found case-insensitively,
1150 * the hash key is the uppercased name of the zone.
1154 /* tznameupper contains the all-upper-case name of the timezone */
1155 char tznameupper
[TZ_STRLEN_MAX
+ 1];
1159 static HTAB
*timezone_cache
= NULL
;
1163 init_timezone_hashtable(void)
1167 MemSet(&hash_ctl
, 0, sizeof(hash_ctl
));
1169 hash_ctl
.keysize
= TZ_STRLEN_MAX
+ 1;
1170 hash_ctl
.entrysize
= sizeof(pg_tz_cache
);
1172 timezone_cache
= hash_create("Timezones",
1176 if (!timezone_cache
)
1183 * Load a timezone from file or from cache.
1184 * Does not verify that the timezone is acceptable!
1187 pg_tzset(const char *name
)
1190 struct state tzstate
;
1191 char uppername
[TZ_STRLEN_MAX
+ 1];
1192 char canonname
[TZ_STRLEN_MAX
+ 1];
1195 if (strlen(name
) > TZ_STRLEN_MAX
)
1196 return NULL
; /* not going to fit */
1198 if (!timezone_cache
)
1199 if (!init_timezone_hashtable())
1203 * Upcase the given name to perform a case-insensitive hashtable search.
1204 * (We could alternatively downcase it, but we prefer upcase so that we
1205 * can get consistently upcased results from tzparse() in case the name is
1206 * a POSIX-style timezone spec.)
1210 *p
++ = pg_toupper((unsigned char) *name
++);
1213 tzp
= (pg_tz_cache
*) hash_search(timezone_cache
,
1219 /* Timezone found in cache, nothing more to do */
1223 if (tzload(uppername
, canonname
, &tzstate
, TRUE
) != 0)
1225 if (uppername
[0] == ':' || tzparse(uppername
, &tzstate
, FALSE
) != 0)
1227 /* Unknown timezone. Fail our call instead of loading GMT! */
1230 /* For POSIX timezone specs, use uppercase name as canonical */
1231 strcpy(canonname
, uppername
);
1234 /* Save timezone in the cache */
1235 tzp
= (pg_tz_cache
*) hash_search(timezone_cache
,
1240 /* hash_search already copied uppername into the hash key */
1241 strcpy(tzp
->tz
.TZname
, canonname
);
1242 memcpy(&tzp
->tz
.state
, &tzstate
, sizeof(tzstate
));
1249 * Check whether timezone is acceptable.
1251 * What we are doing here is checking for leap-second-aware timekeeping.
1252 * We need to reject such TZ settings because they'll wreak havoc with our
1253 * date/time arithmetic.
1255 * NB: this must NOT ereport(ERROR). The caller must get control back so that
1256 * it can restore the old value of TZ if we don't like the new one.
1259 tz_acceptable(pg_tz
*tz
)
1265 * To detect leap-second timekeeping, run pg_localtime for what should be
1266 * GMT midnight, 2000-01-01. Insist that the tm_sec value be zero; any
1267 * other result has to be due to leap seconds.
1269 time2000
= (POSTGRES_EPOCH_JDATE
- UNIX_EPOCH_JDATE
) * SECS_PER_DAY
;
1270 tt
= pg_localtime(&time2000
, tz
);
1271 if (!tt
|| tt
->tm_sec
!= 0)
1279 * Get a pg_tz struct for the given timezone name. Returns NULL if name
1280 * is invalid or not an "acceptable" zone.
1283 get_pg_tz_for_zone(const char *tzname
)
1287 if (!tzname
|| !tzname
[0])
1290 tz
= pg_tzset(tzname
);
1294 if (!tz_acceptable(tz
))
1301 * Identify a suitable default timezone setting based on the environment.
1303 * We first look to the TZ environment variable. If not found or not
1304 * recognized by our own code, we see if we can identify the timezone
1305 * from the behavior of the system timezone library. When all else fails,
1309 select_default_timezone(void)
1313 def_tz
= get_pg_tz_for_zone(getenv("TZ"));
1317 def_tz
= get_pg_tz_for_zone(identify_system_timezone());
1321 def_tz
= get_pg_tz_for_zone("GMT");
1326 (errmsg("could not select a suitable default timezone"),
1327 errdetail("It appears that your GMT time zone uses leap seconds. PostgreSQL does not support leap seconds.")));
1328 return NULL
; /* keep compiler quiet */
1333 * Pre-initialize timezone library
1335 * This is called before GUC variable initialization begins. Its purpose
1336 * is to ensure that elog.c has a pgtz variable available to format timestamps
1337 * with, in case log_line_prefix is set to a value requiring that. We cannot
1338 * set log_timezone yet.
1341 pg_timezone_pre_initialize(void)
1344 * We can't use tzload() because we may not know where PGSHAREDIR is (in
1345 * particular this is true in an EXEC_BACKEND subprocess). Since this
1346 * timezone variable will only be used for emergency fallback purposes, it
1347 * seems OK to just use the "lastditch" case provided by tzparse().
1349 if (tzparse("GMT", &gmt_timezone_data
.state
, TRUE
) != 0)
1350 elog(FATAL
, "could not initialize GMT timezone");
1351 strcpy(gmt_timezone_data
.TZname
, "GMT");
1352 gmt_timezone
= &gmt_timezone_data
;
1356 * Initialize timezone library
1358 * This is called after initial loading of postgresql.conf. If no TimeZone
1359 * setting was found therein, we try to derive one from the environment.
1360 * Likewise for log_timezone.
1363 pg_timezone_initialize(void)
1365 pg_tz
*def_tz
= NULL
;
1367 /* Do we need to try to figure the session timezone? */
1368 if (pg_strcasecmp(GetConfigOption("timezone"), "UNKNOWN") == 0)
1370 /* Select setting */
1371 def_tz
= select_default_timezone();
1372 session_timezone
= def_tz
;
1373 /* Tell GUC about the value. Will redundantly call pg_tzset() */
1374 SetConfigOption("timezone", pg_get_timezone_name(def_tz
),
1375 PGC_POSTMASTER
, PGC_S_ARGV
);
1378 /* What about the log timezone? */
1379 if (pg_strcasecmp(GetConfigOption("log_timezone"), "UNKNOWN") == 0)
1381 /* Select setting, but don't duplicate work */
1383 def_tz
= select_default_timezone();
1384 log_timezone
= def_tz
;
1385 /* Tell GUC about the value. Will redundantly call pg_tzset() */
1386 SetConfigOption("log_timezone", pg_get_timezone_name(def_tz
),
1387 PGC_POSTMASTER
, PGC_S_ARGV
);
1393 * Functions to enumerate available timezones
1395 * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
1396 * structure, so the data is only valid up to the next call.
1398 * All data is allocated using palloc in the current context.
1400 #define MAX_TZDIR_DEPTH 10
1406 DIR *dirdesc
[MAX_TZDIR_DEPTH
];
1407 char *dirname
[MAX_TZDIR_DEPTH
];
1411 /* typedef pg_tzenum is declared in pgtime.h */
1414 pg_tzenumerate_start(void)
1416 pg_tzenum
*ret
= (pg_tzenum
*) palloc0(sizeof(pg_tzenum
));
1417 char *startdir
= pstrdup(pg_TZDIR());
1419 ret
->baselen
= strlen(startdir
) + 1;
1421 ret
->dirname
[0] = startdir
;
1422 ret
->dirdesc
[0] = AllocateDir(startdir
);
1423 if (!ret
->dirdesc
[0])
1425 (errcode_for_file_access(),
1426 errmsg("could not open directory \"%s\": %m", startdir
)));
1431 pg_tzenumerate_end(pg_tzenum
*dir
)
1433 while (dir
->depth
>= 0)
1435 FreeDir(dir
->dirdesc
[dir
->depth
]);
1436 pfree(dir
->dirname
[dir
->depth
]);
1443 pg_tzenumerate_next(pg_tzenum
*dir
)
1445 while (dir
->depth
>= 0)
1447 struct dirent
*direntry
;
1448 char fullname
[MAXPGPATH
];
1449 struct stat statbuf
;
1451 direntry
= ReadDir(dir
->dirdesc
[dir
->depth
], dir
->dirname
[dir
->depth
]);
1455 /* End of this directory */
1456 FreeDir(dir
->dirdesc
[dir
->depth
]);
1457 pfree(dir
->dirname
[dir
->depth
]);
1462 if (direntry
->d_name
[0] == '.')
1465 snprintf(fullname
, MAXPGPATH
, "%s/%s",
1466 dir
->dirname
[dir
->depth
], direntry
->d_name
);
1467 if (stat(fullname
, &statbuf
) != 0)
1469 (errcode_for_file_access(),
1470 errmsg("could not stat \"%s\": %m", fullname
)));
1472 if (S_ISDIR(statbuf
.st_mode
))
1474 /* Step into the subdirectory */
1475 if (dir
->depth
>= MAX_TZDIR_DEPTH
- 1)
1477 (errmsg("timezone directory stack overflow")));
1479 dir
->dirname
[dir
->depth
] = pstrdup(fullname
);
1480 dir
->dirdesc
[dir
->depth
] = AllocateDir(fullname
);
1481 if (!dir
->dirdesc
[dir
->depth
])
1483 (errcode_for_file_access(),
1484 errmsg("could not open directory \"%s\": %m",
1487 /* Start over reading in the new directory */
1492 * Load this timezone using tzload() not pg_tzset(), so we don't fill
1495 if (tzload(fullname
+ dir
->baselen
, dir
->tz
.TZname
, &dir
->tz
.state
,
1498 /* Zone could not be loaded, ignore it */
1502 if (!tz_acceptable(&dir
->tz
))
1504 /* Ignore leap-second zones */
1508 /* Timezone loaded OK. */
1512 /* Nothing more found */