pg_tzenum * pg_tzenumerate_start(void) { pg_tzenum *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum)); char *startdir = pstrdup(pg_TZDIR()); ret->baselen = strlen(startdir) + 1; ret->depth = 0; ret->dirname[0] = startdir; ret->dirdesc[0] = AllocateDir(startdir); if (!ret->dirdesc[0]) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", startdir))); return ret; }
/* * Given a timezone name, open() the timezone data file. Return the * file descriptor if successful, -1 if not. * * This is simpler than the backend function of the same name because * we assume that the input string has the correct case already, so there * is no need for case-folding. (This is obviously true if we got the file * name from the filesystem to start with. The only other place it can come * from is the environment variable TZ, and there seems no need to allow * case variation in that; other programs aren't likely to.) * * If "canonname" is not NULL, then on success the canonical spelling of the * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!). * This is redundant but kept for compatibility with the backend code. */ int pg_open_tzfile(const char *name, char *canonname) { char fullname[MAXPGPATH]; if (canonname) strlcpy(canonname, name, TZ_STRLEN_MAX + 1); strlcpy(fullname, pg_TZDIR(), sizeof(fullname)); if (strlen(fullname) + 1 + strlen(name) >= MAXPGPATH) return -1; /* not gonna fit */ strcat(fullname, "/"); strcat(fullname, name); return open(fullname, O_RDONLY | PG_BINARY, 0); }
/* * Given a timezone name, open() the timezone data file. Return the * file descriptor if successful, -1 if not. * * The input name is searched for case-insensitively (we assume that the * timezone database does not contain case-equivalent names). * * If "canonname" is not NULL, then on success the canonical spelling of the * given name is stored there (it is assumed to be > TZ_STRLEN_MAX bytes!). */ int pg_open_tzfile(const char *name, char *canonname) { const char *fname; char fullname[MAX_PG_PATH]; int fullnamelen; int orignamelen; /* * Loop to split the given name into directory levels; for each level, * search using scan_directory_ci(). */ strcpy(fullname, pg_TZDIR()); orignamelen = fullnamelen = strlen(fullname); fname = name; for (;;) { const char *slashptr; int fnamelen; slashptr = strchr(fname, '/'); if (slashptr) fnamelen = slashptr - fname; else fnamelen = strlen(fname); if (fullnamelen + 1 + fnamelen >= MAX_PG_PATH) return -1; /* not gonna fit */ if (!scan_directory_ci(fullname, fname, fnamelen, fullname + fullnamelen + 1, MAX_PG_PATH - fullnamelen - 1)) return -1; fullname[fullnamelen++] = '/'; fullnamelen += strlen(fullname + fullnamelen); if (slashptr) fname = slashptr + 1; else break; } if (canonname) strlcpy(canonname, fullname + orignamelen + 1, TZ_STRLEN_MAX + 1); return open(fullname, O_RDONLY | PG_BINARY, 0); }
/* * Try to identify a timezone name (in our terminology) that best matches the * observed behavior of the system timezone library. We cannot assume that * the system TZ environment setting (if indeed there is one) matches our * terminology, so we ignore it and just look at what localtime() returns. */ static const char* identify_system_timezone(void) { static char resultbuf[TZ_STRLEN_MAX + 1]; time_t tnow; time_t t; struct tztry tt; struct tm *tm; int thisyear; int bestscore; char tmptzdir[MAX_PG_PATH]; int std_ofs; char std_zone_name[TZ_STRLEN_MAX + 1]; char dst_zone_name[TZ_STRLEN_MAX + 1]; char cbuf[TZ_STRLEN_MAX + 1]; /* Initialize OS timezone library */ tzset(); /* * Set up the list of dates to be probed to see how well our timezone * matches the system zone. We first probe January and July of the * current year; this serves to quickly eliminate the vast majority of the * TZ database entries. If those dates match, we probe every week for 100 * years backwards from the current July. (Weekly resolution is good * enough to identify DST transition rules, since everybody switches on * Sundays.) This is sufficient to cover most of the Unix time_t range, * and we don't want to look further than that since many systems won't * have sane TZ behavior further back anyway. The further back the zone * matches, the better we score it. This may seem like a rather random * way of doing things, but experience has shown that system-supplied * timezone definitions are likely to have DST behavior that is right for * the recent past and not so accurate further back. Scoring in this way * allows us to recognize zones that have some commonality with the zic * database, without insisting on exact match. (Note: we probe Thursdays, * not Sundays, to avoid triggering DST-transition bugs in localtime * itself.) */ tnow = time(NULL); tm = localtime(&tnow); if (!tm) return NULL; /* give up if localtime is broken... */ thisyear = tm->tm_year + 1900; t = build_time_t(thisyear, 1, 15); /* * Round back to GMT midnight Thursday. This depends on the knowledge * that the time_t origin is Thu Jan 01 1970. (With a different origin * we'd be probing some other day of the week, but it wouldn't matter * anyway unless localtime() had DST-transition bugs.) */ t -= (t % T_WEEK); tt.n_test_times = 0; tt.test_times[tt.n_test_times++] = t; t = build_time_t(thisyear, 7, 15); t -= (t % T_WEEK); tt.test_times[tt.n_test_times++] = t; while (tt.n_test_times < MAX_TEST_TIMES) { t -= T_WEEK; tt.test_times[tt.n_test_times++] = t; } /* Search for the best-matching timezone file */ strcpy(tmptzdir, pg_TZDIR()); bestscore = -1; resultbuf[0] = '\0'; scan_available_timezones(tmptzdir, tmptzdir + strlen(tmptzdir) + 1, &tt, &bestscore, resultbuf); if (bestscore > 0) { /* Ignore zic's rather silly "Factory" time zone; use GMT instead */ if (strcmp(resultbuf, "Factory") == 0) return NULL; return resultbuf; } /* * Couldn't find a match in the database, so next we try constructed zone * names (like "PST8PDT"). * * First we need to determine the names of the local standard and daylight * zones. The idea here is to scan forward from today until we have seen * both zones, if both are in use. */ memset(std_zone_name, 0, sizeof(std_zone_name)); memset(dst_zone_name, 0, sizeof(dst_zone_name)); std_ofs = 0; tnow = time(NULL); /* * Round back to a GMT midnight so results don't depend on local time of * day */ tnow -= (tnow % T_DAY); /* * We have to look a little further ahead than one year, in case today is * just past a DST boundary that falls earlier in the year than the next * similar boundary. Arbitrarily scan up to 14 months. */ for (t = tnow; t <= tnow + T_MONTH * 14; t += T_MONTH) { tm = localtime(&t); if (!tm) continue; if (tm->tm_isdst < 0) continue; if (tm->tm_isdst == 0 && std_zone_name[0] == '\0') { /* found STD zone */ memset(cbuf, 0, sizeof(cbuf)); strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */ strcpy(std_zone_name, cbuf); std_ofs = get_timezone_offset(tm); } if (tm->tm_isdst > 0 && dst_zone_name[0] == '\0') { /* found DST zone */ memset(cbuf, 0, sizeof(cbuf)); strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */ strcpy(dst_zone_name, cbuf); } /* Done if found both */ if (std_zone_name[0] && dst_zone_name[0]) break; } /* We should have found a STD zone name by now... */ if (std_zone_name[0] == '\0') { ereport(LOG, ( errmsg("could not determine system time zone"), errdetail("The PostgreSQL time zone will be set to \"%s\".", "GMT"), errhint("You can specify the correct timezone in postgresql.conf."))); return NULL; /* go to GMT */ } /* If we found DST then try STD<ofs>DST */ if (dst_zone_name[0] != '\0') { snprintf(resultbuf, sizeof(resultbuf), "%s%d%s", std_zone_name, -std_ofs / 3600, dst_zone_name); if (score_timezone(resultbuf, &tt) > 0) return resultbuf; } /* Try just the STD timezone (works for GMT at least) */ strcpy(resultbuf, std_zone_name); if (score_timezone(resultbuf, &tt) > 0) return resultbuf; /* Try STD<ofs> */ snprintf(resultbuf, sizeof(resultbuf), "%s%d", std_zone_name, -std_ofs / 3600); if (score_timezone(resultbuf, &tt) > 0) return resultbuf; /* * Did not find the timezone. Fallback to use a GMT zone. Note that the * zic timezone database names the GMT-offset zones in POSIX style: plus * is west of Greenwich. It's unfortunate that this is opposite of SQL * conventions. Should we therefore change the names? Probably not... */ snprintf(resultbuf, sizeof(resultbuf), "Etc/GMT%s%d", (-std_ofs > 0) ? "+" : "", -std_ofs / 3600); ereport(LOG, ( errmsg("could not recognize system time zone"), errdetail("The PostgreSQL time zone will be set to \"%s\".", resultbuf), errhint("You can specify the correct timezone in postgresql.conf."))); return resultbuf; }