Commit 0b35b01e authored by Tom Lane's avatar Tom Lane

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.
parent a2ebf819
<!-- $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"> <chapter id="datatype">
<title id="datatype-title">Data Types</title> <title id="datatype-title">Data Types</title>
...@@ -1593,12 +1593,12 @@ SELECT b, char_length(b) FROM test2; ...@@ -1593,12 +1593,12 @@ SELECT b, char_length(b) FROM test2;
linkend="datatype-datetime-time-table"> linkend="datatype-datetime-time-table">
and <xref linkend="datatype-timezone-table">.) If a time zone is and <xref linkend="datatype-timezone-table">.) If a time zone is
specified in the input for <type>time without time zone</type>, 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 it is silently ignored. You can also specify a date but it will
be ignored except for when you use a full time zone name like be ignored, except when you use a full time zone name like
<literal>America/New_York</literal>. In this case specifying the date <literal>America/New_York</literal>. In this case specifying the date
is compulsory in order to tell which time zone offset should be is required in order to determine whether standard or daylight-savings
applied. It will be applied whatever time zone offset was valid at that time applies. The appropriate time zone offset is recorded in the
date and time at the specified place. <type>time with time zone</type> value.
</para> </para>
<table id="datatype-datetime-time-table"> <table id="datatype-datetime-time-table">
...@@ -1653,7 +1653,7 @@ SELECT b, char_length(b) FROM test2; ...@@ -1653,7 +1653,7 @@ SELECT b, char_length(b) FROM test2;
</row> </row>
<row> <row>
<entry><literal>04:05:06 PST</literal></entry> <entry><literal>04:05:06 PST</literal></entry>
<entry>time zone specified by name</entry> <entry>time zone specified by abbreviation</entry>
</row> </row>
<row> <row>
<entry><literal>2003-04-12 04:05:06 America/New_York</literal></entry> <entry><literal>2003-04-12 04:05:06 America/New_York</literal></entry>
...@@ -2214,6 +2214,12 @@ January 8 04:05:06 1999 PST ...@@ -2214,6 +2214,12 @@ January 8 04:05:06 1999 PST
will always know the correct UTC offset for your region. will always know the correct UTC offset for your region.
</para> </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> <para>
Note that timezone names are <emphasis>not</> used for date/time output Note that timezone names are <emphasis>not</> used for date/time output
&mdash; all supported output formats use numeric timezone displays to &mdash; all supported output formats use numeric timezone displays to
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). * 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
* *
* IDENTIFICATION * 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) ...@@ -122,7 +122,7 @@ detzcode(const char *codep)
} }
int int
tzload(const char *name, struct state * sp) tzload(const char *name, char *canonname, struct state *sp)
{ {
const char *p; const char *p;
int i; int i;
...@@ -130,36 +130,11 @@ tzload(const char *name, struct state * sp) ...@@ -130,36 +130,11 @@ tzload(const char *name, struct state * sp)
if (name == NULL && (name = TZDEFAULT) == NULL) if (name == NULL && (name = TZDEFAULT) == NULL)
return -1; return -1;
{ if (name[0] == ':')
int doaccess; ++name;
char fullname[MAXPGPATH]; fid = pg_open_tzfile(name, canonname);
if (fid < 0)
if (name[0] == ':') return -1;
++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;
}
{ {
struct tzhead *tzhp; struct tzhead *tzhp;
union union
...@@ -587,7 +562,7 @@ tzparse(const char *name, struct state * sp, int lastditch) ...@@ -587,7 +562,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
if (name == NULL) if (name == NULL)
return -1; return -1;
} }
load_result = tzload(TZDEFRULES, sp); load_result = tzload(TZDEFRULES, NULL, sp);
if (load_result != 0) if (load_result != 0)
sp->leapcnt = 0; /* so, we're off a little */ sp->leapcnt = 0; /* so, we're off a little */
if (*name != '\0') if (*name != '\0')
...@@ -794,7 +769,7 @@ tzparse(const char *name, struct state * sp, int lastditch) ...@@ -794,7 +769,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
static void static void
gmtload(struct state * sp) gmtload(struct state * sp)
{ {
if (tzload(gmt, sp) != 0) if (tzload(gmt, NULL, sp) != 0)
(void) tzparse(gmt, sp, TRUE); (void) tzparse(gmt, sp, TRUE);
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* *
* IDENTIFICATION * 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 @@ ...@@ -15,6 +15,7 @@
#include "postgres.h" #include "postgres.h"
#include <ctype.h> #include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h> #include <time.h>
...@@ -31,8 +32,11 @@ pg_tz *global_timezone = NULL; ...@@ -31,8 +32,11 @@ pg_tz *global_timezone = NULL;
static char tzdir[MAXPGPATH]; 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 *identify_system_timezone(void);
static const char *select_default_timezone(void); static const char *select_default_timezone(void);
static bool set_global_timezone(const char *tzname); static bool set_global_timezone(const char *tzname);
...@@ -41,20 +45,123 @@ 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 * Return full pathname of timezone data directory
*/ */
char * static char *
pg_TZDIR(void) pg_TZDIR(void)
{ {
if (done_tzdir) if (done_tzdir)
return tzdir; return tzdir;
get_share_path(my_exec_path, 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; 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 * The following block of code attempts to determine which timezone in our
* timezone database is the best match for the active system timezone. * timezone database is the best match for the active system timezone.
...@@ -167,7 +274,7 @@ score_timezone(const char *tzname, struct tztry * tt) ...@@ -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 * Load timezone directly. Don't use pg_tzset, because we don't want all
* timezones loaded in the cache at startup. * 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) if (tzname[0] == ':' || tzparse(tzname, &tz.state, FALSE) != 0)
{ {
...@@ -958,10 +1065,20 @@ identify_system_timezone(void) ...@@ -958,10 +1065,20 @@ identify_system_timezone(void)
/* /*
* We keep loaded timezones in a hashtable so we don't have to * 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 HTAB *timezone_cache = NULL;
static bool static bool
init_timezone_hashtable(void) init_timezone_hashtable(void)
{ {
...@@ -970,7 +1087,7 @@ init_timezone_hashtable(void) ...@@ -970,7 +1087,7 @@ init_timezone_hashtable(void)
MemSet(&hash_ctl, 0, sizeof(hash_ctl)); MemSet(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = TZ_STRLEN_MAX + 1; 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", timezone_cache = hash_create("Timezones",
4, 4,
...@@ -989,8 +1106,11 @@ init_timezone_hashtable(void) ...@@ -989,8 +1106,11 @@ init_timezone_hashtable(void)
struct pg_tz * struct pg_tz *
pg_tzset(const char *name) pg_tzset(const char *name)
{ {
pg_tz *tzp; pg_tz_cache *tzp;
pg_tz tz; struct state tzstate;
char uppername[TZ_STRLEN_MAX + 1];
char canonname[TZ_STRLEN_MAX + 1];
char *p;
if (strlen(name) > TZ_STRLEN_MAX) if (strlen(name) > TZ_STRLEN_MAX)
return NULL; /* not going to fit */ return NULL; /* not going to fit */
...@@ -999,37 +1119,49 @@ pg_tzset(const char *name) ...@@ -999,37 +1119,49 @@ pg_tzset(const char *name)
if (!init_timezone_hashtable()) if (!init_timezone_hashtable())
return NULL; return NULL;
tzp = (pg_tz *) hash_search(timezone_cache, /*
name, * Upcase the given name to perform a case-insensitive hashtable search.
HASH_FIND, * (We could alternatively downcase it, but we prefer upcase so that we
NULL); * 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) if (tzp)
{ {
/* Timezone found in cache, nothing more to do */ /* 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! */ /* Unknown timezone. Fail our call instead of loading GMT! */
return NULL; return NULL;
} }
/* For POSIX timezone specs, use uppercase name as canonical */
strcpy(canonname, uppername);
} }
strcpy(tz.TZname, name);
/* Save timezone in the cache */ /* Save timezone in the cache */
tzp = hash_search(timezone_cache, tzp = (pg_tz_cache *) hash_search(timezone_cache,
name, uppername,
HASH_ENTER, HASH_ENTER,
NULL); NULL);
strcpy(tzp->TZname, tz.TZname); /* hash_search already copied uppername into the hash key */
memcpy(&tzp->state, &tz.state, sizeof(tz.state)); 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) ...@@ -1241,14 +1373,13 @@ pg_tzenumerate_next(pg_tzenum *dir)
* Load this timezone using tzload() not pg_tzset(), so we don't fill * Load this timezone using tzload() not pg_tzset(), so we don't fill
* the cache * 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 */ /* Zone could not be loaded, ignore it */
continue; continue;
} }
/* Timezone loaded OK. */ /* Timezone loaded OK. */
strcpy(dir->tz.TZname, fullname + dir->baselen);
return &dir->tz; return &dir->tz;
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* *
* IDENTIFICATION * 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 @@ ...@@ -19,7 +19,6 @@
#include "tzfile.h" #include "tzfile.h"
#include "pgtime.h" #include "pgtime.h"
extern char *pg_TZDIR(void);
#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) #define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
...@@ -55,11 +54,17 @@ struct state ...@@ -55,11 +54,17 @@ struct state
struct pg_tz struct pg_tz
{ {
/* TZname contains the canonically-cased name of the timezone */
char TZname[TZ_STRLEN_MAX + 1]; char TZname[TZ_STRLEN_MAX + 1];
struct state state; 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 */ #endif /* _PGTZ_H */
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). * 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov).
* *
* IDENTIFICATION * 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" #include "postgres.h"
...@@ -2387,11 +2387,11 @@ link(const char *oldpath, const char *newpath) ...@@ -2387,11 +2387,11 @@ link(const char *oldpath, const char *newpath)
#endif #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. * localtime.c references it, but no one uses it from zic.
*/ */
char * int
pg_TZDIR(void) pg_open_tzfile(const char *name, char *canonname)
{ {
return NULL; return -1;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment