Project: YugabyteDB (App Definition and Development / Database)
File: catalog-views.md

title: Catalog views for timezone information [YSQL] headerTitle: The pgtimezonenames and pgtimezoneabbrevs catalog views linkTitle: Catalog views description: Explains the information content of the pgtimezonenames and pgtimezoneabbrevs catalog views. [YSQL] menu: v2.18: identifier: catalog-views parent: timezones weight: 10 type: docs There are just two relevant catalog views: ```plpgsql select table_name as "name" from information_schema.views where lower(tableschema) = 'pgcatalog' and ( lower(table_name) like '%zone%' or lower(table_name) like '%time%') order by 1; ``` This is the result: ```output pgtimezoneabbrevs pgtimezonenames ``` The \\d meta-command produces this result: ```outout Column | Type name | text abbrev | text utc_offset | interval is_dst | boolean ``` The business unique key is name: ```plpgsql do $body$ declare x boolean not null := false; begin select (select count(*) from pgtimezonenames) = (select count(distinct name) from pgtimezonenames) into x; assert x, 'assert failed'; end; $body$; ``` The block finishes silently without error showing that the assertion holds. The bare pgtimezonenames view doesn't necessarily tell you explicitly if a particular timezone observes Daylight Savings Time. But for a timezone that does this, the value of isdst will be true_ when, but only when, Daylight Savings Time is in force. So you might be lucky, according to when you do your query, and learn something that at a different time of year you would not. This feels to be unsatisfactory. Here's the inspiration for an obvious technique to fix this irritation. <a name="pg-timezone-names-query"></a>Try this: ```plpgsql set timezone = 'America/Los_Angeles'; select current_setting('timezone') as "timezone", to_char('2021-01-01 12:00:00 UTC'::timestamptz, 'TZH:TZM : TZ') as "January regime", to_char('2021-07-01 12:00:00 UTC'::timestamptz, 'TZH:TZM : TZ') as "July regime"; ``` This is the result: ```output timezone | January regime | July regime +-+- America/Los_Angeles | -08 : PST | -07 : PDT ``` Create the function like this: ```plpgsql drop function if exists janandjultzabbrevsandoffsets() cascade; create function janandjultzabbrevsandoffsets() returns table( name text, jan_abbrev text, jul_abbrev text, jan_offset interval, jul_offset interval) language plpgsql as $body$ declare set_timezone constant text not null := $$set timezone = '%s'$$; tz_set text not null := ''; tzonentry text not null := ''; begin show timezone into tzonentry; for tz_set in ( select pgtimezonenames.name as a from pgtimezonenames ) loop execute format(settimezone, tzset); select current_setting('timezone'), to_char('2021-01-01 12:00:00 UTC'::timestamptz, 'TZ'), to_char('2021-07-01 12:00:00 UTC'::timestamptz, 'TZ'), to_char('2021-01-01 12:00:00 UTC'::timestamptz, 'TZH:TZM')::interval, to_char('2021-07-01 12:00:00 UTC'::timestamptz, 'TZH:TZM')::interval into name, jan_abbrev, jul_abbrev, jan_offset, jul_offset; return next; end loop; execute format(settimezone, tzon_entry); end; $body$; ``` Test it like this: ```plpgsql select name, jan_abbrev, jul_abbrev, lpad(janoffset::text, 9) as janoffset, lpad(juloffset::text, 9) as juloffset from janandjultzabbrevsandoffsets() where name = 'America/Los_Angeles' or name = 'Europe/London' order by name; ``` This is the result: ```output name | janabbrev | julabbrev | janoffset | juloffset ++++ America/Los_Angeles | PST | PDT | -08:00:00 | -07:00:00 Europe/London | GMT | BST | 00:00:00 | 01:00:00 ``` The definition of the view uses this technique and it extends the column list further by joining with facts from the . like, for example, the status of a timezone as Canonical , Alias, or

