#include "postgres_fe.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include "pgtz.h"
Go to the source code of this file.
Data Structures | |
struct | tztry |
Defines | |
#define | T_DAY ((time_t) (60*60*24)) |
#define | T_WEEK ((time_t) (60*60*24*7)) |
#define | T_MONTH ((time_t) (60*60*24*31)) |
#define | MAX_TEST_TIMES (52*100) |
Functions | |
const char * | select_default_timezone (const char *share_path) |
static const char * | pg_TZDIR (void) |
int | pg_open_tzfile (const char *name, char *canonname) |
static pg_tz * | pg_load_tz (const char *name) |
static void | scan_available_timezones (char *tzdir, char *tzdirsub, struct tztry *tt, int *bestscore, char *bestzonename) |
static int | get_timezone_offset (struct tm *tm) |
static time_t | build_time_t (int year, int month, int day) |
static bool | compare_tm (struct tm *s, struct pg_tm *p) |
static int | score_timezone (const char *tzname, struct tztry *tt) |
static const char * | identify_system_timezone (void) |
static bool | validate_zone (const char *tzname) |
Variables | |
static char | tzdirpath [MAXPGPATH] |
#define MAX_TEST_TIMES (52*100) |
Definition at line 145 of file findtimezone.c.
Referenced by identify_system_timezone().
#define T_DAY ((time_t) (60*60*24)) |
Definition at line 141 of file findtimezone.c.
#define T_MONTH ((time_t) (60*60*24*31)) |
Definition at line 143 of file findtimezone.c.
Referenced by identify_system_timezone().
#define T_WEEK ((time_t) (60*60*24*7)) |
Definition at line 142 of file findtimezone.c.
static time_t build_time_t | ( | int | year, | |
int | month, | |||
int | day | |||
) | [static] |
Definition at line 193 of file findtimezone.c.
References pg_tm::tm_hour, pg_tm::tm_isdst, pg_tm::tm_mday, pg_tm::tm_min, pg_tm::tm_mon, pg_tm::tm_sec, pg_tm::tm_wday, pg_tm::tm_yday, and pg_tm::tm_year.
Referenced by score_timezone().
static int get_timezone_offset | ( | struct tm * | tm | ) | [static] |
Definition at line 162 of file findtimezone.c.
Referenced by identify_system_timezone().
static const char* identify_system_timezone | ( | void | ) | [static] |
Definition at line 310 of file findtimezone.c.
References build_time_t(), get_timezone_offset(), MAX_TEST_TIMES, tztry::n_test_times, NULL, pg_TZDIR(), scan_available_timezones(), score_timezone(), snprintf(), T_MONTH, tztry::test_times, tm, and TZ_STRLEN_MAX.
Referenced by select_default_timezone().
{ 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[MAXPGPATH]; int std_ofs; char std_zone_name[TZ_STRLEN_MAX + 1], 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 Olson * 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 Olson's rather silly "Factory" 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') { #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "could not determine system time zone\n"); #endif 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 * Olson 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); #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "could not recognize system time zone, using \"%s\"\n", resultbuf); #endif return resultbuf; }
static pg_tz* pg_load_tz | ( | const char * | name | ) | [static] |
Definition at line 90 of file findtimezone.c.
References FALSE, NULL, pg_tz::state, TRUE, TZ_STRLEN_MAX, tzload(), pg_tz::TZname, and tzparse().
Referenced by score_timezone(), and validate_zone().
{ static pg_tz tz; if (strlen(name) > TZ_STRLEN_MAX) return NULL; /* not going to fit */ /* * "GMT" is always sent to tzparse(); see comments for pg_tzset(). */ if (strcmp(name, "GMT") == 0) { if (tzparse(name, &tz.state, TRUE) != 0) { /* This really, really should not happen ... */ return NULL; } } else if (tzload(name, NULL, &tz.state, TRUE) != 0) { if (name[0] == ':' || tzparse(name, &tz.state, FALSE) != 0) { return NULL; /* unknown timezone */ } } strcpy(tz.TZname, name); return &tz; }
int pg_open_tzfile | ( | const char * | name, | |
char * | canonname | |||
) |
Definition at line 64 of file findtimezone.c.
Referenced by tzload().
static const char* pg_TZDIR | ( | void | ) | [static] |
Definition at line 36 of file findtimezone.c.
References tzdirpath.
Referenced by identify_system_timezone(), and pg_open_tzfile().
{ #ifndef SYSTEMTZDIR /* normal case: timezone stuff is under our share dir */ return tzdirpath; #else /* we're configured to use system's timezone database */ return SYSTEMTZDIR; #endif }
static void scan_available_timezones | ( | char * | tzdir, | |
char * | tzdirsub, | |||
struct tztry * | tt, | |||
int * | bestscore, | |||
char * | bestzonename | |||
) | [static] |
Definition at line 509 of file findtimezone.c.
References MAXPGPATH, name, pgfnames(), pgfnames_cleanup(), score_timezone(), snprintf(), strerror(), strlcpy(), and TZ_STRLEN_MAX.
Referenced by identify_system_timezone().
{ int tzdir_orig_len = strlen(tzdir); char **names; char **namep; names = pgfnames(tzdir); if (!names) return; for (namep = names; *namep; namep++) { char *name = *namep; struct stat statbuf; /* Ignore . and .., plus any other "hidden" files */ if (name[0] == '.') continue; snprintf(tzdir + tzdir_orig_len, MAXPGPATH - tzdir_orig_len, "/%s", name); if (stat(tzdir, &statbuf) != 0) { #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "could not stat \"%s\": %s\n", tzdir, strerror(errno)); #endif tzdir[tzdir_orig_len] = '\0'; continue; } if (S_ISDIR(statbuf.st_mode)) { /* Recurse into subdirectory */ scan_available_timezones(tzdir, tzdirsub, tt, bestscore, bestzonename); } else { /* Load and test this file */ int score = score_timezone(tzdirsub, tt); if (score > *bestscore) { *bestscore = score; strlcpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1); } else if (score == *bestscore) { /* Consider how to break a tie */ if (strlen(tzdirsub) < strlen(bestzonename) || (strlen(tzdirsub) == strlen(bestzonename) && strcmp(tzdirsub, bestzonename) < 0)) strlcpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1); } } /* Restore tzdir */ tzdir[tzdir_orig_len] = '\0'; } pgfnames_cleanup(names); }
static int score_timezone | ( | const char * | tzname, | |
struct tztry * | tt | |||
) | [static] |
Definition at line 220 of file findtimezone.c.
References compare_tm(), i, tztry::n_test_times, NULL, pg_load_tz(), pg_localtime(), pg_tz_acceptable(), tztry::test_times, tm, pg_tm::tm_hour, pg_tm::tm_isdst, pg_tm::tm_mday, pg_tm::tm_min, pg_tm::tm_mon, pg_tm::tm_sec, pg_tm::tm_year, pg_tm::tm_zone, and TZ_STRLEN_MAX.
Referenced by identify_system_timezone(), and scan_available_timezones().
{ int i; pg_time_t pgtt; struct tm *systm; struct pg_tm *pgtm; char cbuf[TZ_STRLEN_MAX + 1]; pg_tz *tz; /* Load timezone definition */ tz = pg_load_tz(tzname); if (!tz) return -1; /* unrecognized zone name */ /* Reject if leap seconds involved */ if (!pg_tz_acceptable(tz)) { #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "Reject TZ \"%s\": uses leap seconds\n", tzname); #endif return -1; } /* Check for match at all the test times */ for (i = 0; i < tt->n_test_times; i++) { pgtt = (pg_time_t) (tt->test_times[i]); pgtm = pg_localtime(&pgtt, tz); if (!pgtm) return -1; /* probably shouldn't happen */ systm = localtime(&(tt->test_times[i])); if (!systm) { #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s, system had no data\n", tzname, i, (long) pgtt, pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday, pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec, pgtm->tm_isdst ? "dst" : "std"); #endif return i; } if (!compare_tm(systm, pgtm)) { #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s\n", tzname, i, (long) pgtt, pgtm->tm_year + 1900, pgtm->tm_mon + 1, pgtm->tm_mday, pgtm->tm_hour, pgtm->tm_min, pgtm->tm_sec, pgtm->tm_isdst ? "dst" : "std", systm->tm_year + 1900, systm->tm_mon + 1, systm->tm_mday, systm->tm_hour, systm->tm_min, systm->tm_sec, systm->tm_isdst ? "dst" : "std"); #endif return i; } if (systm->tm_isdst >= 0) { /* Check match of zone names, too */ if (pgtm->tm_zone == NULL) return -1; /* probably shouldn't happen */ memset(cbuf, 0, sizeof(cbuf)); strftime(cbuf, sizeof(cbuf) - 1, "%Z", systm); /* zone abbr */ if (strcmp(cbuf, pgtm->tm_zone) != 0) { #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "TZ \"%s\" scores %d: at %ld \"%s\" versus \"%s\"\n", tzname, i, (long) pgtt, pgtm->tm_zone, cbuf); #endif return i; } } } #ifdef DEBUG_IDENTIFY_TIMEZONE fprintf(stderr, "TZ \"%s\" gets max score %d\n", tzname, i); #endif return i; }
const char * select_default_timezone | ( | const char * | share_path | ) |
Definition at line 1204 of file findtimezone.c.
References identify_system_timezone(), snprintf(), tzdirpath, and validate_zone().
Referenced by setup_config().
{ const char *tzname; /* Initialize timezone directory path, if needed */ #ifndef SYSTEMTZDIR snprintf(tzdirpath, sizeof(tzdirpath), "%s/timezone", share_path); #endif /* Check TZ environment variable */ tzname = getenv("TZ"); if (validate_zone(tzname)) return tzname; /* Nope, so try to identify the system timezone */ tzname = identify_system_timezone(); if (validate_zone(tzname)) return tzname; return NULL; }
static bool validate_zone | ( | const char * | tzname | ) | [static] |
Definition at line 1175 of file findtimezone.c.
References pg_load_tz(), and pg_tz_acceptable().
Referenced by select_default_timezone().
{ pg_tz *tz; if (!tzname || !tzname[0]) return false; tz = pg_load_tz(tzname); if (!tz) return false; if (!pg_tz_acceptable(tz)) return false; return true; }
char tzdirpath[MAXPGPATH] [static] |
Definition at line 26 of file findtimezone.c.
Referenced by pg_TZDIR(), and select_default_timezone().