Arrange for timezone names to be recognized case-insensitively; for
example SET TIME ZONE 'america/new_york' works now. This seems a good idea on general user-friendliness grounds, and is part of the solution to the timestamp-input parsing problems I noted recently.
This commit is contained in:
parent
a2ebf81913
commit
0b35b01e7a
@ -1,4 +1,4 @@
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.176 2006/09/22 16:20:00 tgl Exp $ -->
|
||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.177 2006/10/16 19:58:26 tgl Exp $ -->
|
||||
|
||||
<chapter id="datatype">
|
||||
<title id="datatype-title">Data Types</title>
|
||||
@ -1593,12 +1593,12 @@ SELECT b, char_length(b) FROM test2;
|
||||
linkend="datatype-datetime-time-table">
|
||||
and <xref linkend="datatype-timezone-table">.) If a time zone is
|
||||
specified in the input for <type>time without time zone</type>,
|
||||
it is silently ignored. You can also always specify a date but it will
|
||||
be ignored except for when you use a full time zone name like
|
||||
it is silently ignored. You can also specify a date but it will
|
||||
be ignored, except when you use a full time zone name like
|
||||
<literal>America/New_York</literal>. In this case specifying the date
|
||||
is compulsory in order to tell which time zone offset should be
|
||||
applied. It will be applied whatever time zone offset was valid at that
|
||||
date and time at the specified place.
|
||||
is required in order to determine whether standard or daylight-savings
|
||||
time applies. The appropriate time zone offset is recorded in the
|
||||
<type>time with time zone</type> value.
|
||||
</para>
|
||||
|
||||
<table id="datatype-datetime-time-table">
|
||||
@ -1653,7 +1653,7 @@ SELECT b, char_length(b) FROM test2;
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>04:05:06 PST</literal></entry>
|
||||
<entry>time zone specified by name</entry>
|
||||
<entry>time zone specified by abbreviation</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>2003-04-12 04:05:06 America/New_York</literal></entry>
|
||||
@ -2214,6 +2214,12 @@ January 8 04:05:06 1999 PST
|
||||
will always know the correct UTC offset for your region.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
In all cases, timezone names are recognized case-insensitively.
|
||||
(This is a change from <productname>PostgreSQL</productname> versions
|
||||
prior to 8.2, which were case-sensitive in some contexts and not others.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that timezone names are <emphasis>not</> used for date/time output
|
||||
— all supported output formats use numeric timezone displays to
|
||||
|
@ -3,7 +3,7 @@
|
||||
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.14 2006/06/07 22:24:46 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/timezone/localtime.c,v 1.15 2006/10/16 19:58:26 tgl Exp $
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -122,7 +122,7 @@ detzcode(const char *codep)
|
||||
}
|
||||
|
||||
int
|
||||
tzload(const char *name, struct state * sp)
|
||||
tzload(const char *name, char *canonname, struct state *sp)
|
||||
{
|
||||
const char *p;
|
||||
int i;
|
||||
@ -130,36 +130,11 @@ tzload(const char *name, struct state * sp)
|
||||
|
||||
if (name == NULL && (name = TZDEFAULT) == NULL)
|
||||
return -1;
|
||||
{
|
||||
int doaccess;
|
||||
char fullname[MAXPGPATH];
|
||||
|
||||
if (name[0] == ':')
|
||||
++name;
|
||||
doaccess = name[0] == '/';
|
||||
if (!doaccess)
|
||||
{
|
||||
p = pg_TZDIR();
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
if ((strlen(p) + strlen(name) + 1) >= sizeof fullname)
|
||||
return -1;
|
||||
(void) strcpy(fullname, p);
|
||||
(void) strcat(fullname, "/");
|
||||
(void) strcat(fullname, name);
|
||||
|
||||
/*
|
||||
* Set doaccess if '.' (as in "../") shows up in name.
|
||||
*/
|
||||
if (strchr(name, '.') != NULL)
|
||||
doaccess = TRUE;
|
||||
name = fullname;
|
||||
}
|
||||
if (doaccess && access(name, R_OK) != 0)
|
||||
return -1;
|
||||
if ((fid = open(name, O_RDONLY | PG_BINARY, 0)) == -1)
|
||||
return -1;
|
||||
}
|
||||
if (name[0] == ':')
|
||||
++name;
|
||||
fid = pg_open_tzfile(name, canonname);
|
||||
if (fid < 0)
|
||||
return -1;
|
||||
{
|
||||
struct tzhead *tzhp;
|
||||
union
|
||||
@ -587,7 +562,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
|
||||
if (name == NULL)
|
||||
return -1;
|
||||
}
|
||||
load_result = tzload(TZDEFRULES, sp);
|
||||
load_result = tzload(TZDEFRULES, NULL, sp);
|
||||
if (load_result != 0)
|
||||
sp->leapcnt = 0; /* so, we're off a little */
|
||||
if (*name != '\0')
|
||||
@ -794,7 +769,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
|
||||
static void
|
||||
gmtload(struct state * sp)
|
||||
{
|
||||
if (tzload(gmt, sp) != 0)
|
||||
if (tzload(gmt, NULL, sp) != 0)
|
||||
(void) tzparse(gmt, sp, TRUE);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.46 2006/10/04 00:30:14 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.47 2006/10/16 19:58:27 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -15,6 +15,7 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
@ -31,8 +32,11 @@ pg_tz *global_timezone = NULL;
|
||||
|
||||
|
||||
static char tzdir[MAXPGPATH];
|
||||
static int done_tzdir = 0;
|
||||
static bool done_tzdir = false;
|
||||
|
||||
static bool scan_directory_ci(const char *dirname,
|
||||
const char *fname, int fnamelen,
|
||||
char *canonname, int canonnamelen);
|
||||
static const char *identify_system_timezone(void);
|
||||
static const char *select_default_timezone(void);
|
||||
static bool set_global_timezone(const char *tzname);
|
||||
@ -41,20 +45,123 @@ static bool set_global_timezone(const char *tzname);
|
||||
/*
|
||||
* Return full pathname of timezone data directory
|
||||
*/
|
||||
char *
|
||||
static char *
|
||||
pg_TZDIR(void)
|
||||
{
|
||||
if (done_tzdir)
|
||||
return tzdir;
|
||||
|
||||
get_share_path(my_exec_path, tzdir);
|
||||
strcat(tzdir, "/timezone");
|
||||
strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir));
|
||||
|
||||
done_tzdir = 1;
|
||||
done_tzdir = true;
|
||||
return tzdir;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
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
|
||||
* (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)
|
||||
{
|
||||
/*
|
||||
* 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.
|
||||
@ -167,7 +274,7 @@ score_timezone(const char *tzname, struct tztry * tt)
|
||||
* Load timezone directly. Don't use pg_tzset, because we don't want all
|
||||
* timezones loaded in the cache at startup.
|
||||
*/
|
||||
if (tzload(tzname, &tz.state) != 0)
|
||||
if (tzload(tzname, NULL, &tz.state) != 0)
|
||||
{
|
||||
if (tzname[0] == ':' || tzparse(tzname, &tz.state, FALSE) != 0)
|
||||
{
|
||||
@ -958,10 +1065,20 @@ identify_system_timezone(void)
|
||||
|
||||
/*
|
||||
* We keep loaded timezones in a hashtable so we don't have to
|
||||
* load and parse the TZ definition file every time it is selected.
|
||||
* 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)
|
||||
{
|
||||
@ -970,7 +1087,7 @@ init_timezone_hashtable(void)
|
||||
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
|
||||
|
||||
hash_ctl.keysize = TZ_STRLEN_MAX + 1;
|
||||
hash_ctl.entrysize = sizeof(pg_tz);
|
||||
hash_ctl.entrysize = sizeof(pg_tz_cache);
|
||||
|
||||
timezone_cache = hash_create("Timezones",
|
||||
4,
|
||||
@ -989,8 +1106,11 @@ init_timezone_hashtable(void)
|
||||
struct pg_tz *
|
||||
pg_tzset(const char *name)
|
||||
{
|
||||
pg_tz *tzp;
|
||||
pg_tz tz;
|
||||
pg_tz_cache *tzp;
|
||||
struct state tzstate;
|
||||
char uppername[TZ_STRLEN_MAX + 1];
|
||||
char canonname[TZ_STRLEN_MAX + 1];
|
||||
char *p;
|
||||
|
||||
if (strlen(name) > TZ_STRLEN_MAX)
|
||||
return NULL; /* not going to fit */
|
||||
@ -999,37 +1119,49 @@ pg_tzset(const char *name)
|
||||
if (!init_timezone_hashtable())
|
||||
return NULL;
|
||||
|
||||
tzp = (pg_tz *) hash_search(timezone_cache,
|
||||
name,
|
||||
HASH_FIND,
|
||||
NULL);
|
||||
/*
|
||||
* Upcase the given name to perform a case-insensitive hashtable search.
|
||||
* (We could alternatively downcase it, but we prefer upcase so that we
|
||||
* 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;
|
||||
return &tzp->tz;
|
||||
}
|
||||
|
||||
if (tzload(name, &tz.state) != 0)
|
||||
if (tzload(uppername, canonname, &tzstate) != 0)
|
||||
{
|
||||
if (name[0] == ':' || tzparse(name, &tz.state, FALSE) != 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);
|
||||
}
|
||||
|
||||
strcpy(tz.TZname, name);
|
||||
|
||||
/* Save timezone in the cache */
|
||||
tzp = hash_search(timezone_cache,
|
||||
name,
|
||||
HASH_ENTER,
|
||||
NULL);
|
||||
tzp = (pg_tz_cache *) hash_search(timezone_cache,
|
||||
uppername,
|
||||
HASH_ENTER,
|
||||
NULL);
|
||||
|
||||
strcpy(tzp->TZname, tz.TZname);
|
||||
memcpy(&tzp->state, &tz.state, sizeof(tz.state));
|
||||
/* hash_search already copied uppername into the hash key */
|
||||
strcpy(tzp->tz.TZname, canonname);
|
||||
memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate));
|
||||
|
||||
return tzp;
|
||||
return &tzp->tz;
|
||||
}
|
||||
|
||||
|
||||
@ -1241,14 +1373,13 @@ pg_tzenumerate_next(pg_tzenum *dir)
|
||||
* Load this timezone using tzload() not pg_tzset(), so we don't fill
|
||||
* the cache
|
||||
*/
|
||||
if (tzload(fullname + dir->baselen, &dir->tz.state) != 0)
|
||||
if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state) != 0)
|
||||
{
|
||||
/* Zone could not be loaded, ignore it */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Timezone loaded OK. */
|
||||
strcpy(dir->tz.TZname, fullname + dir->baselen);
|
||||
return &dir->tz;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/timezone/pgtz.h,v 1.17 2006/07/11 13:54:25 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/timezone/pgtz.h,v 1.18 2006/10/16 19:58:27 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -19,7 +19,6 @@
|
||||
#include "tzfile.h"
|
||||
#include "pgtime.h"
|
||||
|
||||
extern char *pg_TZDIR(void);
|
||||
|
||||
#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
@ -55,11 +54,17 @@ struct state
|
||||
|
||||
struct pg_tz
|
||||
{
|
||||
/* TZname contains the canonically-cased name of the timezone */
|
||||
char TZname[TZ_STRLEN_MAX + 1];
|
||||
struct state state;
|
||||
};
|
||||
|
||||
int tzload(const char *name, struct state * sp);
|
||||
int tzparse(const char *name, struct state * sp, int lastditch);
|
||||
|
||||
/* in pgtz.c */
|
||||
extern int pg_open_tzfile(const char *name, char *canonname);
|
||||
|
||||
/* in localtime.c */
|
||||
extern int tzload(const char *name, char *canonname, struct state *sp);
|
||||
extern int tzparse(const char *name, struct state *sp, int lastditch);
|
||||
|
||||
#endif /* _PGTZ_H */
|
||||
|
@ -3,7 +3,7 @@
|
||||
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/timezone/zic.c,v 1.16 2005/10/15 02:49:51 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/timezone/zic.c,v 1.17 2006/10/16 19:58:27 tgl Exp $
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
@ -2387,11 +2387,11 @@ link(const char *oldpath, const char *newpath)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This allows zic to compile by just assigning a dummy value.
|
||||
* This allows zic to compile by just returning a dummy value.
|
||||
* localtime.c references it, but no one uses it from zic.
|
||||
*/
|
||||
char *
|
||||
pg_TZDIR(void)
|
||||
int
|
||||
pg_open_tzfile(const char *name, char *canonname)
|
||||
{
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user