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