postgresql/src/timezone/pgtz.c

1624 lines
44 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* pgtz.c
* Timezone Library Integration Functions
*
2011-01-01 19:18:15 +01:00
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/timezone/pgtz.c
*
*-------------------------------------------------------------------------
*/
#define NO_REDEFINE_TIMEFUNCS
#include "postgres.h"
#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include "miscadmin.h"
#include "pgtz.h"
#include "storage/fd.h"
#include "tzfile.h"
#include "utils/datetime.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
/* Current session timezone (controlled by TimeZone GUC) */
pg_tz *session_timezone = NULL;
/* Current log timezone (controlled by log_timezone GUC) */
pg_tz *log_timezone = NULL;
/* Fallback GMT timezone for last-ditch error message formatting */
pg_tz *gmt_timezone = NULL;
static pg_tz gmt_timezone_data;
static bool scan_directory_ci(const char *dirname,
2007-11-15 22:14:46 +01:00
const char *fname, int fnamelen,
char *canonname, int canonnamelen);
static const char *identify_system_timezone(void);
static pg_tz *get_pg_tz_for_zone(const char *tzname);
static pg_tz *select_default_timezone(void);
/*
* Return full pathname of timezone data directory
*/
static const char *
pg_TZDIR(void)
{
#ifndef SYSTEMTZDIR
/* normal case: timezone stuff is under our share dir */
static bool done_tzdir = false;
static char tzdir[MAXPGPATH];
if (done_tzdir)
return tzdir;
get_share_path(my_exec_path, tzdir);
strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
done_tzdir = true;
return tzdir;
#else
/* we're configured to use system's timezone database */
return SYSTEMTZDIR;
#endif
}
/*
* 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[MAXPGPATH];
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;
2007-11-15 22:14:46 +01:00
int fnamelen;
slashptr = strchr(fname, '/');
if (slashptr)
fnamelen = slashptr - fname;
else
fnamelen = strlen(fname);
if (fullnamelen + 1 + fnamelen >= MAXPGPATH)
return -1; /* not gonna fit */
if (!scan_directory_ci(fullname, fname, fnamelen,
fullname + fullnamelen + 1,
MAXPGPATH - 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);
}
/*
* Scan specified directory for a case-insensitive match to fname
2007-11-15 22:14:46 +01:00
* (of length fnamelen --- fname may not be null terminated!). If found,
* copy the actual filename into canonname and return true.
*/
static bool
scan_directory_ci(const char *dirname, const char *fname, int fnamelen,
char *canonname, int canonnamelen)
{
bool found = false;
DIR *dirdesc;
struct dirent *direntry;
dirdesc = AllocateDir(dirname);
if (!dirdesc)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m", dirname)));
return false;
}
while ((direntry = ReadDir(dirdesc, dirname)) != NULL)
{
/*
2007-11-15 22:14:46 +01:00
* Ignore . and .., plus any other "hidden" files. This is a security
* measure to prevent access to files outside the timezone directory.
*/
if (direntry->d_name[0] == '.')
continue;
if (strlen(direntry->d_name) == fnamelen &&
pg_strncasecmp(direntry->d_name, fname, fnamelen) == 0)
{
/* Found our match */
strlcpy(canonname, direntry->d_name, canonnamelen);
found = true;
break;
}
}
FreeDir(dirdesc);
return found;
}
/*
* The following block of code attempts to determine which timezone in our
* timezone database is the best match for the active system timezone.
*
* On most systems, we rely on trying to match the observable behavior of
* the C library's localtime() function. The database zone that matches
* furthest into the past is the one to use. Often there will be several
* zones with identical rankings (since the zic database assigns multiple
* names to many zones). We break ties arbitrarily by preferring shorter,
* then alphabetically earlier zone names.
*
* Win32's native knowledge about timezones appears to be too incomplete
* and too different from the zic database for the above matching strategy
* to be of any use. But there is just a limited number of timezones
* available, so we can rely on a handmade mapping table instead.
*/
#ifndef WIN32
#define T_DAY ((time_t) (60*60*24))
2004-08-29 07:07:03 +02:00
#define T_WEEK ((time_t) (60*60*24*7))
#define T_MONTH ((time_t) (60*60*24*31))
#define MAX_TEST_TIMES (52*100) /* 100 years */
struct tztry
{
int n_test_times;
time_t test_times[MAX_TEST_TIMES];
};
static void scan_available_timezones(char *tzdir, char *tzdirsub,
2004-08-29 07:07:03 +02:00
struct tztry * tt,
int *bestscore, char *bestzonename);
/*
* Get GMT offset from a system struct tm
*/
static int
2005-10-15 04:49:52 +02:00
get_timezone_offset(struct tm * tm)
{
#if defined(HAVE_STRUCT_TM_TM_ZONE)
return tm->tm_gmtoff;
#elif defined(HAVE_INT_TIMEZONE)
return -TIMEZONE_GLOBAL;
#else
#error No way to determine TZ? Can this happen?
#endif
}
/*
* Convenience subroutine to convert y/m/d to time_t (NOT pg_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
2005-10-15 04:49:52 +02:00
compare_tm(struct tm * s, struct pg_tm * p)
{
if (s->tm_sec != p->tm_sec ||
s->tm_min != p->tm_min ||
s->tm_hour != p->tm_hour ||
s->tm_mday != p->tm_mday ||
s->tm_mon != p->tm_mon ||
s->tm_year != p->tm_year ||
s->tm_wday != p->tm_wday ||
s->tm_yday != p->tm_yday ||
s->tm_isdst != p->tm_isdst)
return false;
return true;
}
/*
* See how well a specific timezone setting matches the system behavior
*
* We score a timezone setting according to the number of test times it
* matches. (The test times are ordered later-to-earlier, but this routine
* doesn't actually know that; it just scans until the first non-match.)
*
* We return -1 for a completely unusable setting; this is worse than the
* score of zero for a setting that works but matches not even the first
* test time.
*/
static int
2005-10-15 04:49:52 +02:00
score_timezone(const char *tzname, struct tztry * tt)
{
int i;
pg_time_t pgtt;
2004-08-29 07:07:03 +02:00
struct tm *systm;
struct pg_tm *pgtm;
char cbuf[TZ_STRLEN_MAX + 1];
2005-10-15 04:49:52 +02:00
pg_tz tz;
2005-10-15 04:49:52 +02:00
/*
* Load timezone directly. Don't use pg_tzset, because we don't want all
* timezones loaded in the cache at startup.
*/
if (tzload(tzname, NULL, &tz.state, TRUE) != 0)
2005-10-15 04:49:52 +02:00
{
if (tzname[0] == ':' || tzparse(tzname, &tz.state, FALSE) != 0)
{
return -1; /* can't handle the TZ name at all */
}
}
/* Reject if leap seconds involved */
if (!tz_acceptable(&tz))
{
elog(DEBUG4, "Reject TZ \"%s\": uses leap seconds", tzname);
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)
2004-08-29 07:07:03 +02:00
return -1; /* probably shouldn't happen */
systm = localtime(&(tt->test_times[i]));
if (!systm)
{
elog(DEBUG4, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s, system had no data",
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");
return i;
}
if (!compare_tm(systm, pgtm))
{
elog(DEBUG4, "TZ \"%s\" scores %d: at %ld %04d-%02d-%02d %02d:%02d:%02d %s versus %04d-%02d-%02d %02d:%02d:%02d %s",
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");
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));
2004-08-29 07:07:03 +02:00
strftime(cbuf, sizeof(cbuf) - 1, "%Z", systm); /* zone abbr */
if (strcmp(cbuf, pgtm->tm_zone) != 0)
{
elog(DEBUG4, "TZ \"%s\" scores %d: at %ld \"%s\" versus \"%s\"",
tzname, i, (long) pgtt,
pgtm->tm_zone, cbuf);
return i;
}
}
}
elog(DEBUG4, "TZ \"%s\" gets max score %d", tzname, i);
return i;
}
/*
* 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[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 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;
}
/*
2005-10-15 04:49:52 +02:00
* Couldn't find a match in the database, so next we try constructed zone
* names (like "PST8PDT").
*
2005-10-15 04:49:52 +02:00
* 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);
/*
2005-10-15 04:49:52 +02:00
* Round back to a GMT midnight so results don't depend on local time of
* day
*/
tnow -= (tnow % T_DAY);
/*
2005-10-15 04:49:52 +02:00
* 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"),
2010-07-06 21:19:02 +02:00
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;
/*
2005-10-15 04:49:52 +02:00
* 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",
2004-08-29 07:07:03 +02:00
(-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),
2010-07-06 21:19:02 +02:00
errhint("You can specify the correct timezone in postgresql.conf.")));
return resultbuf;
}
/*
* Recursively scan the timezone database looking for the best match to
* the system timezone behavior.
*
2004-08-29 07:07:03 +02:00
* 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.
*
* *bestscore and *bestzonename on entry hold the best score found so far
* and the name of the best zone. We overwrite them if we find a better
* score. bestzonename must be a buffer of length TZ_STRLEN_MAX + 1.
*/
static void
2005-10-15 04:49:52 +02:00
scan_available_timezones(char *tzdir, char *tzdirsub, struct tztry * tt,
int *bestscore, char *bestzonename)
{
int tzdir_orig_len = strlen(tzdir);
DIR *dirdesc;
struct dirent *direntry;
dirdesc = AllocateDir(tzdir);
if (!dirdesc)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m", tzdir)));
return;
}
while ((direntry = ReadDir(dirdesc, tzdir)) != NULL)
{
struct stat statbuf;
/* 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)));
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 */
2004-08-29 07:07:03 +02:00
int score = score_timezone(tzdirsub, tt);
if (score > *bestscore)
{
*bestscore = score;
2007-02-10 15:58:55 +01:00
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))
2007-02-10 15:58:55 +01:00
strlcpy(bestzonename, tzdirsub, TZ_STRLEN_MAX + 1);
}
}
/* Restore tzdir */
tzdir[tzdir_orig_len] = '\0';
}
FreeDir(dirdesc);
}
2004-08-29 07:07:03 +02:00
#else /* WIN32 */
2004-08-29 07:07:03 +02:00
static const struct
{
const char *stdname; /* Windows name of standard timezone */
const char *dstname; /* Windows name of daylight timezone */
const char *pgtzname; /* Name of pgsql timezone to map to */
} win32_tzmap[] =
{
/*
* This list was built from the contents of the registry at
2005-10-15 04:49:52 +02:00
* HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time
* Zones on Windows 2003 R2.
*
* The zones have been matched to zic timezones by looking at the cities
2005-10-15 04:49:52 +02:00
* listed in the win32 display name (in the comment here) in most cases.
*/
2004-08-29 07:07:03 +02:00
{
"Afghanistan Standard Time", "Afghanistan Daylight Time",
"Asia/Kabul"
}, /* (GMT+04:30) Kabul */
{
"Alaskan Standard Time", "Alaskan Daylight Time",
"US/Alaska"
}, /* (GMT-09:00) Alaska */
{
"Arab Standard Time", "Arab Daylight Time",
"Asia/Kuwait"
}, /* (GMT+03:00) Kuwait, Riyadh */
{
"Arabian Standard Time", "Arabian Daylight Time",
"Asia/Muscat"
}, /* (GMT+04:00) Abu Dhabi, Muscat */
{
"Arabic Standard Time", "Arabic Daylight Time",
"Asia/Baghdad"
}, /* (GMT+03:00) Baghdad */
{
"Argentina Standard Time", "Argentina Daylight Time",
"America/Buenos_Aires"
}, /* (GMT-03:00) Buenos Aires */
{
"Armenian Standard Time", "Armenian Daylight Time",
"Asia/Yerevan"
}, /* (GMT+04:00) Yerevan */
2004-08-29 07:07:03 +02:00
{
"Atlantic Standard Time", "Atlantic Daylight Time",
"Canada/Atlantic"
}, /* (GMT-04:00) Atlantic Time (Canada) */
{
"AUS Central Standard Time", "AUS Central Daylight Time",
"Australia/Darwin"
}, /* (GMT+09:30) Darwin */
{
"AUS Eastern Standard Time", "AUS Eastern Daylight Time",
"Australia/Canberra"
}, /* (GMT+10:00) Canberra, Melbourne, Sydney */
{
"Azerbaijan Standard Time", "Azerbaijan Daylight Time",
"Asia/Baku"
}, /* (GMT+04:00) Baku */
2004-08-29 07:07:03 +02:00
{
"Azores Standard Time", "Azores Daylight Time",
"Atlantic/Azores"
}, /* (GMT-01:00) Azores */
{
"Bangladesh Standard Time", "Bangladesh Daylight Time",
2010-04-09 13:49:51 +02:00
"Asia/Dhaka"
}, /* (GMT+06:00) Dhaka */
2004-08-29 07:07:03 +02:00
{
"Canada Central Standard Time", "Canada Central Daylight Time",
"Canada/Saskatchewan"
}, /* (GMT-06:00) Saskatchewan */
{
"Cape Verde Standard Time", "Cape Verde Daylight Time",
"Atlantic/Cape_Verde"
}, /* (GMT-01:00) Cape Verde Is. */
{
"Caucasus Standard Time", "Caucasus Daylight Time",
"Asia/Baku"
}, /* (GMT+04:00) Baku, Tbilisi, Yerevan */
{
"Cen. Australia Standard Time", "Cen. Australia Daylight Time",
"Australia/Adelaide"
}, /* (GMT+09:30) Adelaide */
{
"Central America Standard Time", "Central America Daylight Time",
"CST6CDT"
}, /* (GMT-06:00) Central America */
{
"Central Asia Standard Time", "Central Asia Daylight Time",
"Asia/Dhaka"
}, /* (GMT+06:00) Astana, Dhaka */
{
"Central Brazilian Standard Time", "Central Brazilian Daylight Time",
2010-04-09 13:49:51 +02:00
"America/Cuiaba"
}, /* (GMT-04:00) Cuiaba */
2004-08-29 07:07:03 +02:00
{
"Central Europe Standard Time", "Central Europe Daylight Time",
"Europe/Belgrade"
2005-10-15 04:49:52 +02:00
}, /* (GMT+01:00) Belgrade, Bratislava, Budapest,
* Ljubljana, Prague */
2004-08-29 07:07:03 +02:00
{
"Central European Standard Time", "Central European Daylight Time",
"Europe/Sarajevo"
}, /* (GMT+01:00) Sarajevo, Skopje, Warsaw,
* Zagreb */
{
"Central Pacific Standard Time", "Central Pacific Daylight Time",
"Pacific/Noumea"
}, /* (GMT+11:00) Magadan, Solomon Is., New
* Caledonia */
{
"Central Standard Time", "Central Daylight Time",
"US/Central"
}, /* (GMT-06:00) Central Time (US & Canada) */
{
"Central Standard Time (Mexico)", "Central Daylight Time (Mexico)",
"America/Mexico_City"
}, /* (GMT-06:00) Guadalajara, Mexico City,
* Monterrey - New */
2004-08-29 07:07:03 +02:00
{
"China Standard Time", "China Daylight Time",
"Asia/Hong_Kong"
2005-10-15 04:49:52 +02:00
}, /* (GMT+08:00) Beijing, Chongqing, Hong Kong,
* Urumqi */
2004-08-29 07:07:03 +02:00
{
"Dateline Standard Time", "Dateline Daylight Time",
"Etc/GMT+12"
2005-10-15 04:49:52 +02:00
}, /* (GMT-12:00) International Date Line West */
2004-08-29 07:07:03 +02:00
{
"E. Africa Standard Time", "E. Africa Daylight Time",
"Africa/Nairobi"
}, /* (GMT+03:00) Nairobi */
{
"E. Australia Standard Time", "E. Australia Daylight Time",
"Australia/Brisbane"
}, /* (GMT+10:00) Brisbane */
{
"E. Europe Standard Time", "E. Europe Daylight Time",
"Europe/Bucharest"
}, /* (GMT+02:00) Bucharest */
{
"E. South America Standard Time", "E. South America Daylight Time",
"America/Araguaina"
}, /* (GMT-03:00) Brasilia */
{
"Eastern Standard Time", "Eastern Daylight Time",
"US/Eastern"
}, /* (GMT-05:00) Eastern Time (US & Canada) */
{
"Egypt Standard Time", "Egypt Daylight Time",
"Africa/Cairo"
}, /* (GMT+02:00) Cairo */
{
"Ekaterinburg Standard Time", "Ekaterinburg Daylight Time",
"Asia/Yekaterinburg"
}, /* (GMT+05:00) Ekaterinburg */
{
"Fiji Standard Time", "Fiji Daylight Time",
"Pacific/Fiji"
2005-10-15 04:49:52 +02:00
}, /* (GMT+12:00) Fiji, Kamchatka, Marshall Is. */
2004-08-29 07:07:03 +02:00
{
"FLE Standard Time", "FLE Daylight Time",
"Europe/Helsinki"
2005-10-15 04:49:52 +02:00
}, /* (GMT+02:00) Helsinki, Kyiv, Riga, Sofia,
* Tallinn, Vilnius */
{
"Georgian Standard Time", "Georgian Daylight Time",
"Asia/Tbilisi"
}, /* (GMT+03:00) Tbilisi */
2004-08-29 07:07:03 +02:00
{
"GMT Standard Time", "GMT Daylight Time",
"Europe/London"
2004-08-29 07:07:03 +02:00
}, /* (GMT) Greenwich Mean Time : Dublin,
* Edinburgh, Lisbon, London */
{
"Greenland Standard Time", "Greenland Daylight Time",
"America/Godthab"
}, /* (GMT-03:00) Greenland */
{
"Greenwich Standard Time", "Greenwich Daylight Time",
"Africa/Casablanca"
}, /* (GMT) Casablanca, Monrovia */
{
"GTB Standard Time", "GTB Daylight Time",
"Europe/Athens"
}, /* (GMT+02:00) Athens, Istanbul, Minsk */
{
"Hawaiian Standard Time", "Hawaiian Daylight Time",
"US/Hawaii"
}, /* (GMT-10:00) Hawaii */
{
"India Standard Time", "India Daylight Time",
"Asia/Calcutta"
2005-10-15 04:49:52 +02:00
}, /* (GMT+05:30) Chennai, Kolkata, Mumbai, New
* Delhi */
2004-08-29 07:07:03 +02:00
{
"Iran Standard Time", "Iran Daylight Time",
"Asia/Tehran"
}, /* (GMT+03:30) Tehran */
{
"Jerusalem Standard Time", "Jerusalem Daylight Time",
"Asia/Jerusalem"
}, /* (GMT+02:00) Jerusalem */
{
"Jordan Standard Time", "Jordan Daylight Time",
"Asia/Amman"
}, /* (GMT+02:00) Amman */
{
"Kamchatka Standard Time", "Kamchatka Daylight Time",
2010-04-09 13:49:51 +02:00
"Asia/Kamchatka"
}, /* (GMT+12:00) Petropavlovsk-Kamchatsky */
2004-08-29 07:07:03 +02:00
{
"Korea Standard Time", "Korea Daylight Time",
"Asia/Seoul"
}, /* (GMT+09:00) Seoul */
{
"Mauritius Standard Time", "Mauritius Daylight Time",
2010-04-09 13:49:51 +02:00
"Indian/Mauritius"
}, /* (GMT+04:00) Port Louis */
2004-08-29 07:07:03 +02:00
{
"Mexico Standard Time", "Mexico Daylight Time",
"America/Mexico_City"
}, /* (GMT-06:00) Guadalajara, Mexico City,
* Monterrey */
{
"Mexico Standard Time 2", "Mexico Daylight Time 2",
"America/Chihuahua"
2004-08-29 07:07:03 +02:00
}, /* (GMT-07:00) Chihuahua, La Paz, Mazatlan */
{
"Mid-Atlantic Standard Time", "Mid-Atlantic Daylight Time",
"Atlantic/South_Georgia"
}, /* (GMT-02:00) Mid-Atlantic */
{
"Middle East Standard Time", "Middle East Daylight Time",
"Asia/Beirut"
}, /* (GMT+02:00) Beirut */
{
"Montevideo Standard Time", "Montevideo Daylight Time",
"America/Montevideo"
}, /* (GMT-03:00) Montevideo */
{
"Morocco Standard Time", "Morocco Daylight Time",
2010-04-09 13:49:51 +02:00
"Africa/Casablanca"
}, /* (GMT) Casablanca */
2004-08-29 07:07:03 +02:00
{
"Mountain Standard Time", "Mountain Daylight Time",
"US/Mountain"
}, /* (GMT-07:00) Mountain Time (US & Canada) */
{
"Mountain Standard Time (Mexico)", "Mountain Daylight Time (Mexico)",
"America/Chihuahua"
}, /* (GMT-07:00) Chihuahua, La Paz, Mazatlan -
* New */
2004-08-29 07:07:03 +02:00
{
"Myanmar Standard Time", "Myanmar Daylight Time",
"Asia/Rangoon"
}, /* (GMT+06:30) Rangoon */
{
"N. Central Asia Standard Time", "N. Central Asia Daylight Time",
"Asia/Novosibirsk"
}, /* (GMT+06:00) Novosibirsk */
{
"Namibia Standard Time", "Namibia Daylight Time",
"Africa/Windhoek"
}, /* (GMT+02:00) Windhoek */
2004-08-29 07:07:03 +02:00
{
"Nepal Standard Time", "Nepal Daylight Time",
"Asia/Katmandu"
}, /* (GMT+05:45) Kathmandu */
{
"New Zealand Standard Time", "New Zealand Daylight Time",
"Pacific/Auckland"
}, /* (GMT+12:00) Auckland, Wellington */
{
"Newfoundland Standard Time", "Newfoundland Daylight Time",
"Canada/Newfoundland"
}, /* (GMT-03:30) Newfoundland */
{
"North Asia East Standard Time", "North Asia East Daylight Time",
"Asia/Irkutsk"
}, /* (GMT+08:00) Irkutsk, Ulaan Bataar */
{
"North Asia Standard Time", "North Asia Daylight Time",
"Asia/Krasnoyarsk"
}, /* (GMT+07:00) Krasnoyarsk */
{
"Pacific SA Standard Time", "Pacific SA Daylight Time",
"America/Santiago"
}, /* (GMT-04:00) Santiago */
{
"Pacific Standard Time", "Pacific Daylight Time",
"US/Pacific"
}, /* (GMT-08:00) Pacific Time (US & Canada);
* Tijuana */
{
"Pacific Standard Time (Mexico)", "Pacific Daylight Time (Mexico)",
"America/Tijuana"
}, /* (GMT-08:00) Tijuana, Baja California */
{
"Pakistan Standard Time", "Pakistan Daylight Time",
2010-04-09 13:49:51 +02:00
"Asia/Karachi"
}, /* (GMT+05:00) Islamabad, Karachi */
{
"Paraguay Standard Time", "Paraguay Daylight Time",
2010-04-09 13:49:51 +02:00
"America/Asuncion"
}, /* (GMT-04:00) Asuncion */
2004-08-29 07:07:03 +02:00
{
"Romance Standard Time", "Romance Daylight Time",
"Europe/Brussels"
2005-10-15 04:49:52 +02:00
}, /* (GMT+01:00) Brussels, Copenhagen, Madrid,
* Paris */
2004-08-29 07:07:03 +02:00
{
"Russian Standard Time", "Russian Daylight Time",
"Europe/Moscow"
}, /* (GMT+03:00) Moscow, St. Petersburg,
* Volgograd */
{
"SA Eastern Standard Time", "SA Eastern Daylight Time",
"America/Buenos_Aires"
}, /* (GMT-03:00) Buenos Aires, Georgetown */
{
"SA Pacific Standard Time", "SA Pacific Daylight Time",
"America/Bogota"
}, /* (GMT-05:00) Bogota, Lima, Quito */
{
"SA Western Standard Time", "SA Western Daylight Time",
"America/Caracas"
}, /* (GMT-04:00) Caracas, La Paz */
{
"Samoa Standard Time", "Samoa Daylight Time",
"Pacific/Midway"
}, /* (GMT-11:00) Midway Island, Samoa */
{
"SE Asia Standard Time", "SE Asia Daylight Time",
"Asia/Bangkok"
}, /* (GMT+07:00) Bangkok, Hanoi, Jakarta */
{
"Malay Peninsula Standard Time", "Malay Peninsula Daylight Time",
"Asia/Kuala_Lumpur"
}, /* (GMT+08:00) Kuala Lumpur, Singapore */
{
"South Africa Standard Time", "South Africa Daylight Time",
"Africa/Harare"
}, /* (GMT+02:00) Harare, Pretoria */
{
"Sri Lanka Standard Time", "Sri Lanka Daylight Time",
"Asia/Colombo"
}, /* (GMT+06:00) Sri Jayawardenepura */
{
"Taipei Standard Time", "Taipei Daylight Time",
"Asia/Taipei"
}, /* (GMT+08:00) Taipei */
{
"Tasmania Standard Time", "Tasmania Daylight Time",
"Australia/Hobart"
}, /* (GMT+10:00) Hobart */
{
"Tokyo Standard Time", "Tokyo Daylight Time",
"Asia/Tokyo"
}, /* (GMT+09:00) Osaka, Sapporo, Tokyo */
{
"Tonga Standard Time", "Tonga Daylight Time",
"Pacific/Tongatapu"
}, /* (GMT+13:00) Nuku'alofa */
{
"Ulaanbaatar Standard Time", "Ulaanbaatar Daylight Time",
"Asia/Ulaanbaatar",
}, /* (GMT+08:00) Ulaanbaatar */
2004-08-29 07:07:03 +02:00
{
"US Eastern Standard Time", "US Eastern Daylight Time",
"US/Eastern"
}, /* (GMT-05:00) Indiana (East) */
{
"US Mountain Standard Time", "US Mountain Daylight Time",
"US/Arizona"
}, /* (GMT-07:00) Arizona */
{
"Coordinated Universal Time", "Coordinated Universal Time",
"UTC"
}, /* (GMT) Coordinated Universal Time */
{
"UTC+12", "UTC+12",
"Etc/GMT+12"
}, /* (GMT+12:00) Coordinated Universal Time+12 */
{
"UTC-02", "UTC-02",
"Etc/GMT-02"
}, /* (GMT-02:00) Coordinated Universal Time-02 */
{
"UTC-11", "UTC-11",
"Etc/GMT-11"
}, /* (GMT-11:00) Coordinated Universal Time-11 */
{
"Venezuela Standard Time", "Venezuela Daylight Time",
"America/Caracas",
}, /* (GMT-04:30) Caracas */
2004-08-29 07:07:03 +02:00
{
"Vladivostok Standard Time", "Vladivostok Daylight Time",
"Asia/Vladivostok"
}, /* (GMT+10:00) Vladivostok */
{
"W. Australia Standard Time", "W. Australia Daylight Time",
"Australia/Perth"
}, /* (GMT+08:00) Perth */
#ifdef NOT_USED
/* Could not find a match for this one (just a guess). Excluded for now. */
{
"W. Central Africa Standard Time", "W. Central Africa Daylight Time",
"WAT"
}, /* (GMT+01:00) West Central Africa */
#endif
2004-08-29 07:07:03 +02:00
{
"W. Europe Standard Time", "W. Europe Daylight Time",
"CET"
2005-10-15 04:49:52 +02:00
}, /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome,
* Stockholm, Vienna */
2004-08-29 07:07:03 +02:00
{
"West Asia Standard Time", "West Asia Daylight Time",
"Asia/Karachi"
2005-10-15 04:49:52 +02:00
}, /* (GMT+05:00) Islamabad, Karachi, Tashkent */
2004-08-29 07:07:03 +02:00
{
"West Pacific Standard Time", "West Pacific Daylight Time",
"Pacific/Guam"
}, /* (GMT+10:00) Guam, Port Moresby */
{
"Yakutsk Standard Time", "Yakutsk Daylight Time",
"Asia/Yakutsk"
}, /* (GMT+09:00) Yakutsk */
{
NULL, NULL, NULL
}
};
static const char *
identify_system_timezone(void)
{
2004-08-29 07:07:03 +02:00
int i;
char tzname[128];
2005-10-15 04:49:52 +02:00
char localtzname[256];
2004-08-29 07:07:03 +02:00
time_t t = time(NULL);
struct tm *tm = localtime(&t);
2005-10-15 04:49:52 +02:00
HKEY rootKey;
int idx;
if (!tm)
{
ereport(LOG,
2010-07-06 21:19:02 +02:00
(errmsg("could not identify system time zone: localtime() failed"),
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 */
}
memset(tzname, 0, sizeof(tzname));
2004-08-29 07:07:03 +02:00
strftime(tzname, sizeof(tzname) - 1, "%Z", tm);
2004-08-29 07:07:03 +02:00
for (i = 0; win32_tzmap[i].stdname != NULL; i++)
{
if (strcmp(tzname, win32_tzmap[i].stdname) == 0 ||
strcmp(tzname, win32_tzmap[i].dstname) == 0)
{
elog(DEBUG4, "TZ \"%s\" matches system time zone \"%s\"",
win32_tzmap[i].pgtzname, tzname);
return win32_tzmap[i].pgtzname;
}
}
/*
2005-10-15 04:49:52 +02:00
* Localized Windows versions return localized names for the timezone.
* Scan the registry to find the English name, and then try matching
* against our table again.
*/
memset(localtzname, 0, sizeof(localtzname));
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
2005-10-15 04:49:52 +02:00
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
0,
KEY_READ,
&rootKey) != ERROR_SUCCESS)
{
ereport(LOG,
(errmsg("could not open registry key to identify system time zone: error code %lu",
GetLastError()),
errdetail("The PostgreSQL time zone will be set to \"%s\".",
"GMT"),
2010-07-06 21:19:02 +02:00
errhint("You can specify the correct timezone in postgresql.conf.")));
return NULL; /* go to GMT */
}
2005-10-15 04:49:52 +02:00
for (idx = 0;; idx++)
{
char keyname[256];
char zonename[256];
DWORD namesize;
FILETIME lastwrite;
HKEY key;
LONG r;
memset(keyname, 0, sizeof(keyname));
namesize = sizeof(keyname);
2005-10-15 04:49:52 +02:00
if ((r = RegEnumKeyEx(rootKey,
idx,
keyname,
&namesize,
NULL,
NULL,
NULL,
&lastwrite)) != ERROR_SUCCESS)
{
if (r == ERROR_NO_MORE_ITEMS)
break;
ereport(LOG,
(errmsg_internal("could not enumerate registry subkeys to identify system time zone: %d", (int) r)));
break;
}
2005-10-15 04:49:52 +02:00
if ((r = RegOpenKeyEx(rootKey, keyname, 0, KEY_READ, &key)) != ERROR_SUCCESS)
{
ereport(LOG,
(errmsg_internal("could not open registry subkey to identify system time zone: %d", (int) r)));
break;
}
2005-10-15 04:49:52 +02:00
memset(zonename, 0, sizeof(zonename));
namesize = sizeof(zonename);
if ((r = RegQueryValueEx(key, "Std", NULL, NULL, (unsigned char *) zonename, &namesize)) != ERROR_SUCCESS)
{
ereport(LOG,
(errmsg_internal("could not query value for key \"std\" to identify system time zone \"%s\": %d",
keyname, (int) r)));
RegCloseKey(key);
2010-07-06 21:19:02 +02:00
continue; /* Proceed to look at the next timezone */
}
if (strcmp(tzname, zonename) == 0)
{
/* Matched zone */
strcpy(localtzname, keyname);
RegCloseKey(key);
break;
}
memset(zonename, 0, sizeof(zonename));
namesize = sizeof(zonename);
if ((r = RegQueryValueEx(key, "Dlt", NULL, NULL, (unsigned char *) zonename, &namesize)) != ERROR_SUCCESS)
{
ereport(LOG,
(errmsg_internal("could not query value for key \"dlt\" to identify system time zone \"%s\": %d",
keyname, (int) r)));
RegCloseKey(key);
2010-07-06 21:19:02 +02:00
continue; /* Proceed to look at the next timezone */
}
if (strcmp(tzname, zonename) == 0)
{
/* Matched DST zone */
strcpy(localtzname, keyname);
RegCloseKey(key);
break;
}
RegCloseKey(key);
}
RegCloseKey(rootKey);
if (localtzname[0])
{
/* Found a localized name, so scan for that one too */
for (i = 0; win32_tzmap[i].stdname != NULL; i++)
{
if (strcmp(localtzname, win32_tzmap[i].stdname) == 0 ||
strcmp(localtzname, win32_tzmap[i].dstname) == 0)
{
elog(DEBUG4, "TZ \"%s\" matches localized system time zone \"%s\" (\"%s\")",
win32_tzmap[i].pgtzname, tzname, localtzname);
return win32_tzmap[i].pgtzname;
}
}
}
ereport(LOG,
(errmsg("could not find a match for system time zone \"%s\"",
tzname),
errdetail("The PostgreSQL time zone will be set to \"%s\".",
"GMT"),
2010-07-06 21:19:02 +02:00
errhint("You can specify the correct timezone in postgresql.conf.")));
return NULL; /* go to GMT */
}
2004-08-29 07:07:03 +02:00
#endif /* WIN32 */
/*
* We keep loaded timezones in a hashtable so we don't have to
* load and parse the TZ definition file every time one is selected.
* Because we want timezone names to be found case-insensitively,
* the hash key is the uppercased name of the zone.
*/
typedef struct
{
/* tznameupper contains the all-upper-case name of the timezone */
char tznameupper[TZ_STRLEN_MAX + 1];
pg_tz tz;
} pg_tz_cache;
static HTAB *timezone_cache = NULL;
static bool
init_timezone_hashtable(void)
{
HASHCTL hash_ctl;
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = TZ_STRLEN_MAX + 1;
hash_ctl.entrysize = sizeof(pg_tz_cache);
timezone_cache = hash_create("Timezones",
4,
&hash_ctl,
HASH_ELEM);
if (!timezone_cache)
return false;
return true;
}
/*
* Load a timezone from file or from cache.
* Does not verify that the timezone is acceptable!
*/
struct pg_tz *
pg_tzset(const char *name)
{
pg_tz_cache *tzp;
struct state tzstate;
char uppername[TZ_STRLEN_MAX + 1];
char canonname[TZ_STRLEN_MAX + 1];
char *p;
2005-10-15 04:49:52 +02:00
if (strlen(name) > TZ_STRLEN_MAX)
return NULL; /* not going to fit */
if (!timezone_cache)
if (!init_timezone_hashtable())
return NULL;
/*
* Upcase the given name to perform a case-insensitive hashtable search.
* (We could alternatively downcase it, but we prefer upcase so that we
2007-11-15 22:14:46 +01:00
* can get consistently upcased results from tzparse() in case the name is
* a POSIX-style timezone spec.)
*/
p = uppername;
while (*name)
*p++ = pg_toupper((unsigned char) *name++);
*p = '\0';
tzp = (pg_tz_cache *) hash_search(timezone_cache,
uppername,
HASH_FIND,
NULL);
if (tzp)
{
/* Timezone found in cache, nothing more to do */
return &tzp->tz;
}
if (tzload(uppername, canonname, &tzstate, TRUE) != 0)
{
if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0)
{
/* Unknown timezone. Fail our call instead of loading GMT! */
return NULL;
}
/* For POSIX timezone specs, use uppercase name as canonical */
strcpy(canonname, uppername);
}
/* Save timezone in the cache */
tzp = (pg_tz_cache *) hash_search(timezone_cache,
uppername,
HASH_ENTER,
NULL);
2005-10-15 04:49:52 +02:00
/* hash_search already copied uppername into the hash key */
strcpy(tzp->tz.TZname, canonname);
memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
return &tzp->tz;
}
/*
* Check whether timezone is acceptable.
*
* What we are doing here is checking for leap-second-aware timekeeping.
* We need to reject such TZ settings because they'll wreak havoc with our
* date/time arithmetic.
*
* NB: this must NOT ereport(ERROR). The caller must get control back so that
* it can restore the old value of TZ if we don't like the new one.
*/
bool
tz_acceptable(pg_tz *tz)
{
struct pg_tm *tt;
pg_time_t time2000;
/*
2005-10-15 04:49:52 +02:00
* To detect leap-second timekeeping, run pg_localtime for what should be
* GMT midnight, 2000-01-01. Insist that the tm_sec value be zero; any
* other result has to be due to leap seconds.
*/
2005-05-23 23:54:02 +02:00
time2000 = (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY;
tt = pg_localtime(&time2000, tz);
if (!tt || tt->tm_sec != 0)
return false;
return true;
}
/*
2007-11-15 22:14:46 +01:00
* Get a pg_tz struct for the given timezone name. Returns NULL if name
* is invalid or not an "acceptable" zone.
*/
static pg_tz *
get_pg_tz_for_zone(const char *tzname)
{
pg_tz *tz;
if (!tzname || !tzname[0])
return NULL;
2005-10-15 04:49:52 +02:00
tz = pg_tzset(tzname);
if (!tz)
return NULL;
if (!tz_acceptable(tz))
return NULL;
return tz;
}
/*
* Identify a suitable default timezone setting based on the environment.
*
* We first look to the TZ environment variable. If not found or not
* recognized by our own code, we see if we can identify the timezone
* from the behavior of the system timezone library. When all else fails,
* fall back to GMT.
*/
static pg_tz *
select_default_timezone(void)
{
pg_tz *def_tz;
def_tz = get_pg_tz_for_zone(getenv("TZ"));
if (def_tz)
return def_tz;
def_tz = get_pg_tz_for_zone(identify_system_timezone());
if (def_tz)
return def_tz;
def_tz = get_pg_tz_for_zone("GMT");
if (def_tz)
return def_tz;
ereport(FATAL,
(errmsg("could not select a suitable default time zone"),
errdetail("It appears that your GMT time zone uses leap seconds. PostgreSQL does not support leap seconds.")));
return NULL; /* keep compiler quiet */
}
/*
* Pre-initialize timezone library
*
* This is called before GUC variable initialization begins. Its purpose
* is to ensure that elog.c has a pgtz variable available to format timestamps
2007-11-15 22:14:46 +01:00
* with, in case log_line_prefix is set to a value requiring that. We cannot
* set log_timezone yet.
*/
void
pg_timezone_pre_initialize(void)
{
/*
2007-11-15 22:14:46 +01:00
* We can't use tzload() because we may not know where PGSHAREDIR is (in
* particular this is true in an EXEC_BACKEND subprocess). Since this
* timezone variable will only be used for emergency fallback purposes, it
* seems OK to just use the "lastditch" case provided by tzparse().
*/
if (tzparse("GMT", &gmt_timezone_data.state, TRUE) != 0)
elog(FATAL, "could not initialize GMT time zone");
strcpy(gmt_timezone_data.TZname, "GMT");
gmt_timezone = &gmt_timezone_data;
}
/*
* Initialize timezone library
*
* This is called after initial loading of postgresql.conf. If no TimeZone
* setting was found therein, we try to derive one from the environment.
* Likewise for log_timezone.
Split PGC_S_DEFAULT into two values, for true boot_val vs computed default. Failure to distinguish these cases is the real cause behind the recent reports of Windows builds crashing on 'infinity'::timestamp, which was directly due to failure to establish a value of timezone_abbreviations in postmaster child processes. The postmaster had the desired value, but write_one_nondefault_variable() didn't transmit it to backends. To fix that, invent a new value PGC_S_DYNAMIC_DEFAULT, and be sure to use that or PGC_S_ENV_VAR (as appropriate) for "default" settings that are computed during initialization. (We need both because there's at least one variable that could receive a value from either source.) This commit also fixes ProcessConfigFile's failure to restore the correct default value for certain GUC variables if they are set in postgresql.conf and then removed/commented out of the file. We have to recompute and reinstall the value for any GUC variable that could have received a value from PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR sources, and there were a number of oversights. (That whole thing is a crock that needs to be redesigned, but not today.) However, I intentionally didn't make it work "exactly right" for the cases of timezone and log_timezone. The exactly right behavior would involve running select_default_timezone, which we'd have to do independently in each postgres process, causing the whole database to become entirely unresponsive for as much as several seconds. That didn't seem like a good idea, especially since the variable's removal from postgresql.conf might be just an accidental edit. Instead the behavior is to adopt the previously active setting as if it were default. Note that this patch creates an ABI break for extensions that use any of the PGC_S_XXX constants; they'll need to be recompiled.
2011-05-12 01:57:38 +02:00
*
* Note: this is also called from ProcessConfigFile, to re-establish valid
* GUC settings if the GUCs have been reset to default following their
* removal from postgresql.conf.
*/
void
pg_timezone_initialize(void)
{
pg_tz *def_tz = NULL;
/*
* Make sure that session_timezone and log_timezone are set.
* (session_timezone could still be NULL even if a timezone value was set
* in postgresql.conf, if that setting was interval-based rather than
* timezone-based.)
*/
if (!session_timezone)
{
def_tz = select_default_timezone();
session_timezone = def_tz;
}
if (!log_timezone)
{
/* Don't duplicate work */
if (!def_tz)
def_tz = select_default_timezone();
log_timezone = def_tz;
}
Split PGC_S_DEFAULT into two values, for true boot_val vs computed default. Failure to distinguish these cases is the real cause behind the recent reports of Windows builds crashing on 'infinity'::timestamp, which was directly due to failure to establish a value of timezone_abbreviations in postmaster child processes. The postmaster had the desired value, but write_one_nondefault_variable() didn't transmit it to backends. To fix that, invent a new value PGC_S_DYNAMIC_DEFAULT, and be sure to use that or PGC_S_ENV_VAR (as appropriate) for "default" settings that are computed during initialization. (We need both because there's at least one variable that could receive a value from either source.) This commit also fixes ProcessConfigFile's failure to restore the correct default value for certain GUC variables if they are set in postgresql.conf and then removed/commented out of the file. We have to recompute and reinstall the value for any GUC variable that could have received a value from PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR sources, and there were a number of oversights. (That whole thing is a crock that needs to be redesigned, but not today.) However, I intentionally didn't make it work "exactly right" for the cases of timezone and log_timezone. The exactly right behavior would involve running select_default_timezone, which we'd have to do independently in each postgres process, causing the whole database to become entirely unresponsive for as much as several seconds. That didn't seem like a good idea, especially since the variable's removal from postgresql.conf might be just an accidental edit. Instead the behavior is to adopt the previously active setting as if it were default. Note that this patch creates an ABI break for extensions that use any of the PGC_S_XXX constants; they'll need to be recompiled.
2011-05-12 01:57:38 +02:00
/*
* Now, set the timezone and log_timezone GUCs if they're still default.
* (This will redundantly call pg_tzset().)
*
* We choose to label these values PGC_S_ENV_VAR, rather than
* PGC_S_DYNAMIC_DEFAULT which would be functionally equivalent, because
* they came either from getenv("TZ") or from libc behavior that's
* determined by process environment of some kind.
*
* Note: in the case where a setting has just been removed from
* postgresql.conf, this code will not do what you might expect, namely
* call select_default_timezone() and install that value as the setting.
* Rather, the previously active setting --- typically the one from
2011-06-09 20:32:50 +02:00
* postgresql.conf --- will be reinstalled, relabeled as PGC_S_ENV_VAR. If
* we did try to install the "correct" default value, the effect would be
* that each postmaster child would independently run an extremely
Split PGC_S_DEFAULT into two values, for true boot_val vs computed default. Failure to distinguish these cases is the real cause behind the recent reports of Windows builds crashing on 'infinity'::timestamp, which was directly due to failure to establish a value of timezone_abbreviations in postmaster child processes. The postmaster had the desired value, but write_one_nondefault_variable() didn't transmit it to backends. To fix that, invent a new value PGC_S_DYNAMIC_DEFAULT, and be sure to use that or PGC_S_ENV_VAR (as appropriate) for "default" settings that are computed during initialization. (We need both because there's at least one variable that could receive a value from either source.) This commit also fixes ProcessConfigFile's failure to restore the correct default value for certain GUC variables if they are set in postgresql.conf and then removed/commented out of the file. We have to recompute and reinstall the value for any GUC variable that could have received a value from PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR sources, and there were a number of oversights. (That whole thing is a crock that needs to be redesigned, but not today.) However, I intentionally didn't make it work "exactly right" for the cases of timezone and log_timezone. The exactly right behavior would involve running select_default_timezone, which we'd have to do independently in each postgres process, causing the whole database to become entirely unresponsive for as much as several seconds. That didn't seem like a good idea, especially since the variable's removal from postgresql.conf might be just an accidental edit. Instead the behavior is to adopt the previously active setting as if it were default. Note that this patch creates an ABI break for extensions that use any of the PGC_S_XXX constants; they'll need to be recompiled.
2011-05-12 01:57:38 +02:00
* expensive search of the timezone database, bringing the database to its
* knees for possibly multiple seconds. This is so unpleasant, and could
* so easily be triggered quite unintentionally, that it seems better to
* violate the principle of least astonishment.
*/
if (GetConfigOptionResetString("timezone") == NULL)
SetConfigOption("timezone", pg_get_timezone_name(session_timezone),
PGC_POSTMASTER, PGC_S_ENV_VAR);
Split PGC_S_DEFAULT into two values, for true boot_val vs computed default. Failure to distinguish these cases is the real cause behind the recent reports of Windows builds crashing on 'infinity'::timestamp, which was directly due to failure to establish a value of timezone_abbreviations in postmaster child processes. The postmaster had the desired value, but write_one_nondefault_variable() didn't transmit it to backends. To fix that, invent a new value PGC_S_DYNAMIC_DEFAULT, and be sure to use that or PGC_S_ENV_VAR (as appropriate) for "default" settings that are computed during initialization. (We need both because there's at least one variable that could receive a value from either source.) This commit also fixes ProcessConfigFile's failure to restore the correct default value for certain GUC variables if they are set in postgresql.conf and then removed/commented out of the file. We have to recompute and reinstall the value for any GUC variable that could have received a value from PGC_S_DYNAMIC_DEFAULT or PGC_S_ENV_VAR sources, and there were a number of oversights. (That whole thing is a crock that needs to be redesigned, but not today.) However, I intentionally didn't make it work "exactly right" for the cases of timezone and log_timezone. The exactly right behavior would involve running select_default_timezone, which we'd have to do independently in each postgres process, causing the whole database to become entirely unresponsive for as much as several seconds. That didn't seem like a good idea, especially since the variable's removal from postgresql.conf might be just an accidental edit. Instead the behavior is to adopt the previously active setting as if it were default. Note that this patch creates an ABI break for extensions that use any of the PGC_S_XXX constants; they'll need to be recompiled.
2011-05-12 01:57:38 +02:00
if (GetConfigOptionResetString("log_timezone") == NULL)
SetConfigOption("log_timezone", pg_get_timezone_name(log_timezone),
PGC_POSTMASTER, PGC_S_ENV_VAR);
}
/*
* Functions to enumerate available timezones
*
* Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
* structure, so the data is only valid up to the next call.
*
* All data is allocated using palloc in the current context.
*/
#define MAX_TZDIR_DEPTH 10
2006-10-04 02:30:14 +02:00
struct pg_tzenum
{
int baselen;
int depth;
DIR *dirdesc[MAX_TZDIR_DEPTH];
char *dirname[MAX_TZDIR_DEPTH];
struct pg_tz tz;
};
2006-10-04 02:30:14 +02:00
/* typedef pg_tzenum is declared in pgtime.h */
pg_tzenum *
2006-10-04 02:30:14 +02:00
pg_tzenumerate_start(void)
{
2006-10-04 02:30:14 +02:00
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);
2006-10-04 02:30:14 +02:00
if (!ret->dirdesc[0])
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m", startdir)));
return ret;
}
void
pg_tzenumerate_end(pg_tzenum *dir)
{
while (dir->depth >= 0)
{
FreeDir(dir->dirdesc[dir->depth]);
pfree(dir->dirname[dir->depth]);
dir->depth--;
}
pfree(dir);
}
pg_tz *
pg_tzenumerate_next(pg_tzenum *dir)
{
while (dir->depth >= 0)
{
struct dirent *direntry;
2006-10-04 02:30:14 +02:00
char fullname[MAXPGPATH];
struct stat statbuf;
direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
if (!direntry)
{
/* End of this directory */
FreeDir(dir->dirdesc[dir->depth]);
pfree(dir->dirname[dir->depth]);
dir->depth--;
continue;
}
if (direntry->d_name[0] == '.')
continue;
snprintf(fullname, MAXPGPATH, "%s/%s",
dir->dirname[dir->depth], direntry->d_name);
if (stat(fullname, &statbuf) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat \"%s\": %m", fullname)));
if (S_ISDIR(statbuf.st_mode))
{
/* Step into the subdirectory */
2006-10-04 02:30:14 +02:00
if (dir->depth >= MAX_TZDIR_DEPTH - 1)
ereport(ERROR,
2010-07-06 21:19:02 +02:00
(errmsg_internal("timezone directory stack overflow")));
dir->depth++;
dir->dirname[dir->depth] = pstrdup(fullname);
dir->dirdesc[dir->depth] = AllocateDir(fullname);
2006-10-04 02:30:14 +02:00
if (!dir->dirdesc[dir->depth])
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
fullname)));
/* Start over reading in the new directory */
continue;
}
/*
2006-10-04 02:30:14 +02:00
* Load this timezone using tzload() not pg_tzset(), so we don't fill
* the cache
*/
if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state,
TRUE) != 0)
{
/* Zone could not be loaded, ignore it */
continue;
}
if (!tz_acceptable(&dir->tz))
{
/* Ignore leap-second zones */
continue;
}
/* Timezone loaded OK. */
return &dir->tz;
}
/* Nothing more found */
return NULL;
}