#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().
1.7.1