diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index d9e4ea11b3..10da8d8d7b 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -1,4 +1,4 @@ - + Data Types @@ -1593,12 +1593,12 @@ SELECT b, char_length(b) FROM test2; linkend="datatype-datetime-time-table"> and .) If a time zone is specified in the input for time without time zone, - 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 America/New_York. 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 + time with time zone value. @@ -1653,7 +1653,7 @@ SELECT b, char_length(b) FROM test2; 04:05:06 PST - time zone specified by name + time zone specified by abbreviation 2003-04-12 04:05:06 America/New_York @@ -2214,6 +2214,12 @@ January 8 04:05:06 1999 PST will always know the correct UTC offset for your region. + + In all cases, timezone names are recognized case-insensitively. + (This is a change from PostgreSQL versions + prior to 8.2, which were case-sensitive in some contexts and not others.) + + Note that timezone names are not used for date/time output — all supported output formats use numeric timezone displays to diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c index 35fa21ef87..f5c6c0db8d 100644 --- a/src/timezone/localtime.c +++ b/src/timezone/localtime.c @@ -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); } diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c index 6f9d4225b5..8e6a64d786 100644 --- a/src/timezone/pgtz.c +++ b/src/timezone/pgtz.c @@ -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 +#include #include #include @@ -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; } diff --git a/src/timezone/pgtz.h b/src/timezone/pgtz.h index b58613bb62..0e1a06f82d 100644 --- a/src/timezone/pgtz.h +++ b/src/timezone/pgtz.h @@ -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 */ diff --git a/src/timezone/zic.c b/src/timezone/zic.c index 8ef425367f..039f6f73e0 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -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; }