The \\d meta-command produces this result: ```output Column | Type +- abbrev | text utc_offset | interval is_dst | boolean ``` The business unique key is abbrev: ```plpgsql do $body$ declare x boolean not null := false; begin select (select count(*) from pgtimezoneabbrevs) = (select count(distinct abbrev) from pgtimezoneabbrevs) into x; assert x, 'assert failed'; end; $body$; ``` <a name="pg-timezone-abbrevs-query"></a>The block finishes silently without error showing that the assertion holds. Here is a telling example query: ```plpgsql select abbrev, utcoffset::text, isdst::text from pgtimezoneabbrevs where abbrev in ('PST', 'PDT') order by abbrev; ``` This is the result: ```output abbrev | utcoffset | isdst --++-- PDT | -07:00:00 | true PST | -08:00:00 | false ``` pgtimezonenames maps from its key, name, to an abbrev and utcoffset whose values, in general, are different during the Daylight Savings Time and the Standard Time periods. (The mapping is unique on any particular date.) See the at the end of the pgtimezonenames_ section above. pgtimezoneabbrevs maps from its key abbrev to a fixed, unique utcoffset value. When a timezone observes Daylight Savings Time, it has two different abbreviations: one for the Daylight Savings Time period, and one for the Standard Time period. See the at the end of the pgtimezoneabbrevs_ section above. The columns pgtimezonenames.abbrev and pgtimezoneabbrevs.abbrev record different kinds of facts The column structure and naming of the pgtimezonenames and pgtimezoneabbrevs catalog views might suggest to SQL professionals that pgtimezoneabbrevs implements a list of values that controls the [abbrev, utcoffset, isdst] tuple in pgtimezonenamesand they might wonder why the utcoffset and isdst columns are repeated in the referring controllee, pgtimezonenames. But this is not the case. The explanation is simplealbeit surprising: The pgtimezonenames view exposes an unrestricted projection of the tz&nbsp;database facts. (See the section .) The tz&nbsp;database facts are determined by committee and they change periodically in response to changes in convention. See, for example, : > In 2020, the territory of Yukon abandoned seasonal time change to permanently observe year-round Mountain Standard Time (MST). The pgtimezonenames view's population is controlled by operating system files. See the PostgreSQL documentation appendix . The PostgreSQL developers aim, with each successive release, to update the content of pgtimezonenames to keep it current with the tz&nbsp;database. (YugabyteDB Version 2.4 has therefore fallen behind currency. tracks this.) The administrator could fix this for a particular database by editing the appropriate files. The server refuses to start if pgtimezonenames.name is not unique. The contents of the pgtimezoneabbrevs view, too, can be changed, for a particular database, by the administrator. The population is controlled by operating system files. See the PostgreSQL documentation appendix . The PostgreSQL developers provide a default population. Further, which of these files are used can be controlled at the session level by setting the timezoneabbreviations run-time parameter. See the PostgreSQL documentation section . The server refuses to start if pgtimezoneabbrevs.abbrev_ is not unique. There is no requirement that every abbrev value found in pgtimezonenames, establishing this set over the whole year, will be found in pgtimezoneabbrevs. A particular abbrev that's found in pgtimezonenames can denote different values of utcoffset according to the timezone to which it belongs. For example, PST that maps both to -08:00 (for, for example, America/LosAngeles) and to +08:00 for Asia/Manila). See below. There is no requirement that every abbrev value from pgtimezonenames, establishing this set over the whole year maps to the same utcoffset value as does its match in pgtimezoneabbrevs_ when such a match is found. Nor could there be, given what the previous bullet states. The purpose of the pgtimezoneabbrevs view, these days, is

In historical versions of PostgreSQL, it provided a useful way to allow the specification of a timezone that wasn't defined in the shipped files described in appendix . of the PostgreSQL documenation. The following queries shed light on the information content of the pgtimezonenames and pgtimezoneabbrevs views. The section explains that either a name value from pgtimezonenames or an abbrev value from pgtimezoneabbrevs is legal as the argument of the at time zone operator that can be used to decorate a plain timestamp value or a timestamptz value. The queries show that nothing prevents what you might think is the abbreviation for a timezone of interest specifying a different utcoffset value in pgtimezoneabbrevs than is specified for its name value in pgtimezonenames_. This brings a risk that you don't get the outcome that you intend. This is spelled out more carefully in the section . The section shows how to implement a discipline that avoids this risk. A few timezones have the same name and abbreviation. Do this: ```plpgsql select name from pgtimezonenames where name = abbrev order by name; ``` This is the result: ```output EST GMT HST MST UCT UTC ``` ```plpgsql select name from pgtimezonenames where name in ( select abbrev from pgtimezoneabbrevs) order by name; ``` This is the result: ```output CET EET EST GMT HST MET MST UCT UTC WET ``` You can see that, for example, the string UCT is the name value in pgtimezonenames of a timezone that the marks as Deprecated; and that it is the abbrev value of a row in pgtimezoneabbrevs. The utcoffset_ value in these two differtent rows is not guaranteed to be the same. ```plpgsql with v as ( select distinct abbrev, utc_offset from pgtimezonenames where abbrev not like '%0%' and abbrev not like '%1%' and abbrev not like '%2%' and abbrev not like '%3%' and abbrev not like '%4%' and abbrev not like '%5%' and abbrev not like '%6%' and abbrev not like '%7%' and abbrev not like '%8%' and abbrev not like '%9%' ) select abbrev, lpad(utc_offset::text, 9) as "UTC offset" from v except select abbrev, lpad(utcoffset::text, 9) as "UTC offset" from pgtimezone_abbrevs order by abbrev; ``` This is the result: ```output abbrev | UTC offset --+ CAT | 02:00:00 CDT | -04:00:00 CST | 08:00:00 ChST | 10:00:00 HDT | -09:00:00 IST | 05:30:00 IST | 01:00:00 PST | 08:00:00 SST | -11:00:00 WEST | 01:00:00 WIB | 07:00:00 WIT | 09:00:00 WITA | 08:00:00 ``` Timezones whose abbrev value contains a digit are excluded because there are 228 such rows and none is present in the pgtimezoneabbrevs view. This query depends on the table function

