mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Rewrite identify_system_timezone() to give it better-than-chance odds
of correctly identifying the system's daylight-savings transition rules. This still begs the question of how to look through the zic database to find a matching zone definition, but at least now we'll have some chance of recognizing the match when we find it.
This commit is contained in:
@ -6,7 +6,7 @@
|
|||||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.13 2004/05/23 23:26:53 tgl Exp $
|
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.14 2004/05/24 02:30:29 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -44,19 +44,16 @@ pg_TZDIR(void)
|
|||||||
* Try to determine the system timezone (as opposed to the timezone
|
* Try to determine the system timezone (as opposed to the timezone
|
||||||
* set in our own library).
|
* set in our own library).
|
||||||
*/
|
*/
|
||||||
#define T_YEAR (60*60*24*365)
|
#define T_DAY ((time_t) (60*60*24))
|
||||||
#define T_MONTH (60*60*24*30)
|
#define T_MONTH ((time_t) (60*60*24*31))
|
||||||
|
|
||||||
struct tztry
|
struct tztry
|
||||||
{
|
{
|
||||||
time_t std_t,
|
char std_zone_name[TZ_STRLEN_MAX + 1],
|
||||||
dst_t;
|
dst_zone_name[TZ_STRLEN_MAX + 1];
|
||||||
char std_time[TZ_STRLEN_MAX + 1],
|
#define MAX_TEST_TIMES 5
|
||||||
dst_time[TZ_STRLEN_MAX + 1];
|
int n_test_times;
|
||||||
int std_ofs,
|
time_t test_times[MAX_TEST_TIMES];
|
||||||
dst_ofs;
|
|
||||||
struct tm std_tm,
|
|
||||||
dst_tm;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -77,30 +74,25 @@ compare_tm(struct tm * s, struct pg_tm * p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
try_timezone(char *tzname, struct tztry * tt, bool checkdst)
|
try_timezone(char *tzname, struct tztry *tt)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
|
struct tm *systm;
|
||||||
struct pg_tm *pgtm;
|
struct pg_tm *pgtm;
|
||||||
|
|
||||||
if (!pg_tzset(tzname))
|
if (!pg_tzset(tzname))
|
||||||
return false; /* If this timezone couldn't be picked at
|
return false; /* can't handle the TZ name at all */
|
||||||
* all */
|
|
||||||
|
|
||||||
/* Verify standard time */
|
/* Check for match at all the test times */
|
||||||
pgtm = pg_localtime(&(tt->std_t));
|
for (i = 0; i < tt->n_test_times; i++)
|
||||||
|
{
|
||||||
|
pgtm = pg_localtime(&(tt->test_times[i]));
|
||||||
if (!pgtm)
|
if (!pgtm)
|
||||||
|
return false; /* probably shouldn't happen */
|
||||||
|
systm = localtime(&(tt->test_times[i]));
|
||||||
|
if (!compare_tm(systm, pgtm))
|
||||||
return false;
|
return false;
|
||||||
if (!compare_tm(&(tt->std_tm), pgtm))
|
}
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!checkdst)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
/* Now check daylight time */
|
|
||||||
pgtm = pg_localtime(&(tt->dst_t));
|
|
||||||
if (!pgtm)
|
|
||||||
return false;
|
|
||||||
if (!compare_tm(&(tt->dst_tm), pgtm))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -150,88 +142,132 @@ win32_get_timezone_abbrev(char *tz)
|
|||||||
* Try to identify a timezone name (in our terminology) that matches the
|
* Try to identify a timezone name (in our terminology) that matches the
|
||||||
* observed behavior of the system timezone library. We cannot assume that
|
* observed behavior of the system timezone library. We cannot assume that
|
||||||
* the system TZ environment setting (if indeed there is one) matches our
|
* the system TZ environment setting (if indeed there is one) matches our
|
||||||
* terminology, so ignore it and just look at what localtime() returns.
|
* terminology, so we ignore it and just look at what localtime() returns.
|
||||||
*/
|
*/
|
||||||
static char *
|
static char *
|
||||||
identify_system_timezone(void)
|
identify_system_timezone(void)
|
||||||
{
|
{
|
||||||
static char __tzbuf[TZ_STRLEN_MAX + 1];
|
static char resultbuf[TZ_STRLEN_MAX + 1];
|
||||||
bool std_found = false,
|
time_t tnow;
|
||||||
dst_found = false;
|
|
||||||
time_t tnow = time(NULL);
|
|
||||||
time_t t;
|
time_t t;
|
||||||
|
int nowisdst,
|
||||||
|
curisdst;
|
||||||
|
int std_ofs = 0;
|
||||||
struct tztry tt;
|
struct tztry tt;
|
||||||
|
struct tm *tm;
|
||||||
char cbuf[TZ_STRLEN_MAX + 1];
|
char cbuf[TZ_STRLEN_MAX + 1];
|
||||||
|
|
||||||
/* Initialize OS timezone library */
|
/* Initialize OS timezone library */
|
||||||
tzset();
|
tzset();
|
||||||
|
|
||||||
|
/* No info yet */
|
||||||
memset(&tt, 0, sizeof(tt));
|
memset(&tt, 0, sizeof(tt));
|
||||||
|
|
||||||
for (t = tnow; t < tnow + T_YEAR; t += T_MONTH)
|
/*
|
||||||
{
|
* The idea here is to scan forward from today and try to locate the
|
||||||
struct tm *tm = localtime(&t);
|
* next two daylight-savings transition boundaries. We will test for
|
||||||
|
* correct results on the day before and after each boundary; this
|
||||||
|
* gives at least some confidence that we've selected the right DST
|
||||||
|
* rule set.
|
||||||
|
*/
|
||||||
|
tnow = time(NULL);
|
||||||
|
|
||||||
if (tm->tm_isdst == 0 && !std_found)
|
/*
|
||||||
|
* Round back to a GMT midnight so results don't depend on local time
|
||||||
|
* of day
|
||||||
|
*/
|
||||||
|
tnow -= (tnow % T_DAY);
|
||||||
|
|
||||||
|
/* Always test today, so we have at least one test point */
|
||||||
|
tt.test_times[tt.n_test_times++] = tnow;
|
||||||
|
|
||||||
|
tm = localtime(&tnow);
|
||||||
|
nowisdst = tm->tm_isdst;
|
||||||
|
curisdst = nowisdst;
|
||||||
|
|
||||||
|
if (curisdst == 0)
|
||||||
{
|
{
|
||||||
/* Standard time */
|
/* Set up STD zone name, in case we are in a non-DST zone */
|
||||||
memcpy(&tt.std_tm, tm, sizeof(struct tm));
|
|
||||||
memset(cbuf, 0, sizeof(cbuf));
|
memset(cbuf, 0, sizeof(cbuf));
|
||||||
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
|
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
|
||||||
strcpy(tt.std_time, TZABBREV(cbuf));
|
strcpy(tt.std_zone_name, TZABBREV(cbuf));
|
||||||
tt.std_ofs = get_timezone_offset(tm);
|
/* Also preset std_ofs */
|
||||||
tt.std_t = t;
|
std_ofs = get_timezone_offset(tm);
|
||||||
std_found = true;
|
|
||||||
}
|
|
||||||
else if (tm->tm_isdst == 1 && !dst_found)
|
|
||||||
{
|
|
||||||
/* Daylight time */
|
|
||||||
memcpy(&tt.dst_tm, tm, sizeof(struct tm));
|
|
||||||
memset(cbuf, 0, sizeof(cbuf));
|
|
||||||
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
|
|
||||||
strcpy(tt.dst_time, TZABBREV(cbuf));
|
|
||||||
tt.dst_ofs = get_timezone_offset(tm);
|
|
||||||
tt.dst_t = t;
|
|
||||||
dst_found = true;
|
|
||||||
}
|
|
||||||
if (std_found && dst_found)
|
|
||||||
break; /* Got both standard and daylight */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std_found)
|
/*
|
||||||
|
* 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_DAY; t < tnow + T_MONTH * 14; t += T_DAY)
|
||||||
|
{
|
||||||
|
tm = localtime(&t);
|
||||||
|
if (tm->tm_isdst >= 0 && tm->tm_isdst != curisdst)
|
||||||
|
{
|
||||||
|
/* Found a boundary */
|
||||||
|
tt.test_times[tt.n_test_times++] = t - T_DAY;
|
||||||
|
tt.test_times[tt.n_test_times++] = t;
|
||||||
|
curisdst = tm->tm_isdst;
|
||||||
|
/* Save STD or DST zone name, also std_ofs */
|
||||||
|
memset(cbuf, 0, sizeof(cbuf));
|
||||||
|
strftime(cbuf, sizeof(cbuf) - 1, "%Z", tm); /* zone abbr */
|
||||||
|
if (curisdst == 0)
|
||||||
|
{
|
||||||
|
strcpy(tt.std_zone_name, TZABBREV(cbuf));
|
||||||
|
std_ofs = get_timezone_offset(tm);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
strcpy(tt.dst_zone_name, TZABBREV(cbuf));
|
||||||
|
/* Have we found two boundaries? */
|
||||||
|
if (tt.n_test_times >= 5)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We should have found a STD zone name by now... */
|
||||||
|
if (tt.std_zone_name[0] == '\0')
|
||||||
{
|
{
|
||||||
/* Failed to determine TZ! */
|
|
||||||
ereport(LOG,
|
ereport(LOG,
|
||||||
(errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"),
|
(errmsg("unable to determine system timezone, defaulting to \"%s\"", "GMT"),
|
||||||
errhint("You can specify the correct timezone in postgresql.conf.")));
|
errhint("You can specify the correct timezone in postgresql.conf.")));
|
||||||
return NULL; /* go to GMT */
|
return NULL; /* go to GMT */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dst_found)
|
/* If we found DST too then try STD<ofs>DST */
|
||||||
|
if (tt.dst_zone_name[0] != '\0')
|
||||||
{
|
{
|
||||||
/* Try STD<ofs>DST */
|
snprintf(resultbuf, sizeof(resultbuf), "%s%d%s",
|
||||||
sprintf(__tzbuf, "%s%d%s", tt.std_time, -tt.std_ofs / 3600, tt.dst_time);
|
tt.std_zone_name, -std_ofs / 3600, tt.dst_zone_name);
|
||||||
if (try_timezone(__tzbuf, &tt, dst_found))
|
if (try_timezone(resultbuf, &tt))
|
||||||
return __tzbuf;
|
return resultbuf;
|
||||||
}
|
}
|
||||||
/* Try just the STD timezone */
|
|
||||||
strcpy(__tzbuf, tt.std_time);
|
/* Try just the STD timezone (works for GMT at least) */
|
||||||
if (try_timezone(__tzbuf, &tt, dst_found))
|
strcpy(resultbuf, tt.std_zone_name);
|
||||||
return __tzbuf;
|
if (try_timezone(resultbuf, &tt))
|
||||||
|
return resultbuf;
|
||||||
|
|
||||||
|
/* Try STD<ofs> */
|
||||||
|
snprintf(resultbuf, sizeof(resultbuf), "%s%d",
|
||||||
|
tt.std_zone_name, -std_ofs / 3600);
|
||||||
|
if (try_timezone(resultbuf, &tt))
|
||||||
|
return resultbuf;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Did not find the timezone. Fallback to try a GMT zone. Note that the
|
* 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
|
* 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
|
* is west of Greenwich. It's unfortunate that this is opposite of SQL
|
||||||
* conventions. Should we therefore change the names? Probably not...
|
* conventions. Should we therefore change the names? Probably not...
|
||||||
*/
|
*/
|
||||||
sprintf(__tzbuf, "Etc/GMT%s%d",
|
snprintf(resultbuf, sizeof(resultbuf), "Etc/GMT%s%d",
|
||||||
(-tt.std_ofs > 0) ? "+" : "", -tt.std_ofs / 3600);
|
(-std_ofs > 0) ? "+" : "", -std_ofs / 3600);
|
||||||
|
|
||||||
ereport(LOG,
|
ereport(LOG,
|
||||||
(errmsg("could not recognize system timezone, defaulting to \"%s\"",
|
(errmsg("could not recognize system timezone, defaulting to \"%s\"",
|
||||||
__tzbuf),
|
resultbuf),
|
||||||
errhint("You can specify the correct timezone in postgresql.conf.")));
|
errhint("You can specify the correct timezone in postgresql.conf.")));
|
||||||
return __tzbuf;
|
return resultbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user