</term>
<listitem>
<para>
- Sets the collection of time zone abbreviations that will be accepted
- by the server for datetime input. The default is <literal>'Default'</literal>,
+ Sets the collection of additional time zone abbreviations that
+ will be accepted by the server for datetime input (beyond any
+ abbreviations defined by the current <varname>TimeZone</varname>
+ setting). The default is <literal>'Default'</literal>,
which is a collection that works in most of the world; there are
also <literal>'Australia'</literal> and <literal>'India'</literal>,
and other collections can be defined for a particular installation.
abbreviation if one is in common use in the current zone. Otherwise
it appears as a signed numeric offset in ISO 8601 basic format
(<replaceable>hh</replaceable> or <replaceable>hhmm</replaceable>).
+ The alphabetic abbreviations shown in these styles are taken from the
+ IANA time zone database entry currently selected by the
+ <xref linkend="guc-timezone"/> run-time parameter; they are not
+ affected by the <xref linkend="guc-timezone-abbreviations"/> setting.
</para>
<para>
<step>
<para>
See if the token matches any known time zone abbreviation.
- These abbreviations are supplied by the configuration file
+ These abbreviations are determined by the configuration settings
described in <xref linkend="datetime-config-files"/>.
</para>
</step>
<para>
Since timezone abbreviations are not well standardized,
<productname>PostgreSQL</productname> provides a means to customize
- the set of abbreviations accepted by the server. The
- <xref linkend="guc-timezone-abbreviations"/> run-time parameter
- determines the active set of abbreviations. While this parameter
+ the set of abbreviations accepted in datetime input.
+ There are two sources for these abbreviations:
+
+ <orderedlist>
+ <listitem>
+ <para>
+ The <xref linkend="guc-timezone"/> run-time parameter is usually
+ set to the name of an entry in the IANA time zone database.
+ If that zone has widely-used zone abbreviations, they will appear
+ in the IANA data, and <productname>PostgreSQL</productname> will
+ preferentially recognize those abbreviations with the meanings
+ given in the IANA data.
+ For example, if <varname>timezone</varname> is set
+ to <literal>America/New_York</literal> then <literal>EST</literal>
+ will be understood as UTC-5 and <literal>EDT</literal> will be
+ understood as UTC-4. (These IANA abbreviations will also be used
+ in datetime output, if <xref linkend="guc-datestyle"/> is set to a
+ style that prefers non-numeric zone abbreviations.)
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ If an abbreviation is not found in the current IANA time zone,
+ it is sought in the list specified by the
+ <xref linkend="guc-timezone-abbreviations"/> run-time parameter.
+ The <varname>timezone_abbreviations</varname> list is primarily
+ useful for allowing datetime input to recognize abbreviations for
+ time zones other than the current zone. (These abbreviations will
+ not be used in datetime output.)
+ </para>
+ </listitem>
+ </orderedlist>
+ </para>
+
+ <para>
+ While the <varname>timezone_abbreviations</varname> parameter
can be altered by any database user, the possible values for it
are under the control of the database administrator — they
are in fact names of configuration files stored in
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.
+ <xref linkend="guc-timezone"/> or
+ <xref linkend="guc-timezone-abbreviations"/> run-time parameters are
+ modified.
</para>
<table>
REVOKE EXECUTE ON FUNCTION pg_ident_file_mappings() FROM PUBLIC;
CREATE VIEW pg_timezone_abbrevs AS
- SELECT * FROM pg_timezone_abbrevs();
+ SELECT * FROM pg_timezone_abbrevs_zone() z
+ UNION ALL
+ (SELECT * FROM pg_timezone_abbrevs_abbrevs() a
+ WHERE NOT EXISTS (SELECT 1 FROM pg_timezone_abbrevs_zone() z2
+ WHERE z2.abbrev = a.abbrev))
+ ORDER BY abbrev;
CREATE VIEW pg_timezone_names AS
SELECT * FROM pg_timezone_names();
assign_timezone(const char *newval, void *extra)
{
session_timezone = *((pg_tz **) extra);
+ /* datetime.c's cache of timezone abbrevs may now be obsolete */
+ ClearTimeZoneAbbrevCache();
}
/*
static const datetkn *deltacache[MAXDATEFIELDS] = {NULL};
-static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL};
+/* Cache for results of timezone abbreviation lookups */
+
+typedef struct TzAbbrevCache
+{
+ char abbrev[TOKMAXLEN + 1]; /* always NUL-terminated */
+ char ftype; /* TZ, DTZ, or DYNTZ */
+ int offset; /* GMT offset, if fixed-offset */
+ pg_tz *tz; /* relevant zone, if variable-offset */
+} TzAbbrevCache;
+
+static TzAbbrevCache tzabbrevcache[MAXDATEFIELDS];
/*
}
+/* TimeZoneAbbrevIsKnown()
+ *
+ * Detect whether the given string is a time zone abbreviation that's known
+ * in the specified TZDB timezone, and if so whether it's fixed or varying
+ * meaning. The match is not case-sensitive.
+ */
+static bool
+TimeZoneAbbrevIsKnown(const char *abbr, pg_tz *tzp,
+ bool *isfixed, int *offset, int *isdst)
+{
+ char upabbr[TZ_STRLEN_MAX + 1];
+ unsigned char *p;
+ long int gmtoff;
+
+ /* We need to force the abbrev to upper case */
+ strlcpy(upabbr, abbr, sizeof(upabbr));
+ for (p = (unsigned char *) upabbr; *p; p++)
+ *p = pg_toupper(*p);
+
+ /* Look up the abbrev's meaning in this zone */
+ if (pg_timezone_abbrev_is_known(upabbr,
+ isfixed,
+ &gmtoff,
+ isdst,
+ tzp))
+ {
+ /* Change sign to agree with DetermineTimeZoneOffset() */
+ *offset = (int) -gmtoff;
+ return true;
+ }
+ return false;
+}
+
+
/* DecodeTimeOnly()
* Interpret parsed string as time fields only.
* Returns 0 if successful, DTERR code if bogus input detected.
int *ftype, int *offset, pg_tz **tz,
DateTimeErrorExtra *extra)
{
+ TzAbbrevCache *tzc = &tzabbrevcache[field];
+ bool isfixed;
+ int isdst;
const datetkn *tp;
- tp = abbrevcache[field];
- /* use strncmp so that we match truncated tokens */
- if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
+ /*
+ * Do we have a cached result? Use strncmp so that we match truncated
+ * names, although we shouldn't really see that happen with normal
+ * abbreviations.
+ */
+ if (strncmp(lowtoken, tzc->abbrev, TOKMAXLEN) == 0)
{
- if (zoneabbrevtbl)
- tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
- zoneabbrevtbl->numabbrevs);
- else
- tp = NULL;
+ *ftype = tzc->ftype;
+ *offset = tzc->offset;
+ *tz = tzc->tz;
+ return 0;
+ }
+
+ /*
+ * See if the current session_timezone recognizes it. Checking this
+ * before zoneabbrevtbl allows us to correctly handle abbreviations whose
+ * meaning varies across zones, such as "LMT".
+ */
+ if (session_timezone &&
+ TimeZoneAbbrevIsKnown(lowtoken, session_timezone,
+ &isfixed, offset, &isdst))
+ {
+ *ftype = (isfixed ? (isdst ? DTZ : TZ) : DYNTZ);
+ *tz = (isfixed ? NULL : session_timezone);
+ /* flip sign to agree with the convention used in zoneabbrevtbl */
+ *offset = -(*offset);
+ /* cache result; use strlcpy to truncate name if necessary */
+ strlcpy(tzc->abbrev, lowtoken, TOKMAXLEN + 1);
+ tzc->ftype = *ftype;
+ tzc->offset = *offset;
+ tzc->tz = *tz;
+ return 0;
}
+
+ /* Nope, so look in zoneabbrevtbl */
+ if (zoneabbrevtbl)
+ tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
+ zoneabbrevtbl->numabbrevs);
+ else
+ tp = NULL;
if (tp == NULL)
{
*ftype = UNKNOWN_FIELD;
*offset = 0;
*tz = NULL;
+ /* failure results are not cached */
}
else
{
- abbrevcache[field] = tp;
*ftype = tp->type;
if (tp->type == DYNTZ)
{
*offset = tp->value;
*tz = NULL;
}
+
+ /* cache result; use strlcpy to truncate name if necessary */
+ strlcpy(tzc->abbrev, lowtoken, TOKMAXLEN + 1);
+ tzc->ftype = *ftype;
+ tzc->offset = *offset;
+ tzc->tz = *tz;
}
return 0;
}
+/*
+ * Reset tzabbrevcache after a change in session_timezone.
+ */
+void
+ClearTimeZoneAbbrevCache(void)
+{
+ memset(tzabbrevcache, 0, sizeof(tzabbrevcache));
+}
+
/* DecodeSpecial()
* Decode text string using lookup table.
*offset = 0; /* avoid uninitialized vars on failure */
*tz = NULL;
- if (!zoneabbrevtbl)
- return -1; /* no abbrevs known, so fail immediately */
-
/* Downcase as much of the string as we could need */
for (len = 0; len < TOKMAXLEN; len++)
{
*/
while (len > 0)
{
- const datetkn *tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
- zoneabbrevtbl->numabbrevs);
+ bool isfixed;
+ int isdst;
+ const datetkn *tp;
+
+ /* See if the current session_timezone recognizes it. */
+ if (session_timezone &&
+ TimeZoneAbbrevIsKnown(lowtoken, session_timezone,
+ &isfixed, offset, &isdst))
+ {
+ if (isfixed)
+ {
+ /* flip sign to agree with the convention in zoneabbrevtbl */
+ *offset = -(*offset);
+ }
+ else
+ {
+ /* Caller must resolve the abbrev's current meaning */
+ *tz = session_timezone;
+ }
+ return len;
+ }
+ /* Known in zoneabbrevtbl? */
+ if (zoneabbrevtbl)
+ tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
+ zoneabbrevtbl->numabbrevs);
+ else
+ tp = NULL;
if (tp != NULL)
{
if (tp->type == DYNTZ)
return len;
}
}
+
+ /* Nope, try the next shorter string. */
lowtoken[--len] = '\0';
}
InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
{
zoneabbrevtbl = tbl;
- /* reset abbrevcache, which may contain pointers into old table */
- memset(abbrevcache, 0, sizeof(abbrevcache));
+ /* reset tzabbrevcache, which may contain results from old table */
+ memset(tzabbrevcache, 0, sizeof(tzabbrevcache));
}
/*
/*
- * This set-returning function reads all the available time zone abbreviations
+ * This set-returning function reads all the time zone abbreviations
+ * defined by the IANA data for the current timezone setting,
+ * and returns a set of (abbrev, utc_offset, is_dst).
+ */
+Datum
+pg_timezone_abbrevs_zone(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ int *pindex;
+ Datum result;
+ HeapTuple tuple;
+ Datum values[3];
+ bool nulls[3] = {0};
+ TimestampTz now = GetCurrentTransactionStartTimestamp();
+ pg_time_t t = timestamptz_to_time_t(now);
+ const char *abbrev;
+ long int gmtoff;
+ int isdst;
+ struct pg_itm_in itm_in;
+ Interval *resInterval;
+
+ /* stuff done only on the first call of the function */
+ if (SRF_IS_FIRSTCALL())
+ {
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+
+ /* 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);
+
+ /* allocate memory for user context */
+ pindex = (int *) palloc(sizeof(int));
+ *pindex = 0;
+ funcctx->user_fctx = pindex;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ funcctx->tuple_desc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /* stuff done on every call of the function */
+ funcctx = SRF_PERCALL_SETUP();
+ pindex = (int *) funcctx->user_fctx;
+
+ while ((abbrev = pg_get_next_timezone_abbrev(pindex,
+ session_timezone)) != NULL)
+ {
+ /* Ignore abbreviations that aren't all-alphabetic */
+ if (strspn(abbrev, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != strlen(abbrev))
+ continue;
+
+ /* Determine the current meaning of the abbrev */
+ if (!pg_interpret_timezone_abbrev(abbrev,
+ &t,
+ &gmtoff,
+ &isdst,
+ session_timezone))
+ continue; /* hm, not actually used in this zone? */
+
+ values[0] = CStringGetTextDatum(abbrev);
+
+ /* Convert offset (in seconds) to an interval; can't overflow */
+ MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+ itm_in.tm_usec = (int64) gmtoff * USECS_PER_SEC;
+ resInterval = (Interval *) palloc(sizeof(Interval));
+ (void) itmin2interval(&itm_in, resInterval);
+ values[1] = IntervalPGetDatum(resInterval);
+
+ values[2] = BoolGetDatum(isdst);
+
+ tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ SRF_RETURN_NEXT(funcctx, result);
+ }
+
+ SRF_RETURN_DONE(funcctx);
+}
+
+/*
+ * This set-returning function reads all the time zone abbreviations
+ * defined by the timezone_abbreviations setting,
* and returns a set of (abbrev, utc_offset, is_dst).
*/
Datum
-pg_timezone_abbrevs(PG_FUNCTION_ARGS)
+pg_timezone_abbrevs_abbrevs(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
int *pindex;
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202501161
+#define CATALOG_VERSION_NO 202501162
#endif
proargmodes => '{o,o,o,o,o,o}',
proargnames => '{name,statement,is_holdable,is_binary,is_scrollable,creation_time}',
prosrc => 'pg_cursor' },
-{ oid => '2599', descr => 'get the available time zone abbreviations',
- proname => 'pg_timezone_abbrevs', prorows => '1000', proretset => 't',
+{ oid => '9221', descr => 'get abbreviations from current timezone',
+ proname => 'pg_timezone_abbrevs_zone', prorows => '10', proretset => 't',
provolatile => 's', prorettype => 'record', proargtypes => '',
proallargtypes => '{text,interval,bool}', proargmodes => '{o,o,o}',
proargnames => '{abbrev,utc_offset,is_dst}',
- prosrc => 'pg_timezone_abbrevs' },
+ prosrc => 'pg_timezone_abbrevs_zone' },
+{ oid => '2599', descr => 'get abbreviations from timezone_abbreviations',
+ proname => 'pg_timezone_abbrevs_abbrevs', prorows => '1000', proretset => 't',
+ provolatile => 's', prorettype => 'record', proargtypes => '',
+ proallargtypes => '{text,interval,bool}', proargmodes => '{o,o,o}',
+ proargnames => '{abbrev,utc_offset,is_dst}',
+ prosrc => 'pg_timezone_abbrevs_abbrevs' },
{ oid => '2856', descr => 'get the available time zone names',
proname => 'pg_timezone_names', prorows => '1000', proretset => 't',
provolatile => 's', prorettype => 'record', proargtypes => '',
long int *gmtoff,
int *isdst,
const pg_tz *tz);
+extern bool pg_timezone_abbrev_is_known(const char *abbrev,
+ bool *isfixed,
+ long int *gmtoff,
+ int *isdst,
+ const pg_tz *tz);
+extern const char *pg_get_next_timezone_abbrev(int *indx,
+ const pg_tz *tz);
extern bool pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff);
extern const char *pg_get_timezone_name(pg_tz *tz);
extern bool pg_tz_acceptable(pg_tz *tz);
extern int DecodeTimezoneAbbrevPrefix(const char *str,
int *offset, pg_tz **tz);
+extern void ClearTimeZoneAbbrevCache(void);
+
extern int j2day(int date);
extern struct Node *TemporalSimplify(int32 max_precis, struct Node *node);
Sat Dec 17 23:38:00 2011 PST
(1 row)
+SELECT to_timestamp('2011-12-18 00:00 LMT', 'YYYY-MM-DD HH24:MI TZ'); -- dyntz
+ to_timestamp
+------------------------------
+ Sat Dec 17 23:52:58 2011 PST
+(1 row)
+
SELECT to_timestamp('2011-12-18 11:38ESTFOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
to_timestamp
------------------------------
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
WHERE (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char"]));
-pg_timezone_abbrevs| SELECT abbrev,
- utc_offset,
- is_dst
- FROM pg_timezone_abbrevs() pg_timezone_abbrevs(abbrev, utc_offset, is_dst);
+pg_timezone_abbrevs| SELECT z.abbrev,
+ z.utc_offset,
+ z.is_dst
+ FROM pg_timezone_abbrevs_zone() z(abbrev, utc_offset, is_dst)
+UNION ALL
+ SELECT a.abbrev,
+ a.utc_offset,
+ a.is_dst
+ FROM pg_timezone_abbrevs_abbrevs() a(abbrev, utc_offset, is_dst)
+ WHERE (NOT (EXISTS ( SELECT 1
+ FROM pg_timezone_abbrevs_zone() z2(abbrev, utc_offset, is_dst)
+ WHERE (z2.abbrev = a.abbrev))))
+ ORDER BY 1;
pg_timezone_names| SELECT name,
abbrev,
utc_offset,
t
(1 row)
+-- One specific case we can check without much fear of breakage
+-- is the historical local-mean-time value used for America/Los_Angeles.
+select * from pg_timezone_abbrevs where abbrev = 'LMT';
+ abbrev | utc_offset | is_dst
+--------+-------------------------------+--------
+ LMT | @ 7 hours 52 mins 58 secs ago | f
+(1 row)
+
Fri Jan 10 07:32:01 205000 PST
(1 row)
+-- Recognize "LMT" as whatever it means in the current zone
+SELECT 'Jan 01 00:00:00 1000 LMT'::timestamptz;
+ timestamptz
+------------------------------
+ Wed Jan 01 00:00:00 1000 LMT
+(1 row)
+
+SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz;
+ timestamptz
+------------------------------
+ Sun Dec 31 23:52:58 2023 PST
+(1 row)
+
+SET timezone = 'Europe/London';
+SELECT 'Jan 01 00:00:00 1000 LMT'::timestamptz;
+ timestamptz
+------------------------------
+ Wed Jan 01 00:00:00 1000 LMT
+(1 row)
+
+SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz;
+ timestamptz
+------------------------------
+ Mon Jan 01 00:01:15 2024 GMT
+(1 row)
+
+-- which might be nothing
+SET timezone = 'UTC';
+SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz; -- fail
+ERROR: invalid input syntax for type timestamp with time zone: "Jan 01 00:00:00 2024 LMT"
+LINE 1: SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz;
+ ^
+-- Another example of an abbrev that varies across zones
+SELECT '1912-01-01 00:00 MMT'::timestamptz; -- from timezone_abbreviations
+ timestamptz
+------------------------------
+ Sun Dec 31 17:30:00 1911 UTC
+(1 row)
+
+SET timezone = 'America/Montevideo';
+SELECT '1912-01-01 00:00'::timestamptz;
+ timestamptz
+------------------------------
+ Mon Jan 01 00:00:00 1912 MMT
+(1 row)
+
+SELECT '1912-01-01 00:00 MMT'::timestamptz;
+ timestamptz
+------------------------------
+ Mon Jan 01 00:00:00 1912 MMT
+(1 row)
+
+SELECT '1912-01-01 00:00 MMT'::timestamptz AT TIME ZONE 'UTC';
+ timezone
+--------------------------
+ Mon Jan 01 03:44:51 1912
+(1 row)
+
+RESET timezone;
-- Test non-error-throwing API
SELECT pg_input_is_valid('now', 'timestamptz');
pg_input_is_valid
SELECT to_timestamp('2011-12-18 11:38 -05', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 +01:30', 'YYYY-MM-DD HH12:MI TZ');
SELECT to_timestamp('2011-12-18 11:38 MSK', 'YYYY-MM-DD HH12:MI TZ'); -- dyntz
+SELECT to_timestamp('2011-12-18 00:00 LMT', 'YYYY-MM-DD HH24:MI TZ'); -- dyntz
SELECT to_timestamp('2011-12-18 11:38ESTFOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
SELECT to_timestamp('2011-12-18 11:38-05FOO24', 'YYYY-MM-DD HH12:MITZFOOSS');
SELECT to_timestamp('2011-12-18 11:38 JUNK', 'YYYY-MM-DD HH12:MI TZ'); -- error
select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
set timezone_abbreviations = 'India';
select count(distinct utc_offset) >= 24 as ok from pg_timezone_abbrevs;
+-- One specific case we can check without much fear of breakage
+-- is the historical local-mean-time value used for America/Los_Angeles.
+select * from pg_timezone_abbrevs where abbrev = 'LMT';
SELECT '205000-07-10 17:32:01 Europe/Helsinki'::timestamptz; -- DST
SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST
+-- Recognize "LMT" as whatever it means in the current zone
+SELECT 'Jan 01 00:00:00 1000 LMT'::timestamptz;
+SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz;
+SET timezone = 'Europe/London';
+SELECT 'Jan 01 00:00:00 1000 LMT'::timestamptz;
+SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz;
+-- which might be nothing
+SET timezone = 'UTC';
+SELECT 'Jan 01 00:00:00 2024 LMT'::timestamptz; -- fail
+-- Another example of an abbrev that varies across zones
+SELECT '1912-01-01 00:00 MMT'::timestamptz; -- from timezone_abbreviations
+SET timezone = 'America/Montevideo';
+SELECT '1912-01-01 00:00'::timestamptz;
+SELECT '1912-01-01 00:00 MMT'::timestamptz;
+SELECT '1912-01-01 00:00 MMT'::timestamptz AT TIME ZONE 'UTC';
+RESET timezone;
+
-- Test non-error-throwing API
SELECT pg_input_is_valid('now', 'timestamptz');
SELECT pg_input_is_valid('garbage', 'timestamptz');
return false; /* hm, not actually used in any interval? */
}
+/*
+ * Detect whether a timezone abbreviation is defined within the given zone.
+ *
+ * This is similar to pg_interpret_timezone_abbrev() but is not concerned
+ * with a specific point in time. We want to know if the abbreviation is
+ * known at all, and if so whether it has one meaning or several.
+ *
+ * Returns true if the abbreviation is known, false if not.
+ * If the abbreviation is known and has a single meaning (only one value
+ * of gmtoff/isdst), sets *isfixed = true and sets *gmtoff and *isdst.
+ * If there are multiple meanings, sets *isfixed = false.
+ *
+ * Note: abbrev is matched case-sensitively; it should be all-upper-case.
+ */
+bool
+pg_timezone_abbrev_is_known(const char *abbrev,
+ bool *isfixed,
+ long int *gmtoff,
+ int *isdst,
+ const pg_tz *tz)
+{
+ bool result = false;
+ const struct state *sp = &tz->state;
+ const char *abbrs;
+ int abbrind;
+
+ /*
+ * Locate the abbreviation in the zone's abbreviation list. We assume
+ * there are not duplicates in the list.
+ */
+ abbrs = sp->chars;
+ abbrind = 0;
+ while (abbrind < sp->charcnt)
+ {
+ if (strcmp(abbrev, abbrs + abbrind) == 0)
+ break;
+ while (abbrs[abbrind] != '\0')
+ abbrind++;
+ abbrind++;
+ }
+ if (abbrind >= sp->charcnt)
+ return false; /* definitely not there */
+
+ /*
+ * Scan the ttinfo array to find uses of the abbreviation.
+ */
+ for (int i = 0; i < sp->typecnt; i++)
+ {
+ const struct ttinfo *ttisp = &sp->ttis[i];
+
+ if (ttisp->tt_desigidx == abbrind)
+ {
+ if (!result)
+ {
+ /* First usage */
+ *isfixed = true; /* for the moment */
+ *gmtoff = ttisp->tt_utoff;
+ *isdst = ttisp->tt_isdst;
+ result = true;
+ }
+ else
+ {
+ /* Second or later usage, does it match? */
+ if (*gmtoff != ttisp->tt_utoff ||
+ *isdst != ttisp->tt_isdst)
+ {
+ *isfixed = false;
+ break; /* no point in looking further */
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Iteratively fetch all the abbreviations used in the given time zone.
+ *
+ * *indx is a state counter that the caller must initialize to zero
+ * before the first call, and not touch between calls.
+ *
+ * Returns the next known abbreviation, or NULL if there are no more.
+ *
+ * Note: the caller typically applies pg_interpret_timezone_abbrev()
+ * to each result. While that nominally results in O(N^2) time spent
+ * searching the sp->chars[] array, we don't expect any zone to have
+ * enough abbreviations to make that meaningful.
+ */
+const char *
+pg_get_next_timezone_abbrev(int *indx,
+ const pg_tz *tz)
+{
+ const char *result;
+ const struct state *sp = &tz->state;
+ const char *abbrs;
+ int abbrind;
+
+ /* If we're still in range, the result is the current abbrev. */
+ abbrs = sp->chars;
+ abbrind = *indx;
+ if (abbrind < 0 || abbrind >= sp->charcnt)
+ return NULL;
+ result = abbrs + abbrind;
+
+ /* Advance *indx past this abbrev and its trailing null. */
+ while (abbrs[abbrind] != '\0')
+ abbrind++;
+ abbrind++;
+ *indx = abbrind;
+
+ return result;
+}
+
/*
* If the given timezone uses only one GMT offset, store that offset
* into *gmtoff and return true, else return false.
TypeFuncClass
TypeInfo
TypeName
+TzAbbrevCache
U32
U8
UChar