It's convenient first to create the abbrevswithmanyoffsets_ view thus: ```plpgsql drop view if exists abbrevswithmany_offsets cascade; create view abbrevswithmanyoffsets(abbrev, utcoffset) as with jandistinctabbrevoffsetvalues(abbrev, utc_offset) as ( select distinct janabbrev, janoffset from janandjultzabbrevsandoffsets()), juldistinctabbrevoffsetvalues(abbrev, utc_offset) as ( select distinct julabbrev, juloffset from janandjultzabbrevsandoffsets()), alldistinctabbrevoffsetvalues(abbrev, utc_offset) as ( select abbrev, utcoffset from jandistinctabbrevoffset_values union select abbrev, utcoffset from juldistinctabbrevoffset_values), multiplydefinedabbrevs(abbrev) as ( select abbrev from alldistinctabbrevoffsetvalues group by abbrev having count(*) > 1) select abbrev, utc_offset from alldistinctabbrevoffsetvalues where abbrev in ( select abbrev from multiplydefinedabbrevs); ``` Check what it exposes this: ```plpgsql select abbrev, lpad(utc_offset ::text, 9) as "UTC offset" from abbrevswithmany_offsets order by abbrev, utc_offset; ``` This is the result: ```output abbrev | UTC offset --+ CDT | -05:00:00 CDT | -04:00:00 CST | -06:00:00 CST | -05:00:00 CST | 08:00:00 IST | 01:00:00 IST | 02:00:00 IST | 05:30:00 PST | -08:00:00 PST | 08:00:00 ``` The blank lines were added by hand to improve the readability. Now look at the pgtimezonenames rows that have PST as an abbreviation at some time of the year: ```plpgsql select name, jan_abbrev, jul_abbrev, lpad(jan_offset::text, 9) as "Jan offset", lpad(jul_offset::text, 9) as "Jul offset" from janandjultzabbrevsandoffsets() where jan_abbrev = 'PST' or jul_abbrev = 'PST' order by jan_offset; ``` This is the result: ```output name | janabbrev | julabbrev | Jan offset | Jul offset -++++ Canada/Yukon | PST | PDT | -08:00:00 | -07:00:00 America/Santa_Isabel | PST | PDT | -08:00:00 | -07:00:00 America/Dawson | PST | PDT | -08:00:00 | -07:00:00 America/Whitehorse | PST | PDT | -08:00:00 | -07:00:00 America/Vancouver | PST | PDT | -08:00:00 | -07:00:00 America/Los_Angeles | PST | PDT | -08:00:00 | -07:00:00 America/Ensenada | PST | PDT | -08:00:00 | -07:00:00 America/Tijuana | PST | PDT | -08:00:00 | -07:00:00 US/Pacific | PST | PDT | -08:00:00 | -07:00:00 PST8PDT | PST | PDT | -08:00:00 | -07:00:00 Mexico/BajaNorte | PST | PDT | -08:00:00 | -07:00:00 Canada/Pacific | PST | PDT | -08:00:00 | -07:00:00 Asia/Manila | PST | PST | 08:00:00 | 08:00:00 ``` The blank line was added by hand to improve the readability. Notice that the abbreviation stands for Pacific Standard Time in the context of the Canonical timezone America/LosAngeles and for Philippine Standard Time in the context of the Canonical timezone Asia/Manila_. Now look at the pgtimezonenames rows that have IST as an abbreviation at some time of the year: ```plpgsql select name, jan_abbrev, jul_abbrev, lpad(jan_offset::text, 9) as "Jan offset", lpad(jul_offset::text, 9) as "Jul offset" from janandjultzabbrevsandoffsets() where jan_abbrev = 'IST' or jul_abbrev = 'IST' order by jan_offset; ``` This is the result: ```output name | janabbrev | julabbrev | Jan offset | Jul offset -++++ Europe/Dublin | GMT | IST | 00:00:00 | 01:00:00 Eire | GMT | IST | 00:00:00 | 01:00:00 Israel | IST | IDT | 02:00:00 | 03:00:00 Asia/Tel_Aviv | IST | IDT | 02:00:00 | 03:00:00 Asia/Jerusalem | IST | IDT | 02:00:00 | 03:00:00 Asia/Kolkata | IST | IST | 05:30:00 | 05:30:00 Asia/Calcutta | IST | IST | 05:30:00 | 05:30:00 ``` The blank lines were added by hand to improve the readability. Notice that the abbreviation stands for Irish Summer Time in the context of the Canonical timezone Europe/Dublin, for Israel Standard Time in the context of the Canonical timezone Asia/Jerusalem, and for India Standard Time in the context of the Canonical timezone Asia/Kolkata_. These observations prompt the question "How are the different meanings of the values of pgtimezonenames.abbrev reflected in the pgtimezoneabbrevs.abbrev column?" Try this: ```plpgsql select abbrev, lpad(utc_offset ::text, 9) as "UTC offset" from pgtimezoneabbrevs where abbrev in ( select