-<!-- $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>
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">
</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>
</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
avoid ambiguity.
* 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 $
*/
/*
}
int
-tzload(const char *name, struct state * sp)
+tzload(const char *name, char *canonname, struct state *sp)
{
const char *p;
int i;
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
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')
static void
gmtload(struct state * sp)
{
- if (tzload(gmt, sp) != 0)
+ if (tzload(gmt, NULL, sp) != 0)
(void) tzparse(gmt, sp, TRUE);
}
* 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 $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <ctype.h>
+#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
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);
/*
* 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.
*
* 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)
{
/*
* 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)
{
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,
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 */
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;
}
* 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;
}