| 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 |  | 
|---|