From 5ff4f39c0efd45d7a85d5139fd7367fc19da82cd Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 16 Sep 2006 20:14:34 +0000 Subject: [PATCH] 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. --- doc/src/sgml/catalogs.sgml | 80 +++++++++++++++-- src/backend/catalog/system_views.sql | 11 +-- src/backend/utils/adt/datetime.c | 125 +++++++++++++++++++++++++-- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_proc.h | 6 +- src/include/pgtime.h | 7 +- src/include/utils/datetime.h | 5 +- src/test/regress/expected/rules.out | 5 +- src/timezone/pgtz.c | 119 ++++++++++++++++++++++++- 9 files changed, 331 insertions(+), 31 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 3a5ad70cc2..2201a687eb 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ - + @@ -4564,10 +4564,15 @@ - pg_timezonenames + pg_timezone_abbrevs time zone abbreviations + + pg_timezone_names + time zone names + + pg_user database users @@ -5884,22 +5889,74 @@ - - <structname>pg_timezonenames</structname> + + <structname>pg_timezone_abbrevs</structname> - - pg_timezonenames + + pg_timezone_abbrevs - The view pg_timezonenames provides a list + The view pg_timezone_abbrevs provides a list of time zone abbreviations that are currently recognized by the datetime input routines. The contents of this view change when the run-time parameter is modified. - <structname>pg_timezonenames</> Columns + <structname>pg_timezone_abbrevs</> Columns + + + + + Name + Type + Description + + + + + abbrev + text + time zone abbreviation + + + utc_offset + interval + offset from UTC (positive means east of Greenwich) + + + is_dst + boolean + true if this is a daylight-savings abbreviation + + + +
+ +
+ + + <structname>pg_timezone_names</structname> + + + pg_timezone_names + + + + The view pg_timezone_names provides a list + of time zone names that are recognized by SET TIMEZONE, + along with their associated abbreviations, UTC offsets, + and daylight-savings status. + Unlike the abbreviations shown in pg_timezone_abbrevs, 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 CURRENT_TIMESTAMP. + + + + <structname>pg_timezone_names</> Columns @@ -5913,6 +5970,11 @@ name text + time zone name + + + abbrev + text time zone abbreviation @@ -5923,7 +5985,7 @@ is_dst boolean - true if this is a daylight-savings zone + true if currently observing daylight savings diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index b0c690c596..2a65b12cff 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -3,7 +3,7 @@ * * Copyright (c) 1996-2006, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.30 2006/08/19 01:36:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.31 2006/09/16 20:14:33 tgl Exp $ */ CREATE VIEW pg_roles AS @@ -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 ab0a3b4e89..81f7e63650 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.170 2006/09/04 01:26:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.171 2006/09/16 20:14:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 125cc6cd5e..a5f917c371 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.356 2006/09/14 22:05:06 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.357 2006/09/16 20:14:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 9e83cb4108..af80192686 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.424 2006/09/14 22:05:06 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.425 2006/09/16 20:14:33 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -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 95a95fdf0b..0e02c9e24b 100644 --- a/src/include/pgtime.h +++ b/src/include/pgtime.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/include/pgtime.h,v 1.12 2006/03/05 15:58:53 momjian Exp $ + * $PostgreSQL: pgsql/src/include/pgtime.h,v 1.13 2006/09/16 20:14:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 17ae262135..56f018b6cc 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.60 2006/07/25 03:51:22 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.61 2006/09/16 20:14:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 4169214499..c11eb5b9b2 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.44 2006/07/14 14:52:27 momjian Exp $ + * $PostgreSQL: pgsql/src/timezone/pgtz.c,v 1.45 2006/09/16 20:14:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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; +}