Add code to test for unknown timezone names (following some ideas from
Ross Reedstrom, a couple months back) and to detect timezones that are using leap-second timekeeping. The unknown-zone-name test is pretty heuristic and ugly, but it seems better than the old behavior of just switching to GMT given a bad name. Also make DecodePosixTimezone() a tad more robust.
This commit is contained in:
parent
6d8c774f55
commit
6d7ff848e5
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.75 2003/04/27 17:31:25 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/variable.c,v 1.76 2003/05/18 01:06:25 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -235,7 +235,147 @@ show_datestyle(void)
|
|||||||
/*
|
/*
|
||||||
* Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
|
* Storage for TZ env var is allocated with an arbitrary size of 64 bytes.
|
||||||
*/
|
*/
|
||||||
static char tzbuf[64];
|
#define TZBUF_LEN 64
|
||||||
|
|
||||||
|
static char tzbuf[TZBUF_LEN];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First time through, we remember the original environment TZ value, if any.
|
||||||
|
*/
|
||||||
|
static bool have_saved_tz = false;
|
||||||
|
static char orig_tzbuf[TZBUF_LEN];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convenience subroutine for assigning the value of TZ
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
set_tz(const char *tz)
|
||||||
|
{
|
||||||
|
strcpy(tzbuf, "TZ=");
|
||||||
|
strncpy(tzbuf + 3, tz, sizeof(tzbuf) - 4);
|
||||||
|
if (putenv(tzbuf) != 0) /* shouldn't happen? */
|
||||||
|
elog(LOG, "Unable to set TZ environment variable");
|
||||||
|
tzset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove any value of TZ we have established
|
||||||
|
*
|
||||||
|
* Note: this leaves us with *no* value of TZ in the environment, and
|
||||||
|
* is therefore only appropriate for reverting to that state, not for
|
||||||
|
* reverting to a state where TZ was set to something else.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
clear_tz(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* unsetenv() works fine, but is BSD, not POSIX, and is not
|
||||||
|
* available under Solaris, among others. Apparently putenv()
|
||||||
|
* called as below clears the process-specific environment
|
||||||
|
* variables. Other reasonable arguments to putenv() (e.g.
|
||||||
|
* "TZ=", "TZ", "") result in a core dump (under Linux
|
||||||
|
* anyway). - thomas 1998-01-26
|
||||||
|
*/
|
||||||
|
if (tzbuf[0] == 'T')
|
||||||
|
{
|
||||||
|
strcpy(tzbuf, "=");
|
||||||
|
if (putenv(tzbuf) != 0)
|
||||||
|
elog(LOG, "Unable to clear TZ environment variable");
|
||||||
|
tzset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check whether tzset() succeeded
|
||||||
|
*
|
||||||
|
* Unfortunately, tzset doesn't offer any well-defined way to detect that the
|
||||||
|
* value of TZ was bad. Often it will just select UTC (GMT) as the effective
|
||||||
|
* timezone. We use the following heuristics:
|
||||||
|
*
|
||||||
|
* If tzname[1] is a nonempty string, *or* the global timezone variable is
|
||||||
|
* not zero, then tzset must have recognized the TZ value as something
|
||||||
|
* different from UTC. Return true.
|
||||||
|
*
|
||||||
|
* Otherwise, check to see if the TZ name is a known spelling of "UTC"
|
||||||
|
* (ie, appears in our internal tables as a timezone equivalent to UTC).
|
||||||
|
* If so, accept it.
|
||||||
|
*
|
||||||
|
* This will reject nonstandard spellings of UTC unless tzset() chose to
|
||||||
|
* set tzname[1] as well as tzname[0]. The glibc version of tzset() will
|
||||||
|
* do so, but on other systems we may be tightening the spec a little.
|
||||||
|
*
|
||||||
|
* Another problem is that on some platforms (eg HPUX), if tzset thinks the
|
||||||
|
* input is bogus then it will adopt the system default timezone, which we
|
||||||
|
* really can't tell is not the intended translation of the input.
|
||||||
|
*
|
||||||
|
* Still, it beats failing to detect bad TZ names at all, and a silent
|
||||||
|
* failure mode of adopting the system-wide default is much better than
|
||||||
|
* a silent failure mode of adopting UTC.
|
||||||
|
*
|
||||||
|
* NB: this must NOT elog(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.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
tzset_succeeded(const char *tz)
|
||||||
|
{
|
||||||
|
char tztmp[TZBUF_LEN];
|
||||||
|
char *cp;
|
||||||
|
int tzval;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check first set of heuristics to say that tzset definitely worked.
|
||||||
|
*/
|
||||||
|
if (tzname[1] && tzname[1][0] != '\0')
|
||||||
|
return true;
|
||||||
|
if (TIMEZONE_GLOBAL != 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for known spellings of "UTC". Note we must downcase the input
|
||||||
|
* before passing it to DecodePosixTimezone().
|
||||||
|
*/
|
||||||
|
StrNCpy(tztmp, tz, sizeof(tztmp));
|
||||||
|
for (cp = tztmp; *cp; cp++)
|
||||||
|
*cp = tolower((unsigned char) *cp);
|
||||||
|
if (DecodePosixTimezone(tztmp, &tzval) == 0)
|
||||||
|
if (tzval == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 elog(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.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
tz_acceptable(void)
|
||||||
|
{
|
||||||
|
struct tm tt;
|
||||||
|
time_t time2000;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To detect leap-second timekeeping, compute the time_t value for
|
||||||
|
* local midnight, 2000-01-01. Insist that this be a multiple of 60;
|
||||||
|
* any partial-minute offset has to be due to leap seconds.
|
||||||
|
*/
|
||||||
|
MemSet(&tt, 0, sizeof(tt));
|
||||||
|
tt.tm_year = 100;
|
||||||
|
tt.tm_mon = 0;
|
||||||
|
tt.tm_mday = 1;
|
||||||
|
tt.tm_isdst = -1;
|
||||||
|
time2000 = mktime(&tt);
|
||||||
|
if ((time2000 % 60) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* assign_timezone: GUC assign_hook for timezone
|
* assign_timezone: GUC assign_hook for timezone
|
||||||
@ -247,6 +387,21 @@ assign_timezone(const char *value, bool doit, bool interactive)
|
|||||||
char *endptr;
|
char *endptr;
|
||||||
double hours;
|
double hours;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On first call, see if there is a TZ in the original environment.
|
||||||
|
* Save that value permanently.
|
||||||
|
*/
|
||||||
|
if (!have_saved_tz)
|
||||||
|
{
|
||||||
|
char *orig_tz = getenv("TZ");
|
||||||
|
|
||||||
|
if (orig_tz)
|
||||||
|
StrNCpy(orig_tzbuf, orig_tz, sizeof(orig_tzbuf));
|
||||||
|
else
|
||||||
|
orig_tzbuf[0] = '\0';
|
||||||
|
have_saved_tz = true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check for INTERVAL 'foo'
|
* Check for INTERVAL 'foo'
|
||||||
*/
|
*/
|
||||||
@ -313,25 +468,36 @@ assign_timezone(const char *value, bool doit, bool interactive)
|
|||||||
else if (strcasecmp(value, "UNKNOWN") == 0)
|
else if (strcasecmp(value, "UNKNOWN") == 0)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Clear any TZ value we may have established.
|
* UNKNOWN is the value shown as the "default" for TimeZone
|
||||||
*
|
* in guc.c. We interpret it as meaning the original TZ
|
||||||
* unsetenv() works fine, but is BSD, not POSIX, and is not
|
* inherited from the environment. Note that if there is an
|
||||||
* available under Solaris, among others. Apparently putenv()
|
* original TZ setting, we will return that rather than UNKNOWN
|
||||||
* called as below clears the process-specific environment
|
* as the canonical spelling.
|
||||||
* variables. Other reasonable arguments to putenv() (e.g.
|
|
||||||
* "TZ=", "TZ", "") result in a core dump (under Linux
|
|
||||||
* anyway). - thomas 1998-01-26
|
|
||||||
*/
|
*/
|
||||||
if (doit)
|
if (doit)
|
||||||
{
|
{
|
||||||
if (tzbuf[0] == 'T')
|
bool ok;
|
||||||
|
|
||||||
|
/* Revert to original setting of TZ, whatever it was */
|
||||||
|
if (orig_tzbuf[0])
|
||||||
{
|
{
|
||||||
strcpy(tzbuf, "=");
|
set_tz(orig_tzbuf);
|
||||||
if (putenv(tzbuf) != 0)
|
ok = tzset_succeeded(orig_tzbuf) && tz_acceptable();
|
||||||
elog(ERROR, "Unable to clear TZ environment variable");
|
|
||||||
tzset();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clear_tz();
|
||||||
|
ok = tz_acceptable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
HasCTZSet = false;
|
HasCTZSet = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Bogus, so force UTC (equivalent to INTERVAL 0) */
|
||||||
|
CTimeZone = 0;
|
||||||
|
HasCTZSet = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -339,19 +505,58 @@ assign_timezone(const char *value, bool doit, bool interactive)
|
|||||||
/*
|
/*
|
||||||
* Otherwise assume it is a timezone name.
|
* Otherwise assume it is a timezone name.
|
||||||
*
|
*
|
||||||
* XXX unfortunately we have no reasonable way to check whether a
|
* We have to actually apply the change before we can have any
|
||||||
* timezone name is good, so we have to just assume that it
|
* hope of checking it. So, save the old value in case we have
|
||||||
* is.
|
* to back out. Note that it's possible the old setting is in
|
||||||
|
* tzbuf, so we'd better copy it.
|
||||||
*/
|
*/
|
||||||
if (doit)
|
char save_tzbuf[TZBUF_LEN];
|
||||||
|
char *save_tz;
|
||||||
|
bool known,
|
||||||
|
acceptable;
|
||||||
|
|
||||||
|
save_tz = getenv("TZ");
|
||||||
|
if (save_tz)
|
||||||
|
StrNCpy(save_tzbuf, save_tz, sizeof(save_tzbuf));
|
||||||
|
|
||||||
|
set_tz(value);
|
||||||
|
|
||||||
|
known = tzset_succeeded(value);
|
||||||
|
acceptable = tz_acceptable();
|
||||||
|
|
||||||
|
if (doit && known && acceptable)
|
||||||
{
|
{
|
||||||
strcpy(tzbuf, "TZ=");
|
/* Keep the changed TZ */
|
||||||
strncat(tzbuf, value, sizeof(tzbuf) - 4);
|
|
||||||
if (putenv(tzbuf) != 0) /* shouldn't happen? */
|
|
||||||
elog(LOG, "assign_timezone: putenv failed");
|
|
||||||
tzset();
|
|
||||||
HasCTZSet = false;
|
HasCTZSet = false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Revert to prior TZ setting; note we haven't changed
|
||||||
|
* HasCTZSet in this path, so if we were previously using
|
||||||
|
* a fixed offset, we still are.
|
||||||
|
*/
|
||||||
|
if (save_tz)
|
||||||
|
set_tz(save_tzbuf);
|
||||||
|
else
|
||||||
|
clear_tz();
|
||||||
|
/* Complain if it was bad */
|
||||||
|
if (!known)
|
||||||
|
{
|
||||||
|
elog(interactive ? ERROR : LOG,
|
||||||
|
"unrecognized timezone name \"%s\"",
|
||||||
|
value);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!acceptable)
|
||||||
|
{
|
||||||
|
elog(interactive ? ERROR : LOG,
|
||||||
|
"timezone \"%s\" appears to use leap seconds"
|
||||||
|
"\n\tPostgreSQL does not support leap seconds",
|
||||||
|
value);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,10 +574,7 @@ assign_timezone(const char *value, bool doit, bool interactive)
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (HasCTZSet)
|
if (HasCTZSet)
|
||||||
{
|
snprintf(result, sizeof(tzbuf), "%.5f", (double) CTimeZone / 3600.0);
|
||||||
snprintf(result, sizeof(tzbuf), "%.5f",
|
|
||||||
(double) CTimeZone / 3600.0);
|
|
||||||
}
|
|
||||||
else if (tzbuf[0] == 'T')
|
else if (tzbuf[0] == 'T')
|
||||||
strcpy(result, tzbuf + 3);
|
strcpy(result, tzbuf + 3);
|
||||||
else
|
else
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.104 2003/05/04 04:30:15 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.105 2003/05/18 01:06:26 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -36,7 +36,6 @@ static int DecodeTime(char *str, int fmask, int *tmask,
|
|||||||
static int DecodeTimezone(char *str, int *tzp);
|
static int DecodeTimezone(char *str, int *tzp);
|
||||||
static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
|
static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
|
||||||
static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
|
static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
|
||||||
static int DecodePosixTimezone(char *str, int *val);
|
|
||||||
static void TrimTrailingZeros(char *str);
|
static void TrimTrailingZeros(char *str);
|
||||||
|
|
||||||
|
|
||||||
@ -942,8 +941,6 @@ DecodeDateTime(char **field, int *ftype, int nf,
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
val = strtol(field[i], &cp, 10);
|
val = strtol(field[i], &cp, 10);
|
||||||
if (*cp != '-')
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
|
j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
|
||||||
/* Get the time zone from the end of the string */
|
/* Get the time zone from the end of the string */
|
||||||
@ -2555,6 +2552,10 @@ DecodeNumberField(int len, char *str, int fmask,
|
|||||||
/* DecodeTimezone()
|
/* DecodeTimezone()
|
||||||
* Interpret string as a numeric timezone.
|
* Interpret string as a numeric timezone.
|
||||||
*
|
*
|
||||||
|
* Return 0 if okay (and set *tzp), nonzero if not okay.
|
||||||
|
*
|
||||||
|
* NB: this must *not* elog on failure; see commands/variable.c.
|
||||||
|
*
|
||||||
* Note: we allow timezone offsets up to 13:59. There are places that
|
* Note: we allow timezone offsets up to 13:59. There are places that
|
||||||
* use +1300 summer time.
|
* use +1300 summer time.
|
||||||
*/
|
*/
|
||||||
@ -2567,7 +2568,10 @@ DecodeTimezone(char *str, int *tzp)
|
|||||||
char *cp;
|
char *cp;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
/* assume leading character is "+" or "-" */
|
/* leading character must be "+" or "-" */
|
||||||
|
if (*str != '+' && *str != '-')
|
||||||
|
return -1;
|
||||||
|
|
||||||
hr = strtol((str + 1), &cp, 10);
|
hr = strtol((str + 1), &cp, 10);
|
||||||
|
|
||||||
/* explicit delimiter? */
|
/* explicit delimiter? */
|
||||||
@ -2589,6 +2593,7 @@ DecodeTimezone(char *str, int *tzp)
|
|||||||
min = 0;
|
min = 0;
|
||||||
|
|
||||||
tz = (hr * 60 + min) * 60;
|
tz = (hr * 60 + min) * 60;
|
||||||
|
|
||||||
if (*str == '-')
|
if (*str == '-')
|
||||||
tz = -tz;
|
tz = -tz;
|
||||||
|
|
||||||
@ -2601,9 +2606,14 @@ DecodeTimezone(char *str, int *tzp)
|
|||||||
* Interpret string as a POSIX-compatible timezone:
|
* Interpret string as a POSIX-compatible timezone:
|
||||||
* PST-hh:mm
|
* PST-hh:mm
|
||||||
* PST+h
|
* PST+h
|
||||||
|
* PST
|
||||||
* - thomas 2000-03-15
|
* - thomas 2000-03-15
|
||||||
|
*
|
||||||
|
* Return 0 if okay (and set *tzp), nonzero if not okay.
|
||||||
|
*
|
||||||
|
* NB: this must *not* elog on failure; see commands/variable.c.
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
DecodePosixTimezone(char *str, int *tzp)
|
DecodePosixTimezone(char *str, int *tzp)
|
||||||
{
|
{
|
||||||
int val,
|
int val,
|
||||||
@ -2612,13 +2622,21 @@ DecodePosixTimezone(char *str, int *tzp)
|
|||||||
char *cp;
|
char *cp;
|
||||||
char delim;
|
char delim;
|
||||||
|
|
||||||
|
/* advance over name part */
|
||||||
cp = str;
|
cp = str;
|
||||||
while ((*cp != '\0') && isalpha((unsigned char) *cp))
|
while (*cp && isalpha((unsigned char) *cp))
|
||||||
cp++;
|
cp++;
|
||||||
|
|
||||||
|
/* decode offset, if present */
|
||||||
|
if (*cp)
|
||||||
|
{
|
||||||
if (DecodeTimezone(cp, &tz) != 0)
|
if (DecodeTimezone(cp, &tz) != 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tz = 0;
|
||||||
|
|
||||||
|
/* decode name part. We must temporarily scribble on the input! */
|
||||||
delim = *cp;
|
delim = *cp;
|
||||||
*cp = '\0';
|
*cp = '\0';
|
||||||
type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val);
|
type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val);
|
||||||
@ -2641,8 +2659,12 @@ DecodePosixTimezone(char *str, int *tzp)
|
|||||||
|
|
||||||
/* DecodeSpecial()
|
/* DecodeSpecial()
|
||||||
* Decode text string using lookup table.
|
* Decode text string using lookup table.
|
||||||
|
*
|
||||||
* Implement a cache lookup since it is likely that dates
|
* Implement a cache lookup since it is likely that dates
|
||||||
* will be related in format.
|
* will be related in format.
|
||||||
|
*
|
||||||
|
* NB: this must *not* elog on failure;
|
||||||
|
* see commands/variable.c.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
DecodeSpecial(int field, char *lowtoken, int *val)
|
DecodeSpecial(int field, char *lowtoken, int *val)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $Id: datetime.h,v 1.38 2003/04/18 01:03:42 momjian Exp $
|
* $Id: datetime.h,v 1.39 2003/05/18 01:06:26 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -290,6 +290,8 @@ extern bool ClearDateCache(bool newval, bool doit, bool interactive);
|
|||||||
|
|
||||||
extern int j2day(int jd);
|
extern int j2day(int jd);
|
||||||
|
|
||||||
|
extern int DecodePosixTimezone(char *str, int *tzp);
|
||||||
|
|
||||||
extern bool CheckDateTokenTables(void);
|
extern bool CheckDateTokenTables(void);
|
||||||
|
|
||||||
#endif /* DATETIME_H */
|
#endif /* DATETIME_H */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user