Skip to content
Snippets Groups Projects
Commit 76c50c08 authored by Tom Lane's avatar Tom Lane
Browse files

Add code to identify_system_timezone() to try all zones in the zic

database, not just ones that we cons up POSIX names for.  This looks
grim but it seems to take less than a second even on a relatively slow
machine, and since it only happens once during postmaster startup, that
seems acceptable.
parent 17ff1814
No related branches found
No related tags found
No related merge requests found
......@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.14 2004/05/24 02:30:29 tgl Exp $
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.15 2004/05/25 18:08:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -15,18 +15,39 @@
#include "postgres.h"
#include <ctype.h>
#include <sys/stat.h>
#include "miscadmin.h"
#include "pgtime.h"
#include "pgtz.h"
#include "storage/fd.h"
#include "tzfile.h"
#include "utils/elog.h"
#include "utils/guc.h"
#define T_DAY ((time_t) (60*60*24))
#define T_MONTH ((time_t) (60*60*24*31))
struct tztry
{
char std_zone_name[TZ_STRLEN_MAX + 1],
dst_zone_name[TZ_STRLEN_MAX + 1];
#define MAX_TEST_TIMES 10
int n_test_times;
time_t test_times[MAX_TEST_TIMES];
};
static char tzdir[MAXPGPATH];
static int done_tzdir = 0;
static bool scan_available_timezones(char *tzdir, char *tzdirsub,
struct tztry *tt);
/*
* Return full pathname of timezone data directory
*/
char *
pg_TZDIR(void)
{
......@@ -41,22 +62,69 @@ pg_TZDIR(void)
}
/*
* Try to determine the system timezone (as opposed to the timezone
* set in our own library).
* Get GMT offset from a system struct tm
*/
#define T_DAY ((time_t) (60*60*24))
#define T_MONTH ((time_t) (60*60*24*31))
static int
get_timezone_offset(struct tm *tm)
{
#if defined(HAVE_STRUCT_TM_TM_ZONE)
return tm->tm_gmtoff;
#elif defined(HAVE_INT_TIMEZONE)
#ifdef HAVE_UNDERSCORE_TIMEZONE
return -_timezone;
#else
return -timezone;
#endif
#else
#error No way to determine TZ? Can this happen?
#endif
}
struct tztry
/*
* Grotty kluge for win32 ... do we really need this?
*/
#ifdef WIN32
#define TZABBREV(tz) win32_get_timezone_abbrev(tz)
static char *
win32_get_timezone_abbrev(const char *tz)
{
char std_zone_name[TZ_STRLEN_MAX + 1],
dst_zone_name[TZ_STRLEN_MAX + 1];
#define MAX_TEST_TIMES 5
int n_test_times;
time_t test_times[MAX_TEST_TIMES];
};
static char w32tzabbr[TZ_STRLEN_MAX + 1];
int l = 0;
const char *c;
for (c = tz; *c; c++)
{
if (isupper((unsigned char) *c))
w32tzabbr[l++] = *c;
}
w32tzabbr[l] = '\0';
return w32tzabbr;
}
#else
#define TZABBREV(tz) (tz)
#endif
/*
* Convenience subroutine to convert y/m/d to time_t
*/
static time_t
build_time_t(int year, int month, int day)
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_mday = day;
tm.tm_mon = month - 1;
tm.tm_year = year - 1900;
return mktime(&tm);
}
/*
* Does a system tm value match one we computed ourselves?
*/
static bool
compare_tm(struct tm *s, struct pg_tm *p)
{
......@@ -73,12 +141,16 @@ compare_tm(struct tm *s, struct pg_tm *p)
return true;
}
/*
* See if a specific timezone setting matches the system behavior
*/
static bool
try_timezone(char *tzname, struct tztry *tt)
try_timezone(const char *tzname, struct tztry *tt)
{
int i;
struct tm *systm;
struct pg_tm *pgtm;
char cbuf[TZ_STRLEN_MAX + 1];
if (!pg_tzset(tzname))
return false; /* can't handle the TZ name at all */
......@@ -92,51 +164,25 @@ try_timezone(char *tzname, struct tztry *tt)
systm = localtime(&(tt->test_times[i]));
if (!compare_tm(systm, pgtm))
return false;
if (systm->tm_isdst >= 0)
{
/* Check match of zone names, too */
if (pgtm->tm_zone == NULL)
return false;
memset(cbuf, 0, sizeof(cbuf));
strftime(cbuf, sizeof(cbuf) - 1, "%Z", systm); /* zone abbr */
if (strcmp(TZABBREV(cbuf), pgtm->tm_zone) != 0)
return false;
}
}
return true;
}
static int
get_timezone_offset(struct tm *tm)
{
#if defined(HAVE_STRUCT_TM_TM_ZONE)
return tm->tm_gmtoff;
#elif defined(HAVE_INT_TIMEZONE)
#ifdef HAVE_UNDERSCORE_TIMEZONE
return -_timezone;
#else
return -timezone;
#endif
#else
#error No way to determine TZ? Can this happen?
#endif
}
#ifdef WIN32
#define TZABBREV(tz) win32_get_timezone_abbrev(tz)
static char *
win32_get_timezone_abbrev(char *tz)
{
static char w32tzabbr[TZ_STRLEN_MAX + 1];
int l = 0;
char *c;
/* Reject if leap seconds involved */
if (!tz_acceptable())
return false;
for (c = tz; *c; c++)
{
if (isupper(*c))
w32tzabbr[l++] = *c;
}
w32tzabbr[l] = '\0';
return w32tzabbr;
return true;
}
#else
#define TZABBREV(tz) tz
#endif
/*
* Try to identify a timezone name (in our terminology) that matches the
......@@ -155,6 +201,7 @@ identify_system_timezone(void)
int std_ofs = 0;
struct tztry tt;
struct tm *tm;
char tmptzdir[MAXPGPATH];
char cbuf[TZ_STRLEN_MAX + 1];
/* Initialize OS timezone library */
......@@ -225,6 +272,20 @@ identify_system_timezone(void)
}
}
/*
* Add a couple of historical dates as well; without this we are likely
* to choose an accidental match, such as Antartica/Palmer when we
* really want America/Santiago. Ideally we'd probe some dates before
* 1970 too, but that is guaranteed to fail if the system TZ library
* doesn't cope with DST before 1970.
*/
tt.test_times[tt.n_test_times++] = build_time_t(1970, 1, 15);
tt.test_times[tt.n_test_times++] = build_time_t(1970, 7, 15);
tt.test_times[tt.n_test_times++] = build_time_t(1990, 4, 1);
tt.test_times[tt.n_test_times++] = build_time_t(1990, 10, 1);
Assert(tt.n_test_times <= MAX_TEST_TIMES);
/* We should have found a STD zone name by now... */
if (tt.std_zone_name[0] == '\0')
{
......@@ -234,7 +295,17 @@ identify_system_timezone(void)
return NULL; /* go to GMT */
}
/* If we found DST too then try STD<ofs>DST */
/* Search for a matching timezone file */
strcpy(tmptzdir, pg_TZDIR());
if (scan_available_timezones(tmptzdir,
tmptzdir + strlen(tmptzdir) + 1,
&tt))
{
StrNCpy(resultbuf, pg_get_current_timezone(), sizeof(resultbuf));
return resultbuf;
}
/* If we found DST then try STD<ofs>DST */
if (tt.dst_zone_name[0] != '\0')
{
snprintf(resultbuf, sizeof(resultbuf), "%s%d%s",
......@@ -270,6 +341,96 @@ identify_system_timezone(void)
return resultbuf;
}
/*
* Recursively scan the timezone database looking for a usable match to
* the system timezone behavior.
*
* tzdir points to a buffer of size MAXPGPATH. On entry, it holds the
* pathname of a directory containing TZ files. We internally modify it
* to hold pathnames of sub-directories and files, but must restore it
* to its original contents before exit.
*
* tzdirsub points to the part of tzdir that represents the subfile name
* (ie, tzdir + the original directory name length, plus one for the
* first added '/').
*
* tt tells about the system timezone behavior we need to match.
*
* On success, returns TRUE leaving the proper timezone selected.
* On failure, returns FALSE with a random timezone selected.
*/
static bool
scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry *tt)
{
int tzdir_orig_len = strlen(tzdir);
bool found = false;
DIR *dirdesc;
dirdesc = AllocateDir(tzdir);
if (!dirdesc)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m", tzdir)));
return false;
}
for (;;)
{
struct dirent *direntry;
struct stat statbuf;
errno = 0;
direntry = readdir(dirdesc);
if (!direntry)
{
if (errno)
ereport(LOG,
(errcode_for_file_access(),
errmsg("error reading directory: %m")));
break;
}
/* Ignore . and .., plus any other "hidden" files */
if (direntry->d_name[0] == '.')
continue;
snprintf(tzdir + tzdir_orig_len, MAXPGPATH - tzdir_orig_len,
"/%s", direntry->d_name);
if (stat(tzdir, &statbuf) != 0)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not stat \"%s\": %m", tzdir)));
continue;
}
if (S_ISDIR(statbuf.st_mode))
{
/* Recurse into subdirectory */
found = scan_available_timezones(tzdir, tzdirsub, tt);
if (found)
break;
}
else
{
/* Load and test this file */
found = try_timezone(tzdirsub, tt);
if (found)
break;
}
}
FreeDir(dirdesc);
/* Restore tzdir */
tzdir[tzdir_orig_len] = '\0';
return found;
}
/*
* Check whether timezone is acceptable.
*
......@@ -351,6 +512,6 @@ pg_timezone_initialize(void)
/* Select setting */
def_tz = select_default_timezone();
/* Tell GUC about the value. Will redundantly call pg_tzset() */
SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ENV_VAR);
SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ARGV);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment