diff options
author | Tom Lane | 2006-09-16 20:14:34 +0000 |
---|---|---|
committer | Tom Lane | 2006-09-16 20:14:34 +0000 |
commit | 8030ad425ec694ee84f3fbdccfc80ac132667807 (patch) | |
tree | a54807ccf09c6b143a0d799d8081b6080331511a | |
parent | 0228a53ba53b7a33e66928185be23d73e1150dae (diff) |
Rename the recently-added pg_timezonenames view to pg_timezone_abbrevs,
and create a new view pg_timezone_names that provides information about
the zones known in the 'zic' database. Magnus Hagander, with some
additional work by Tom Lane.
-rw-r--r-- | doc/src/sgml/catalogs.sgml | 78 | ||||
-rw-r--r-- | src/backend/catalog/system_views.sql | 9 | ||||
-rw-r--r-- | src/backend/utils/adt/datetime.c | 123 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/catalog/pg_proc.h | 4 | ||||
-rw-r--r-- | src/include/pgtime.h | 5 | ||||
-rw-r--r-- | src/include/utils/datetime.h | 3 | ||||
-rw-r--r-- | src/test/regress/expected/rules.out | 5 | ||||
-rw-r--r-- | src/timezone/pgtz.c | 117 |
9 files changed, 323 insertions, 23 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 5f2494e6c6..4b039894f0 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -4564,11 +4564,16 @@ </row> <row> - <entry><link linkend="view-pg-timezonenames"><structname>pg_timezonenames</structname></link></entry> + <entry><link linkend="view-pg-timezone-abbrevs"><structname>pg_timezone_abbrevs</structname></link></entry> <entry>time zone abbreviations</entry> </row> <row> + <entry><link linkend="view-pg-timezone-names"><structname>pg_timezone_names</structname></link></entry> + <entry>time zone names</entry> + </row> + + <row> <entry><link linkend="view-pg-user"><structname>pg_user</structname></link></entry> <entry>database users</entry> </row> @@ -5884,22 +5889,74 @@ </sect1> - <sect1 id="view-pg-timezonenames"> - <title><structname>pg_timezonenames</structname></title> + <sect1 id="view-pg-timezone-abbrevs"> + <title><structname>pg_timezone_abbrevs</structname></title> - <indexterm zone="view-pg-timezonenames"> - <primary>pg_timezonenames</primary> + <indexterm zone="view-pg-timezone-abbrevs"> + <primary>pg_timezone_abbrevs</primary> </indexterm> <para> - The view <structname>pg_timezonenames</structname> provides a list + The view <structname>pg_timezone_abbrevs</structname> provides a list of time zone abbreviations that are currently recognized by the datetime input routines. The contents of this view change when the <xref linkend="guc-timezone-abbreviations"> run-time parameter is modified. </para> <table> - <title><structname>pg_timezonenames</> Columns</title> + <title><structname>pg_timezone_abbrevs</> Columns</title> + + <tgroup cols=3> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry><structfield>abbrev</structfield></entry> + <entry><type>text</type></entry> + <entry>time zone abbreviation</entry> + </row> + <row> + <entry><structfield>utc_offset</structfield></entry> + <entry><type>interval</type></entry> + <entry>offset from UTC (positive means east of Greenwich)</entry> + </row> + <row> + <entry><structfield>is_dst</structfield></entry> + <entry><type>boolean</type></entry> + <entry>true if this is a daylight-savings abbreviation</entry> + </row> + </tbody> + </tgroup> + </table> + + </sect1> + + <sect1 id="view-pg-timezone-names"> + <title><structname>pg_timezone_names</structname></title> + + <indexterm zone="view-pg-timezone-names"> + <primary>pg_timezone_names</primary> + </indexterm> + + <para> + The view <structname>pg_timezone_names</structname> provides a list + of time zone names that are recognized by <command>SET TIMEZONE</>, + along with their associated abbreviations, UTC offsets, + and daylight-savings status. + Unlike the abbreviations shown in <link + linkend="view-pg-timezone-abbrevs"><structname>pg_timezone_abbrevs</structname></link>, many of these names imply a set of daylight-savings transition + date rules. Therefore, the associated information changes across local DST + boundaries. The displayed information is computed based on the current + value of <function>CURRENT_TIMESTAMP</>. + </para> + + <table> + <title><structname>pg_timezone_names</> Columns</title> <tgroup cols=3> <thead> @@ -5913,6 +5970,11 @@ <row> <entry><structfield>name</structfield></entry> <entry><type>text</type></entry> + <entry>time zone name</entry> + </row> + <row> + <entry><structfield>abbrev</structfield></entry> + <entry><type>text</type></entry> <entry>time zone abbreviation</entry> </row> <row> @@ -5923,7 +5985,7 @@ <row> <entry><structfield>is_dst</structfield></entry> <entry><type>boolean</type></entry> - <entry>true if this is a daylight-savings zone</entry> + <entry>true if currently observing daylight savings</entry> </row> </tbody> </tgroup> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 27e2440bd7..c8252e840c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -186,10 +186,11 @@ CREATE RULE pg_settings_n AS GRANT SELECT, UPDATE ON pg_settings TO PUBLIC; -CREATE VIEW pg_timezonenames AS - SELECT * - FROM pg_timezonenames() AS T - (name text, utc_offset interval, is_dst boolean); +CREATE VIEW pg_timezone_abbrevs AS + SELECT * FROM pg_timezone_abbrevs(); + +CREATE VIEW pg_timezone_names AS + SELECT * FROM pg_timezone_names(); -- Statistics views diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 4183a94c90..8bc6c44e3e 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -3839,21 +3839,21 @@ InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n) /* * This set-returning function reads all the available time zone abbreviations - * and returns a set of (name, utc_offset, is_dst). + * and returns a set of (abbrev, utc_offset, is_dst). */ Datum -pg_timezonenames(PG_FUNCTION_ARGS) +pg_timezone_abbrevs(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; int *pindex; Datum result; - Interval *resInterval; HeapTuple tuple; Datum values[3]; bool nulls[3]; char buffer[TOKMAXLEN + 1]; unsigned char *p; struct pg_tm tm; + Interval *resInterval; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) @@ -3876,11 +3876,11 @@ pg_timezonenames(PG_FUNCTION_ARGS) funcctx->user_fctx = (void *) pindex; /* - * build tupdesc for result tuples. This must match the - * definition of the pg_timezonenames view in system_views.sql + * build tupdesc for result tuples. This must match this function's + * pg_proc entry! */ tupdesc = CreateTemplateTupleDesc(3, false); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "abbrev", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "utc_offset", INTERVALOID, -1, 0); @@ -3928,3 +3928,114 @@ pg_timezonenames(PG_FUNCTION_ARGS) SRF_RETURN_NEXT(funcctx, result); } + +/* + * This set-returning function reads all the available full time zones + * and returns a set of (name, abbrev, utc_offset, is_dst). + */ +Datum +pg_timezone_names(PG_FUNCTION_ARGS) +{ + MemoryContext oldcontext; + FuncCallContext *funcctx; + pg_tzenum *tzenum; + pg_tz *tz; + Datum result; + HeapTuple tuple; + Datum values[4]; + bool nulls[4]; + int tzoff; + struct pg_tm tm; + fsec_t fsec; + char *tzn; + Interval *resInterval; + struct pg_tm itm; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function + * calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* initialize timezone scanning code */ + tzenum = pg_tzenumerate_start(); + funcctx->user_fctx = (void *) tzenum; + + /* + * build tupdesc for result tuples. This must match this function's + * pg_proc entry! + */ + tupdesc = CreateTemplateTupleDesc(4, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "abbrev", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "utc_offset", + INTERVALOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_dst", + BOOLOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + tzenum = (pg_tzenum *) funcctx->user_fctx; + + /* search for another zone to display */ + for (;;) + { + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + tz = pg_tzenumerate_next(tzenum); + MemoryContextSwitchTo(oldcontext); + + if (!tz) + { + pg_tzenumerate_end(tzenum); + funcctx->user_fctx = NULL; + SRF_RETURN_DONE(funcctx); + } + + /* Convert now() to local time in this zone */ + if (timestamp2tm(GetCurrentTransactionStartTimestamp(), + &tzoff, &tm, &fsec, &tzn, tz) != 0) + continue; /* ignore if conversion fails */ + + /* Ignore zic's rather silly "Factory" time zone */ + if (tzn && strcmp(tzn, "Local time zone must be set--see zic manual page") == 0) + continue; + + /* Found a displayable zone */ + break; + } + + MemSet(nulls, 0, sizeof(nulls)); + + values[0] = DirectFunctionCall1(textin, + CStringGetDatum(pg_get_timezone_name(tz))); + + values[1] = DirectFunctionCall1(textin, + CStringGetDatum(tzn ? tzn : "")); + + MemSet(&itm, 0, sizeof(struct pg_tm)); + itm.tm_sec = -tzoff; + resInterval = (Interval *) palloc(sizeof(Interval)); + tm2interval(&itm, 0, resInterval); + values[2] = IntervalPGetDatum(resInterval); + + values[3] = BoolGetDatum(tm.tm_isdst > 0); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index ebdf494bf2..6613719fec 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200609141 +#define CATALOG_VERSION_NO 200609161 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index e8a4686664..1d40a7ffad 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3791,7 +3791,9 @@ DATA(insert OID = 2510 ( pg_prepared_statement PGNSP PGUID 12 f f t t s 0 2249 DESCR("get the prepared statements for this session"); DATA(insert OID = 2511 ( pg_cursor PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_cursor - _null_ )); DESCR("get the open cursors for this session"); -DATA(insert OID = 2599 ( pg_timezonenames PGNSP PGUID 12 f f t t s 0 2249 "" _null_ _null_ _null_ pg_timezonenames - _null_ )); +DATA(insert OID = 2599 ( pg_timezone_abbrevs PGNSP PGUID 12 f f t t s 0 2249 "" "{25,1186,16}" "{o,o,o}" "{abbrev,utc_offset,is_dst}" pg_timezone_abbrevs - _null_ )); +DESCR("get the available time zone abbreviations"); +DATA(insert OID = 2856 ( pg_timezone_names PGNSP PGUID 12 f f t t s 0 2249 "" "{25,25,1186,16}" "{o,o,o,o}" "{name,abbrev,utc_offset,is_dst}" pg_timezone_names - _null_ )); DESCR("get the available time zone names"); /* non-persistent series generator */ diff --git a/src/include/pgtime.h b/src/include/pgtime.h index 56efd1685e..0b80ec78db 100644 --- a/src/include/pgtime.h +++ b/src/include/pgtime.h @@ -38,6 +38,7 @@ struct pg_tm }; typedef struct pg_tz pg_tz; +typedef struct pg_tzenum pg_tzenum; extern struct pg_tm *pg_localtime(const pg_time_t *timep, const pg_tz *tz); extern struct pg_tm *pg_gmtime(const pg_time_t *timep); @@ -56,6 +57,10 @@ extern pg_tz *pg_tzset(const char *tzname); extern bool tz_acceptable(pg_tz *tz); extern const char *pg_get_timezone_name(pg_tz *tz); +extern pg_tzenum *pg_tzenumerate_start(void); +extern pg_tz *pg_tzenumerate_next(pg_tzenum *dir); +extern void pg_tzenumerate_end(pg_tzenum *dir); + extern pg_tz *global_timezone; /* Maximum length of a timezone name (not including trailing null) */ diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 9a0b4d53cf..07d1460896 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -303,6 +303,7 @@ extern int j2day(int jd); extern bool CheckDateTokenTables(void); extern void InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n); -extern Datum pg_timezonenames(PG_FUNCTION_ARGS); +extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS); +extern Datum pg_timezone_names(PG_FUNCTION_ARGS); #endif /* DATETIME_H */ diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2e1d491edd..72e96e6610 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1306,7 +1306,8 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem pg_statio_user_tables | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE (pg_statio_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'pg_toast'::name, 'information_schema'::name])); pg_stats | SELECT n.nspname AS schemaname, c.relname AS tablename, a.attname, s.stanullfrac AS null_frac, s.stawidth AS avg_width, s.stadistinct AS n_distinct, CASE 1 WHEN s.stakind1 THEN s.stavalues1 WHEN s.stakind2 THEN s.stavalues2 WHEN s.stakind3 THEN s.stavalues3 WHEN s.stakind4 THEN s.stavalues4 ELSE NULL::"unknown" END AS most_common_vals, CASE 1 WHEN s.stakind1 THEN s.stanumbers1 WHEN s.stakind2 THEN s.stanumbers2 WHEN s.stakind3 THEN s.stanumbers3 WHEN s.stakind4 THEN s.stanumbers4 ELSE NULL::real[] END AS most_common_freqs, CASE 2 WHEN s.stakind1 THEN s.stavalues1 WHEN s.stakind2 THEN s.stavalues2 WHEN s.stakind3 THEN s.stavalues3 WHEN s.stakind4 THEN s.stavalues4 ELSE NULL::"unknown" END AS histogram_bounds, CASE 3 WHEN s.stakind1 THEN s.stanumbers1[1] WHEN s.stakind2 THEN s.stanumbers2[1] WHEN s.stakind3 THEN s.stanumbers3[1] WHEN s.stakind4 THEN s.stanumbers4[1] ELSE NULL::real END AS correlation FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE has_table_privilege(c.oid, 'select'::text); pg_tables | SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, t.spcname AS "tablespace", c.relhasindex AS hasindexes, c.relhasrules AS hasrules, (c.reltriggers > 0) AS hastriggers FROM ((pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) WHERE (c.relkind = 'r'::"char"); - pg_timezonenames | SELECT t.name, t.utc_offset, t.is_dst FROM pg_timezonenames() t(name text, utc_offset interval, is_dst boolean); + pg_timezone_abbrevs | SELECT pg_timezone_abbrevs.abbrev, pg_timezone_abbrevs.utc_offset, pg_timezone_abbrevs.is_dst FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst); + pg_timezone_names | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst); pg_user | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow; pg_views | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char"); rtest_v1 | SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1; @@ -1323,7 +1324,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp."location", (12 * emp.salary) AS annualsal FROM emp; -(47 rows) +(48 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c index ba697fb20d..a0c6db11b7 100644 --- a/src/timezone/pgtz.c +++ b/src/timezone/pgtz.c @@ -1136,3 +1136,120 @@ pg_timezone_initialize(void) SetConfigOption("timezone", def_tz, PGC_POSTMASTER, PGC_S_ARGV); } } + + +/* + * Functions to enumerate available timezones + * + * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum + * structure, so the data is only valid up to the next call. + * + * All data is allocated using palloc in the current context. + */ +#define MAX_TZDIR_DEPTH 10 + +struct pg_tzenum { + int baselen; + int depth; + DIR *dirdesc[MAX_TZDIR_DEPTH]; + char *dirname[MAX_TZDIR_DEPTH]; + struct pg_tz tz; +}; +/* typedef pg_tzenum is declared in pgtime.h */ + +pg_tzenum * +pg_tzenumerate_start(void) +{ + pg_tzenum *ret = (pg_tzenum *) palloc0(sizeof(pg_tzenum)); + char *startdir = pstrdup(pg_TZDIR()); + + ret->baselen = strlen(startdir) + 1; + ret->depth = 0; + ret->dirname[0] = startdir; + ret->dirdesc[0] = AllocateDir(startdir); + if (!ret->dirdesc[0]) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", startdir))); + return ret; +} + +void +pg_tzenumerate_end(pg_tzenum *dir) +{ + while (dir->depth >= 0) + { + FreeDir(dir->dirdesc[dir->depth]); + pfree(dir->dirname[dir->depth]); + dir->depth--; + } + pfree(dir); +} + +pg_tz * +pg_tzenumerate_next(pg_tzenum *dir) +{ + while (dir->depth >= 0) + { + struct dirent *direntry; + char fullname[MAXPGPATH]; + struct stat statbuf; + + direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]); + + if (!direntry) + { + /* End of this directory */ + FreeDir(dir->dirdesc[dir->depth]); + pfree(dir->dirname[dir->depth]); + dir->depth--; + continue; + } + + if (direntry->d_name[0] == '.') + continue; + + snprintf(fullname, MAXPGPATH, "%s/%s", + dir->dirname[dir->depth], direntry->d_name); + if (stat(fullname, &statbuf) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat \"%s\": %m", fullname))); + + if (S_ISDIR(statbuf.st_mode)) + { + /* Step into the subdirectory */ + if (dir->depth >= MAX_TZDIR_DEPTH-1) + ereport(ERROR, + (errmsg("timezone directory stack overflow"))); + dir->depth++; + dir->dirname[dir->depth] = pstrdup(fullname); + dir->dirdesc[dir->depth] = AllocateDir(fullname); + if (!dir->dirdesc[dir->depth]) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", + fullname))); + + /* Start over reading in the new directory */ + continue; + } + + /* + * Load this timezone using tzload() not pg_tzset(), + * so we don't fill the cache + */ + if (tzload(fullname + dir->baselen, &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; + } + + /* Nothing more found */ + return NULL; +} |