diff options
Diffstat (limited to 'src/timezone')
27 files changed, 14021 insertions, 0 deletions
diff --git a/src/timezone/.gitignore b/src/timezone/.gitignore new file mode 100644 index 0000000..673f864 --- /dev/null +++ b/src/timezone/.gitignore @@ -0,0 +1,2 @@ +/zic +/abbrevs.txt diff --git a/src/timezone/Makefile b/src/timezone/Makefile new file mode 100644 index 0000000..fbbaae4 --- /dev/null +++ b/src/timezone/Makefile @@ -0,0 +1,79 @@ +#------------------------------------------------------------------------- +# +# Makefile +# Makefile for the timezone library + +# IDENTIFICATION +# src/timezone/Makefile +# +#------------------------------------------------------------------------- + +PGFILEDESC = "zic - time zone compiler" +PGAPPICON = win32 + +subdir = src/timezone +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global + +# files to build into backend +OBJS = \ + localtime.o \ + pgtz.o \ + strftime.o + +# files needed to build zic utility program +ZICOBJS = \ + $(WIN32RES) \ + zic.o + +# we now distribute the timezone data as a single file +TZDATAFILES = $(srcdir)/data/tzdata.zi + +# any custom options you might want to pass to zic while installing data files +ZIC_OPTIONS = + +# use system timezone data? +ifneq (,$(with_system_tzdata)) +override CPPFLAGS += '-DSYSTEMTZDIR="$(with_system_tzdata)"' +endif + +include $(top_srcdir)/src/backend/common.mk + +ifeq (,$(with_system_tzdata)) +all: zic +endif + +# We could do this test in the action section: +# $(if $(ZIC),$(ZIC),./zic) +# but GNU make versions <= 3.78.1 or perhaps later have a bug +# that causes a segfault; GNU make 3.81 or later fixes this. +ifeq (,$(ZIC)) +ZIC= ./zic +endif + +zic: $(ZICOBJS) | submake-libpgport + $(CC) $(CFLAGS) $(ZICOBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +install: all installdirs +ifeq (,$(with_system_tzdata)) + $(ZIC) -d '$(DESTDIR)$(datadir)/timezone' $(ZIC_OPTIONS) $(TZDATAFILES) +endif + $(MAKE) -C tznames $@ + +# Note: -P code currently depends on '-b fat'. Not worth fixing right now. +abbrevs.txt: zic $(TZDATAFILES) + mkdir junkdir + $(ZIC) -P -b fat -d junkdir $(TZDATAFILES) | LANG=C sort | uniq >abbrevs.txt + rm -rf junkdir + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(datadir)' + +uninstall: +ifeq (,$(with_system_tzdata)) + rm -rf '$(DESTDIR)$(datadir)/timezone' +endif + $(MAKE) -C tznames $@ + +clean distclean maintainer-clean: + rm -f zic$(X) $(ZICOBJS) abbrevs.txt diff --git a/src/timezone/README b/src/timezone/README new file mode 100644 index 0000000..f588d1f --- /dev/null +++ b/src/timezone/README @@ -0,0 +1,134 @@ +src/timezone/README + +This is a PostgreSQL adapted version of the IANA timezone library from + + https://www.iana.org/time-zones + +The latest version of the timezone data and library source code is +available right from that page. It's best to get the merged file +tzdb-NNNNX.tar.lz, since the other archive formats omit tzdata.zi. +Historical versions, as well as release announcements, can be found +elsewhere on the site. + +Since time zone rules change frequently in some parts of the world, +we should endeavor to update the data files before each PostgreSQL +release. The code need not be updated as often, but we must track +changes that might affect interpretation of the data files. + + +Time Zone data +============== + +We distribute the time zone source data as-is under src/timezone/data/. +Currently, we distribute just the abbreviated single-file format +"tzdata.zi", to reduce the size of our tarballs as well as churn +in our git repo. Feeding that file to zic produces the same compiled +output as feeding the bulkier individual data files would do. + +While data/tzdata.zi can just be duplicated when updating, manual effort +is needed to update the time zone abbreviation lists under tznames/. +These need to be changed whenever new abbreviations are invented or the +UTC offset associated with an existing abbreviation changes. To detect +if this has happened, after installing new files under data/ do + make abbrevs.txt +which will produce a file showing all abbreviations that are in current +use according to the data/ files. Compare this to known_abbrevs.txt, +which is the list that existed last time the tznames/ files were updated. +Update tznames/ as seems appropriate, then replace known_abbrevs.txt +in the same commit. Usually, if a known abbreviation has changed meaning, +the appropriate fix is to make it refer to a long-form zone name instead +of a fixed GMT offset. + +The core regression test suite does some simple validation of the zone +data and abbreviations data (notably by checking that the pg_timezone_names +and pg_timezone_abbrevs views don't throw errors). It's worth running it +as a cross-check on proposed updates. + +When there has been a new release of Windows (probably including Service +Packs), the list of matching timezones need to be updated. Run the +script in src/tools/win32tzlist.pl on a Windows machine running this new +release and apply any new timezones that it detects. Never remove any +mappings in case they are removed in Windows, since we still need to +match properly on the old version. + + +Time Zone code +============== + +The code in this directory is currently synced with tzcode release 2020d. +There are many cosmetic (and not so cosmetic) differences from the +original tzcode library, but diffs in the upstream version should usually +be propagated to our version. Here are some notes about that. + +For the most part we want to use the upstream code as-is, but there are +several considerations preventing an exact match: + +* For readability/maintainability we reformat the code to match our own +conventions; this includes pgindent'ing it and getting rid of upstream's +overuse of "register" declarations. (It used to include conversion of +old-style function declarations to C89 style, but thank goodness they +fixed that.) + +* We need the code to follow Postgres' portability conventions; this +includes relying on configure's results rather than hand-hacked +#defines (see private.h in particular). + +* Similarly, avoid relying on <stdint.h> features that may not exist on old +systems. In particular this means using Postgres' definitions of the int32 +and int64 typedefs, not int_fast32_t/int_fast64_t. Likewise we use +PG_INT32_MIN/MAX not INT32_MIN/MAX. (Once we desupport all PG versions +that don't require C99, it'd be practical to rely on <stdint.h> and remove +this set of diffs; but that day is not yet.) + +* Since Postgres is typically built on a system that has its own copy +of the <time.h> functions, we must avoid conflicting with those. This +mandates renaming typedef time_t to pg_time_t, and similarly for most +other exposed names. + +* zic.c's typedef "lineno" is renamed to "lineno_t", because having +"lineno" in our typedefs list would cause unfortunate pgindent behavior +in some other files where we have variables named that. + +* We have exposed the tzload() and tzparse() internal functions, and +slightly modified the API of the former, in part because it now relies +on our own pg_open_tzfile() rather than opening files for itself. + +* tzparse() is adjusted to never try to load the TZDEFRULES zone. + +* There's a fair amount of code we don't need and have removed, +including all the nonstandard optional APIs. We have also added +a few functions of our own at the bottom of localtime.c. + +* In zic.c, we have added support for a -P (print_abbrevs) switch, which +is used to create the "abbrevs.txt" summary of currently-in-use zone +abbreviations that was described above. + + +The most convenient way to compare a new tzcode release to our code is +to first run the tzcode source files through a sed filter like this: + + sed -r \ + -e 's/^([ \t]*)\*\*([ \t])/\1 *\2/' \ + -e 's/^([ \t]*)\*\*$/\1 */' \ + -e 's|^\*/| */|' \ + -e 's/\bregister[ \t]//g' \ + -e 's/\bATTRIBUTE_PURE[ \t]//g' \ + -e 's/int_fast32_t/int32/g' \ + -e 's/int_fast64_t/int64/g' \ + -e 's/intmax_t/int64/g' \ + -e 's/INT32_MIN/PG_INT32_MIN/g' \ + -e 's/INT32_MAX/PG_INT32_MAX/g' \ + -e 's/INTMAX_MIN/PG_INT64_MIN/g' \ + -e 's/INTMAX_MAX/PG_INT64_MAX/g' \ + -e 's/struct[ \t]+tm\b/struct pg_tm/g' \ + -e 's/\btime_t\b/pg_time_t/g' \ + -e 's/lineno/lineno_t/g' \ + +and then run them through pgindent. (The first three sed patterns deal +with conversion of their block comment style to something pgindent +won't make a hash of; the remainder address other points noted above.) +After that, the files can be diff'd directly against our corresponding +files. Also, it's typically helpful to diff against the previous tzcode +release (after processing that the same way), and then try to apply the +diff to our files. This will take care of most of the changes +mechanically. diff --git a/src/timezone/data/tzdata.zi b/src/timezone/data/tzdata.zi new file mode 100644 index 0000000..c38197e --- /dev/null +++ b/src/timezone/data/tzdata.zi @@ -0,0 +1,4437 @@ +# version 2022a +# This zic input file is in the public domain. +R d 1916 o - Jun 14 23s 1 S +R d 1916 1919 - O Su>=1 23s 0 - +R d 1917 o - Mar 24 23s 1 S +R d 1918 o - Mar 9 23s 1 S +R d 1919 o - Mar 1 23s 1 S +R d 1920 o - F 14 23s 1 S +R d 1920 o - O 23 23s 0 - +R d 1921 o - Mar 14 23s 1 S +R d 1921 o - Jun 21 23s 0 - +R d 1939 o - S 11 23s 1 S +R d 1939 o - N 19 1 0 - +R d 1944 1945 - Ap M>=1 2 1 S +R d 1944 o - O 8 2 0 - +R d 1945 o - S 16 1 0 - +R d 1971 o - Ap 25 23s 1 S +R d 1971 o - S 26 23s 0 - +R d 1977 o - May 6 0 1 S +R d 1977 o - O 21 0 0 - +R d 1978 o - Mar 24 1 1 S +R d 1978 o - S 22 3 0 - +R d 1980 o - Ap 25 0 1 S +R d 1980 o - O 31 2 0 - +Z Africa/Algiers 0:12:12 - LMT 1891 Mar 16 +0:9:21 - PMT 1911 Mar 11 +0 d WE%sT 1940 F 25 2 +1 d CE%sT 1946 O 7 +0 - WET 1956 Ja 29 +1 - CET 1963 Ap 14 +0 d WE%sT 1977 O 21 +1 d CE%sT 1979 O 26 +0 d WE%sT 1981 May +1 - CET +Z Atlantic/Cape_Verde -1:34:4 - LMT 1912 Ja 1 2u +-2 - -02 1942 S +-2 1 -01 1945 O 15 +-2 - -02 1975 N 25 2 +-1 - -01 +Z Africa/Ndjamena 1:0:12 - LMT 1912 +1 - WAT 1979 O 14 +1 1 WAST 1980 Mar 8 +1 - WAT +Z Africa/Abidjan -0:16:8 - LMT 1912 +0 - GMT +L Africa/Abidjan Africa/Accra +L Africa/Abidjan Africa/Bamako +L Africa/Abidjan Africa/Banjul +L Africa/Abidjan Africa/Conakry +L Africa/Abidjan Africa/Dakar +L Africa/Abidjan Africa/Freetown +L Africa/Abidjan Africa/Lome +L Africa/Abidjan Africa/Nouakchott +L Africa/Abidjan Africa/Ouagadougou +L Africa/Abidjan Atlantic/St_Helena +R K 1940 o - Jul 15 0 1 S +R K 1940 o - O 1 0 0 - +R K 1941 o - Ap 15 0 1 S +R K 1941 o - S 16 0 0 - +R K 1942 1944 - Ap 1 0 1 S +R K 1942 o - O 27 0 0 - +R K 1943 1945 - N 1 0 0 - +R K 1945 o - Ap 16 0 1 S +R K 1957 o - May 10 0 1 S +R K 1957 1958 - O 1 0 0 - +R K 1958 o - May 1 0 1 S +R K 1959 1981 - May 1 1 1 S +R K 1959 1965 - S 30 3 0 - +R K 1966 1994 - O 1 3 0 - +R K 1982 o - Jul 25 1 1 S +R K 1983 o - Jul 12 1 1 S +R K 1984 1988 - May 1 1 1 S +R K 1989 o - May 6 1 1 S +R K 1990 1994 - May 1 1 1 S +R K 1995 2010 - Ap lastF 0s 1 S +R K 1995 2005 - S lastTh 24 0 - +R K 2006 o - S 21 24 0 - +R K 2007 o - S Th>=1 24 0 - +R K 2008 o - Au lastTh 24 0 - +R K 2009 o - Au 20 24 0 - +R K 2010 o - Au 10 24 0 - +R K 2010 o - S 9 24 1 S +R K 2010 o - S lastTh 24 0 - +R K 2014 o - May 15 24 1 S +R K 2014 o - Jun 26 24 0 - +R K 2014 o - Jul 31 24 1 S +R K 2014 o - S lastTh 24 0 - +Z Africa/Cairo 2:5:9 - LMT 1900 O +2 K EE%sT +Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u +-1 - -01 1975 +0 - GMT +Z Africa/Nairobi 2:27:16 - LMT 1908 May +2:30 - +0230 1928 Jun 30 24 +3 - EAT 1930 Ja 4 24 +2:30 - +0230 1936 D 31 24 +2:45 - +0245 1942 Jul 31 24 +3 - EAT +L Africa/Nairobi Africa/Addis_Ababa +L Africa/Nairobi Africa/Asmara +L Africa/Nairobi Africa/Dar_es_Salaam +L Africa/Nairobi Africa/Djibouti +L Africa/Nairobi Africa/Kampala +L Africa/Nairobi Africa/Mogadishu +L Africa/Nairobi Indian/Antananarivo +L Africa/Nairobi Indian/Comoro +L Africa/Nairobi Indian/Mayotte +Z Africa/Monrovia -0:43:8 - LMT 1882 +-0:43:8 - MMT 1919 Mar +-0:44:30 - MMT 1972 Ja 7 +0 - GMT +R L 1951 o - O 14 2 1 S +R L 1952 o - Ja 1 0 0 - +R L 1953 o - O 9 2 1 S +R L 1954 o - Ja 1 0 0 - +R L 1955 o - S 30 0 1 S +R L 1956 o - Ja 1 0 0 - +R L 1982 1984 - Ap 1 0 1 S +R L 1982 1985 - O 1 0 0 - +R L 1985 o - Ap 6 0 1 S +R L 1986 o - Ap 4 0 1 S +R L 1986 o - O 3 0 0 - +R L 1987 1989 - Ap 1 0 1 S +R L 1987 1989 - O 1 0 0 - +R L 1997 o - Ap 4 0 1 S +R L 1997 o - O 4 0 0 - +R L 2013 o - Mar lastF 1 1 S +R L 2013 o - O lastF 2 0 - +Z Africa/Tripoli 0:52:44 - LMT 1920 +1 L CE%sT 1959 +2 - EET 1982 +1 L CE%sT 1990 May 4 +2 - EET 1996 S 30 +1 L CE%sT 1997 O 4 +2 - EET 2012 N 10 2 +1 L CE%sT 2013 O 25 2 +2 - EET +R MU 1982 o - O 10 0 1 - +R MU 1983 o - Mar 21 0 0 - +R MU 2008 o - O lastSu 2 1 - +R MU 2009 o - Mar lastSu 2 0 - +Z Indian/Mauritius 3:50 - LMT 1907 +4 MU +04/+05 +R M 1939 o - S 12 0 1 - +R M 1939 o - N 19 0 0 - +R M 1940 o - F 25 0 1 - +R M 1945 o - N 18 0 0 - +R M 1950 o - Jun 11 0 1 - +R M 1950 o - O 29 0 0 - +R M 1967 o - Jun 3 12 1 - +R M 1967 o - O 1 0 0 - +R M 1974 o - Jun 24 0 1 - +R M 1974 o - S 1 0 0 - +R M 1976 1977 - May 1 0 1 - +R M 1976 o - Au 1 0 0 - +R M 1977 o - S 28 0 0 - +R M 1978 o - Jun 1 0 1 - +R M 1978 o - Au 4 0 0 - +R M 2008 o - Jun 1 0 1 - +R M 2008 o - S 1 0 0 - +R M 2009 o - Jun 1 0 1 - +R M 2009 o - Au 21 0 0 - +R M 2010 o - May 2 0 1 - +R M 2010 o - Au 8 0 0 - +R M 2011 o - Ap 3 0 1 - +R M 2011 o - Jul 31 0 0 - +R M 2012 2013 - Ap lastSu 2 1 - +R M 2012 o - Jul 20 3 0 - +R M 2012 o - Au 20 2 1 - +R M 2012 o - S 30 3 0 - +R M 2013 o - Jul 7 3 0 - +R M 2013 o - Au 10 2 1 - +R M 2013 2018 - O lastSu 3 0 - +R M 2014 2018 - Mar lastSu 2 1 - +R M 2014 o - Jun 28 3 0 - +R M 2014 o - Au 2 2 1 - +R M 2015 o - Jun 14 3 0 - +R M 2015 o - Jul 19 2 1 - +R M 2016 o - Jun 5 3 0 - +R M 2016 o - Jul 10 2 1 - +R M 2017 o - May 21 3 0 - +R M 2017 o - Jul 2 2 1 - +R M 2018 o - May 13 3 0 - +R M 2018 o - Jun 17 2 1 - +R M 2019 o - May 5 3 -1 - +R M 2019 o - Jun 9 2 0 - +R M 2020 o - Ap 19 3 -1 - +R M 2020 o - May 31 2 0 - +R M 2021 o - Ap 11 3 -1 - +R M 2021 o - May 16 2 0 - +R M 2022 o - Mar 27 3 -1 - +R M 2022 o - May 8 2 0 - +R M 2023 o - Mar 19 3 -1 - +R M 2023 o - Ap 30 2 0 - +R M 2024 o - Mar 10 3 -1 - +R M 2024 o - Ap 14 2 0 - +R M 2025 o - F 23 3 -1 - +R M 2025 o - Ap 6 2 0 - +R M 2026 o - F 15 3 -1 - +R M 2026 o - Mar 22 2 0 - +R M 2027 o - F 7 3 -1 - +R M 2027 o - Mar 14 2 0 - +R M 2028 o - Ja 23 3 -1 - +R M 2028 o - Mar 5 2 0 - +R M 2029 o - Ja 14 3 -1 - +R M 2029 o - F 18 2 0 - +R M 2029 o - D 30 3 -1 - +R M 2030 o - F 10 2 0 - +R M 2030 o - D 22 3 -1 - +R M 2031 o - F 2 2 0 - +R M 2031 o - D 14 3 -1 - +R M 2032 o - Ja 18 2 0 - +R M 2032 o - N 28 3 -1 - +R M 2033 o - Ja 9 2 0 - +R M 2033 o - N 20 3 -1 - +R M 2033 o - D 25 2 0 - +R M 2034 o - N 5 3 -1 - +R M 2034 o - D 17 2 0 - +R M 2035 o - O 28 3 -1 - +R M 2035 o - D 9 2 0 - +R M 2036 o - O 19 3 -1 - +R M 2036 o - N 23 2 0 - +R M 2037 o - O 4 3 -1 - +R M 2037 o - N 15 2 0 - +R M 2038 o - S 26 3 -1 - +R M 2038 o - N 7 2 0 - +R M 2039 o - S 18 3 -1 - +R M 2039 o - O 23 2 0 - +R M 2040 o - S 2 3 -1 - +R M 2040 o - O 14 2 0 - +R M 2041 o - Au 25 3 -1 - +R M 2041 o - S 29 2 0 - +R M 2042 o - Au 10 3 -1 - +R M 2042 o - S 21 2 0 - +R M 2043 o - Au 2 3 -1 - +R M 2043 o - S 13 2 0 - +R M 2044 o - Jul 24 3 -1 - +R M 2044 o - Au 28 2 0 - +R M 2045 o - Jul 9 3 -1 - +R M 2045 o - Au 20 2 0 - +R M 2046 o - Jul 1 3 -1 - +R M 2046 o - Au 12 2 0 - +R M 2047 o - Jun 23 3 -1 - +R M 2047 o - Jul 28 2 0 - +R M 2048 o - Jun 7 3 -1 - +R M 2048 o - Jul 19 2 0 - +R M 2049 o - May 30 3 -1 - +R M 2049 o - Jul 4 2 0 - +R M 2050 o - May 15 3 -1 - +R M 2050 o - Jun 26 2 0 - +R M 2051 o - May 7 3 -1 - +R M 2051 o - Jun 18 2 0 - +R M 2052 o - Ap 28 3 -1 - +R M 2052 o - Jun 2 2 0 - +R M 2053 o - Ap 13 3 -1 - +R M 2053 o - May 25 2 0 - +R M 2054 o - Ap 5 3 -1 - +R M 2054 o - May 17 2 0 - +R M 2055 o - Mar 28 3 -1 - +R M 2055 o - May 2 2 0 - +R M 2056 o - Mar 12 3 -1 - +R M 2056 o - Ap 23 2 0 - +R M 2057 o - Mar 4 3 -1 - +R M 2057 o - Ap 8 2 0 - +R M 2058 o - F 17 3 -1 - +R M 2058 o - Mar 31 2 0 - +R M 2059 o - F 9 3 -1 - +R M 2059 o - Mar 23 2 0 - +R M 2060 o - F 1 3 -1 - +R M 2060 o - Mar 7 2 0 - +R M 2061 o - Ja 16 3 -1 - +R M 2061 o - F 27 2 0 - +R M 2062 o - Ja 8 3 -1 - +R M 2062 o - F 19 2 0 - +R M 2062 o - D 31 3 -1 - +R M 2063 o - F 4 2 0 - +R M 2063 o - D 16 3 -1 - +R M 2064 o - Ja 27 2 0 - +R M 2064 o - D 7 3 -1 - +R M 2065 o - Ja 11 2 0 - +R M 2065 o - N 22 3 -1 - +R M 2066 o - Ja 3 2 0 - +R M 2066 o - N 14 3 -1 - +R M 2066 o - D 26 2 0 - +R M 2067 o - N 6 3 -1 - +R M 2067 o - D 11 2 0 - +R M 2068 o - O 21 3 -1 - +R M 2068 o - D 2 2 0 - +R M 2069 o - O 13 3 -1 - +R M 2069 o - N 24 2 0 - +R M 2070 o - O 5 3 -1 - +R M 2070 o - N 9 2 0 - +R M 2071 o - S 20 3 -1 - +R M 2071 o - N 1 2 0 - +R M 2072 o - S 11 3 -1 - +R M 2072 o - O 16 2 0 - +R M 2073 o - Au 27 3 -1 - +R M 2073 o - O 8 2 0 - +R M 2074 o - Au 19 3 -1 - +R M 2074 o - S 30 2 0 - +R M 2075 o - Au 11 3 -1 - +R M 2075 o - S 15 2 0 - +R M 2076 o - Jul 26 3 -1 - +R M 2076 o - S 6 2 0 - +R M 2077 o - Jul 18 3 -1 - +R M 2077 o - Au 29 2 0 - +R M 2078 o - Jul 10 3 -1 - +R M 2078 o - Au 14 2 0 - +R M 2079 o - Jun 25 3 -1 - +R M 2079 o - Au 6 2 0 - +R M 2080 o - Jun 16 3 -1 - +R M 2080 o - Jul 21 2 0 - +R M 2081 o - Jun 1 3 -1 - +R M 2081 o - Jul 13 2 0 - +R M 2082 o - May 24 3 -1 - +R M 2082 o - Jul 5 2 0 - +R M 2083 o - May 16 3 -1 - +R M 2083 o - Jun 20 2 0 - +R M 2084 o - Ap 30 3 -1 - +R M 2084 o - Jun 11 2 0 - +R M 2085 o - Ap 22 3 -1 - +R M 2085 o - Jun 3 2 0 - +R M 2086 o - Ap 14 3 -1 - +R M 2086 o - May 19 2 0 - +R M 2087 o - Mar 30 3 -1 - +R M 2087 o - May 11 2 0 - +Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 +0 M +00/+01 1984 Mar 16 +1 - +01 1986 +0 M +00/+01 2018 O 28 3 +1 M +01/+00 +Z Africa/El_Aaiun -0:52:48 - LMT 1934 +-1 - -01 1976 Ap 14 +0 M +00/+01 2018 O 28 3 +1 M +01/+00 +Z Africa/Maputo 2:10:20 - LMT 1903 Mar +2 - CAT +L Africa/Maputo Africa/Blantyre +L Africa/Maputo Africa/Bujumbura +L Africa/Maputo Africa/Gaborone +L Africa/Maputo Africa/Harare +L Africa/Maputo Africa/Kigali +L Africa/Maputo Africa/Lubumbashi +L Africa/Maputo Africa/Lusaka +R NA 1994 o - Mar 21 0 -1 WAT +R NA 1994 2017 - S Su>=1 2 0 CAT +R NA 1995 2017 - Ap Su>=1 2 -1 WAT +Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 +1:30 - +0130 1903 Mar +2 - SAST 1942 S 20 2 +2 1 SAST 1943 Mar 21 2 +2 - SAST 1990 Mar 21 +2 NA %s +Z Africa/Lagos 0:13:35 - LMT 1905 Jul +0 - GMT 1908 Jul +0:13:35 - LMT 1914 +0:30 - +0030 1919 S +1 - WAT +L Africa/Lagos Africa/Bangui +L Africa/Lagos Africa/Brazzaville +L Africa/Lagos Africa/Douala +L Africa/Lagos Africa/Kinshasa +L Africa/Lagos Africa/Libreville +L Africa/Lagos Africa/Luanda +L Africa/Lagos Africa/Malabo +L Africa/Lagos Africa/Niamey +L Africa/Lagos Africa/Porto-Novo +Z Indian/Reunion 3:41:52 - LMT 1911 Jun +4 - +04 +Z Africa/Sao_Tome 0:26:56 - LMT 1884 +-0:36:45 - LMT 1912 Ja 1 0u +0 - GMT 2018 Ja 1 1 +1 - WAT 2019 Ja 1 2 +0 - GMT +Z Indian/Mahe 3:41:48 - LMT 1907 +4 - +04 +R SA 1942 1943 - S Su>=15 2 1 - +R SA 1943 1944 - Mar Su>=15 2 0 - +Z Africa/Johannesburg 1:52 - LMT 1892 F 8 +1:30 - SAST 1903 Mar +2 SA SAST +L Africa/Johannesburg Africa/Maseru +L Africa/Johannesburg Africa/Mbabane +R SD 1970 o - May 1 0 1 S +R SD 1970 1985 - O 15 0 0 - +R SD 1971 o - Ap 30 0 1 S +R SD 1972 1985 - Ap lastSu 0 1 S +Z Africa/Khartoum 2:10:8 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT 2017 N +2 - CAT +Z Africa/Juba 2:6:28 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT 2021 F +2 - CAT +R n 1939 o - Ap 15 23s 1 S +R n 1939 o - N 18 23s 0 - +R n 1940 o - F 25 23s 1 S +R n 1941 o - O 6 0 0 - +R n 1942 o - Mar 9 0 1 S +R n 1942 o - N 2 3 0 - +R n 1943 o - Mar 29 2 1 S +R n 1943 o - Ap 17 2 0 - +R n 1943 o - Ap 25 2 1 S +R n 1943 o - O 4 2 0 - +R n 1944 1945 - Ap M>=1 2 1 S +R n 1944 o - O 8 0 0 - +R n 1945 o - S 16 0 0 - +R n 1977 o - Ap 30 0s 1 S +R n 1977 o - S 24 0s 0 - +R n 1978 o - May 1 0s 1 S +R n 1978 o - O 1 0s 0 - +R n 1988 o - Jun 1 0s 1 S +R n 1988 1990 - S lastSu 0s 0 - +R n 1989 o - Mar 26 0s 1 S +R n 1990 o - May 1 0s 1 S +R n 2005 o - May 1 0s 1 S +R n 2005 o - S 30 1s 0 - +R n 2006 2008 - Mar lastSu 2s 1 S +R n 2006 2008 - O lastSu 2s 0 - +Z Africa/Tunis 0:40:44 - LMT 1881 May 12 +0:9:21 - PMT 1911 Mar 11 +1 n CE%sT +Z Antarctica/Casey 0 - -00 1969 +8 - +08 2009 O 18 2 +11 - +11 2010 Mar 5 2 +8 - +08 2011 O 28 2 +11 - +11 2012 F 21 17u +8 - +08 2016 O 22 +11 - +11 2018 Mar 11 4 +8 - +08 2018 O 7 4 +11 - +11 2019 Mar 17 3 +8 - +08 2019 O 4 3 +11 - +11 2020 Mar 8 3 +8 - +08 2020 O 4 0:1 +11 - +11 +Z Antarctica/Davis 0 - -00 1957 Ja 13 +7 - +07 1964 N +0 - -00 1969 F +7 - +07 2009 O 18 2 +5 - +05 2010 Mar 10 20u +7 - +07 2011 O 28 2 +5 - +05 2012 F 21 20u +7 - +07 +Z Antarctica/Mawson 0 - -00 1954 F 13 +6 - +06 2009 O 18 2 +5 - +05 +Z Indian/Kerguelen 0 - -00 1950 +5 - +05 +R Tr 2005 ma - Mar lastSu 1u 2 +02 +R Tr 2004 ma - O lastSu 1u 0 +00 +Z Antarctica/Troll 0 - -00 2005 F 12 +0 Tr %s +Z Antarctica/Vostok 0 - -00 1957 D 16 +6 - +06 +Z Antarctica/Rothera 0 - -00 1976 D +-3 - -03 +Z Asia/Kabul 4:36:48 - LMT 1890 +4 - +04 1945 +4:30 - +0430 +R AM 2011 o - Mar lastSu 2s 1 - +R AM 2011 o - O lastSu 2s 0 - +Z Asia/Yerevan 2:58 - LMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1995 S 24 2s +4 - +04 1997 +4 R +04/+05 2011 +4 AM +04/+05 +R AZ 1997 2015 - Mar lastSu 4 1 - +R AZ 1997 2015 - O lastSu 5 0 - +Z Asia/Baku 3:19:24 - LMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1992 S lastSu 2s +4 - +04 1996 +4 E +04/+05 1997 +4 AZ +04/+05 +R BD 2009 o - Jun 19 23 1 - +R BD 2009 o - D 31 24 0 - +Z Asia/Dhaka 6:1:40 - LMT 1890 +5:53:20 - HMT 1941 O +6:30 - +0630 1942 May 15 +5:30 - +0530 1942 S +6:30 - +0630 1951 S 30 +6 - +06 2009 +6 BD +06/+07 +Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 +5:30 - +0530 1987 O +6 - +06 +Z Indian/Chagos 4:49:40 - LMT 1907 +5 - +05 1996 +6 - +06 +Z Asia/Brunei 7:39:40 - LMT 1926 Mar +7:30 - +0730 1933 +8 - +08 +Z Asia/Yangon 6:24:47 - LMT 1880 +6:24:47 - RMT 1920 +6:30 - +0630 1942 May +9 - +09 1945 May 3 +6:30 - +0630 +R Sh 1919 o - Ap 12 24 1 D +R Sh 1919 o - S 30 24 0 S +R Sh 1940 o - Jun 1 0 1 D +R Sh 1940 o - O 12 24 0 S +R Sh 1941 o - Mar 15 0 1 D +R Sh 1941 o - N 1 24 0 S +R Sh 1942 o - Ja 31 0 1 D +R Sh 1945 o - S 1 24 0 S +R Sh 1946 o - May 15 0 1 D +R Sh 1946 o - S 30 24 0 S +R Sh 1947 o - Ap 15 0 1 D +R Sh 1947 o - O 31 24 0 S +R Sh 1948 1949 - May 1 0 1 D +R Sh 1948 1949 - S 30 24 0 S +R CN 1986 o - May 4 2 1 D +R CN 1986 1991 - S Su>=11 2 0 S +R CN 1987 1991 - Ap Su>=11 2 1 D +Z Asia/Shanghai 8:5:43 - LMT 1901 +8 Sh C%sT 1949 May 28 +8 CN C%sT +Z Asia/Urumqi 5:50:20 - LMT 1928 +6 - +06 +R HK 1946 o - Ap 21 0 1 S +R HK 1946 o - D 1 3:30s 0 - +R HK 1947 o - Ap 13 3:30s 1 S +R HK 1947 o - N 30 3:30s 0 - +R HK 1948 o - May 2 3:30s 1 S +R HK 1948 1952 - O Su>=28 3:30s 0 - +R HK 1949 1953 - Ap Su>=1 3:30 1 S +R HK 1953 1964 - O Su>=31 3:30 0 - +R HK 1954 1964 - Mar Su>=18 3:30 1 S +R HK 1965 1976 - Ap Su>=16 3:30 1 S +R HK 1965 1976 - O Su>=16 3:30 0 - +R HK 1973 o - D 30 3:30 1 S +R HK 1979 o - May 13 3:30 1 S +R HK 1979 o - O 21 3:30 0 - +Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 0:36:42 +8 - HKT 1941 Jun 15 3 +8 1 HKST 1941 O 1 4 +8 0:30 HKWT 1941 D 25 +9 - JST 1945 N 18 2 +8 HK HK%sT +R f 1946 o - May 15 0 1 D +R f 1946 o - O 1 0 0 S +R f 1947 o - Ap 15 0 1 D +R f 1947 o - N 1 0 0 S +R f 1948 1951 - May 1 0 1 D +R f 1948 1951 - O 1 0 0 S +R f 1952 o - Mar 1 0 1 D +R f 1952 1954 - N 1 0 0 S +R f 1953 1959 - Ap 1 0 1 D +R f 1955 1961 - O 1 0 0 S +R f 1960 1961 - Jun 1 0 1 D +R f 1974 1975 - Ap 1 0 1 D +R f 1974 1975 - O 1 0 0 S +R f 1979 o - Jul 1 0 1 D +R f 1979 o - O 1 0 0 S +Z Asia/Taipei 8:6 - LMT 1896 +8 - CST 1937 O +9 - JST 1945 S 21 1 +8 f C%sT +R _ 1942 1943 - Ap 30 23 1 - +R _ 1942 o - N 17 23 0 - +R _ 1943 o - S 30 23 0 S +R _ 1946 o - Ap 30 23s 1 D +R _ 1946 o - S 30 23s 0 S +R _ 1947 o - Ap 19 23s 1 D +R _ 1947 o - N 30 23s 0 S +R _ 1948 o - May 2 23s 1 D +R _ 1948 o - O 31 23s 0 S +R _ 1949 1950 - Ap Sa>=1 23s 1 D +R _ 1949 1950 - O lastSa 23s 0 S +R _ 1951 o - Mar 31 23s 1 D +R _ 1951 o - O 28 23s 0 S +R _ 1952 1953 - Ap Sa>=1 23s 1 D +R _ 1952 o - N 1 23s 0 S +R _ 1953 1954 - O lastSa 23s 0 S +R _ 1954 1956 - Mar Sa>=17 23s 1 D +R _ 1955 o - N 5 23s 0 S +R _ 1956 1964 - N Su>=1 3:30 0 S +R _ 1957 1964 - Mar Su>=18 3:30 1 D +R _ 1965 1973 - Ap Su>=16 3:30 1 D +R _ 1965 1966 - O Su>=16 2:30 0 S +R _ 1967 1976 - O Su>=16 3:30 0 S +R _ 1973 o - D 30 3:30 1 D +R _ 1975 1976 - Ap Su>=16 3:30 1 D +R _ 1979 o - May 13 3:30 1 D +R _ 1979 o - O Su>=16 3:30 0 S +Z Asia/Macau 7:34:10 - LMT 1904 O 30 +8 - CST 1941 D 21 23 +9 _ +09/+10 1945 S 30 24 +8 _ C%sT +R CY 1975 o - Ap 13 0 1 S +R CY 1975 o - O 12 0 0 - +R CY 1976 o - May 15 0 1 S +R CY 1976 o - O 11 0 0 - +R CY 1977 1980 - Ap Su>=1 0 1 S +R CY 1977 o - S 25 0 0 - +R CY 1978 o - O 2 0 0 - +R CY 1979 1997 - S lastSu 0 0 - +R CY 1981 1998 - Mar lastSu 0 1 S +Z Asia/Nicosia 2:13:28 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT +Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT 2016 S 8 +3 - +03 2017 O 29 1u +2 E EE%sT +L Asia/Nicosia Europe/Nicosia +Z Asia/Tbilisi 2:59:11 - LMT 1880 +2:59:11 - TBMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1992 +3 e +03/+04 1994 S lastSu +4 e +04/+05 1996 O lastSu +4 1 +05 1997 Mar lastSu +4 e +04/+05 2004 Jun 27 +3 R +03/+04 2005 Mar lastSu 2 +4 - +04 +Z Asia/Dili 8:22:20 - LMT 1912 +8 - +08 1942 F 21 23 +9 - +09 1976 May 3 +8 - +08 2000 S 17 +9 - +09 +Z Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 +5:53:20 - HMT 1870 +5:21:10 - MMT 1906 +5:30 - IST 1941 O +5:30 1 +0630 1942 May 15 +5:30 - IST 1942 S +5:30 1 +0630 1945 O 15 +5:30 - IST +Z Asia/Jakarta 7:7:12 - LMT 1867 Au 10 +7:7:12 - BMT 1923 D 31 23:47:12 +7:20 - +0720 1932 N +7:30 - +0730 1942 Mar 23 +9 - +09 1945 S 23 +7:30 - +0730 1948 May +8 - +08 1950 May +7:30 - +0730 1964 +7 - WIB +Z Asia/Pontianak 7:17:20 - LMT 1908 May +7:17:20 - PMT 1932 N +7:30 - +0730 1942 Ja 29 +9 - +09 1945 S 23 +7:30 - +0730 1948 May +8 - +08 1950 May +7:30 - +0730 1964 +8 - WITA 1988 +7 - WIB +Z Asia/Makassar 7:57:36 - LMT 1920 +7:57:36 - MMT 1932 N +8 - +08 1942 F 9 +9 - +09 1945 S 23 +8 - WITA +Z Asia/Jayapura 9:22:48 - LMT 1932 N +9 - +09 1944 S +9:30 - +0930 1964 +9 - WIT +R i 1978 1980 - Mar 20 24 1 - +R i 1978 o - O 20 24 0 - +R i 1979 o - S 18 24 0 - +R i 1980 o - S 22 24 0 - +R i 1991 o - May 2 24 1 - +R i 1992 1995 - Mar 21 24 1 - +R i 1991 1995 - S 21 24 0 - +R i 1996 o - Mar 20 24 1 - +R i 1996 o - S 20 24 0 - +R i 1997 1999 - Mar 21 24 1 - +R i 1997 1999 - S 21 24 0 - +R i 2000 o - Mar 20 24 1 - +R i 2000 o - S 20 24 0 - +R i 2001 2003 - Mar 21 24 1 - +R i 2001 2003 - S 21 24 0 - +R i 2004 o - Mar 20 24 1 - +R i 2004 o - S 20 24 0 - +R i 2005 o - Mar 21 24 1 - +R i 2005 o - S 21 24 0 - +R i 2008 o - Mar 20 24 1 - +R i 2008 o - S 20 24 0 - +R i 2009 2011 - Mar 21 24 1 - +R i 2009 2011 - S 21 24 0 - +R i 2012 o - Mar 20 24 1 - +R i 2012 o - S 20 24 0 - +R i 2013 2015 - Mar 21 24 1 - +R i 2013 2015 - S 21 24 0 - +R i 2016 o - Mar 20 24 1 - +R i 2016 o - S 20 24 0 - +R i 2017 2019 - Mar 21 24 1 - +R i 2017 2019 - S 21 24 0 - +R i 2020 o - Mar 20 24 1 - +R i 2020 o - S 20 24 0 - +R i 2021 2023 - Mar 21 24 1 - +R i 2021 2023 - S 21 24 0 - +R i 2024 o - Mar 20 24 1 - +R i 2024 o - S 20 24 0 - +R i 2025 2027 - Mar 21 24 1 - +R i 2025 2027 - S 21 24 0 - +R i 2028 2029 - Mar 20 24 1 - +R i 2028 2029 - S 20 24 0 - +R i 2030 2031 - Mar 21 24 1 - +R i 2030 2031 - S 21 24 0 - +R i 2032 2033 - Mar 20 24 1 - +R i 2032 2033 - S 20 24 0 - +R i 2034 2035 - Mar 21 24 1 - +R i 2034 2035 - S 21 24 0 - +R i 2036 2037 - Mar 20 24 1 - +R i 2036 2037 - S 20 24 0 - +R i 2038 2039 - Mar 21 24 1 - +R i 2038 2039 - S 21 24 0 - +R i 2040 2041 - Mar 20 24 1 - +R i 2040 2041 - S 20 24 0 - +R i 2042 2043 - Mar 21 24 1 - +R i 2042 2043 - S 21 24 0 - +R i 2044 2045 - Mar 20 24 1 - +R i 2044 2045 - S 20 24 0 - +R i 2046 2047 - Mar 21 24 1 - +R i 2046 2047 - S 21 24 0 - +R i 2048 2049 - Mar 20 24 1 - +R i 2048 2049 - S 20 24 0 - +R i 2050 2051 - Mar 21 24 1 - +R i 2050 2051 - S 21 24 0 - +R i 2052 2053 - Mar 20 24 1 - +R i 2052 2053 - S 20 24 0 - +R i 2054 2055 - Mar 21 24 1 - +R i 2054 2055 - S 21 24 0 - +R i 2056 2057 - Mar 20 24 1 - +R i 2056 2057 - S 20 24 0 - +R i 2058 2059 - Mar 21 24 1 - +R i 2058 2059 - S 21 24 0 - +R i 2060 2062 - Mar 20 24 1 - +R i 2060 2062 - S 20 24 0 - +R i 2063 o - Mar 21 24 1 - +R i 2063 o - S 21 24 0 - +R i 2064 2066 - Mar 20 24 1 - +R i 2064 2066 - S 20 24 0 - +R i 2067 o - Mar 21 24 1 - +R i 2067 o - S 21 24 0 - +R i 2068 2070 - Mar 20 24 1 - +R i 2068 2070 - S 20 24 0 - +R i 2071 o - Mar 21 24 1 - +R i 2071 o - S 21 24 0 - +R i 2072 2074 - Mar 20 24 1 - +R i 2072 2074 - S 20 24 0 - +R i 2075 o - Mar 21 24 1 - +R i 2075 o - S 21 24 0 - +R i 2076 2078 - Mar 20 24 1 - +R i 2076 2078 - S 20 24 0 - +R i 2079 o - Mar 21 24 1 - +R i 2079 o - S 21 24 0 - +R i 2080 2082 - Mar 20 24 1 - +R i 2080 2082 - S 20 24 0 - +R i 2083 o - Mar 21 24 1 - +R i 2083 o - S 21 24 0 - +R i 2084 2086 - Mar 20 24 1 - +R i 2084 2086 - S 20 24 0 - +R i 2087 o - Mar 21 24 1 - +R i 2087 o - S 21 24 0 - +R i 2088 ma - Mar 20 24 1 - +R i 2088 ma - S 20 24 0 - +Z Asia/Tehran 3:25:44 - LMT 1916 +3:25:44 - TMT 1946 +3:30 - +0330 1977 N +4 i +04/+05 1979 +3:30 i +0330/+0430 +R IQ 1982 o - May 1 0 1 - +R IQ 1982 1984 - O 1 0 0 - +R IQ 1983 o - Mar 31 0 1 - +R IQ 1984 1985 - Ap 1 0 1 - +R IQ 1985 1990 - S lastSu 1s 0 - +R IQ 1986 1990 - Mar lastSu 1s 1 - +R IQ 1991 2007 - Ap 1 3s 1 - +R IQ 1991 2007 - O 1 3s 0 - +Z Asia/Baghdad 2:57:40 - LMT 1890 +2:57:36 - BMT 1918 +3 - +03 1982 May +3 IQ +03/+04 +R Z 1940 o - May 31 24u 1 D +R Z 1940 o - S 30 24u 0 S +R Z 1940 o - N 16 24u 1 D +R Z 1942 1946 - O 31 24u 0 S +R Z 1943 1944 - Mar 31 24u 1 D +R Z 1945 1946 - Ap 15 24u 1 D +R Z 1948 o - May 22 24u 2 DD +R Z 1948 o - Au 31 24u 1 D +R Z 1948 1949 - O 31 24u 0 S +R Z 1949 o - Ap 30 24u 1 D +R Z 1950 o - Ap 15 24u 1 D +R Z 1950 o - S 14 24u 0 S +R Z 1951 o - Mar 31 24u 1 D +R Z 1951 o - N 10 24u 0 S +R Z 1952 o - Ap 19 24u 1 D +R Z 1952 o - O 18 24u 0 S +R Z 1953 o - Ap 11 24u 1 D +R Z 1953 o - S 12 24u 0 S +R Z 1954 o - Jun 12 24u 1 D +R Z 1954 o - S 11 24u 0 S +R Z 1955 o - Jun 11 24u 1 D +R Z 1955 o - S 10 24u 0 S +R Z 1956 o - Jun 2 24u 1 D +R Z 1956 o - S 29 24u 0 S +R Z 1957 o - Ap 27 24u 1 D +R Z 1957 o - S 21 24u 0 S +R Z 1974 o - Jul 6 24 1 D +R Z 1974 o - O 12 24 0 S +R Z 1975 o - Ap 19 24 1 D +R Z 1975 o - Au 30 24 0 S +R Z 1980 o - Au 2 24s 1 D +R Z 1980 o - S 13 24s 0 S +R Z 1984 o - May 5 24s 1 D +R Z 1984 o - Au 25 24s 0 S +R Z 1985 o - Ap 13 24 1 D +R Z 1985 o - Au 31 24 0 S +R Z 1986 o - May 17 24 1 D +R Z 1986 o - S 6 24 0 S +R Z 1987 o - Ap 14 24 1 D +R Z 1987 o - S 12 24 0 S +R Z 1988 o - Ap 9 24 1 D +R Z 1988 o - S 3 24 0 S +R Z 1989 o - Ap 29 24 1 D +R Z 1989 o - S 2 24 0 S +R Z 1990 o - Mar 24 24 1 D +R Z 1990 o - Au 25 24 0 S +R Z 1991 o - Mar 23 24 1 D +R Z 1991 o - Au 31 24 0 S +R Z 1992 o - Mar 28 24 1 D +R Z 1992 o - S 5 24 0 S +R Z 1993 o - Ap 2 0 1 D +R Z 1993 o - S 5 0 0 S +R Z 1994 o - Ap 1 0 1 D +R Z 1994 o - Au 28 0 0 S +R Z 1995 o - Mar 31 0 1 D +R Z 1995 o - S 3 0 0 S +R Z 1996 o - Mar 14 24 1 D +R Z 1996 o - S 15 24 0 S +R Z 1997 o - Mar 20 24 1 D +R Z 1997 o - S 13 24 0 S +R Z 1998 o - Mar 20 0 1 D +R Z 1998 o - S 6 0 0 S +R Z 1999 o - Ap 2 2 1 D +R Z 1999 o - S 3 2 0 S +R Z 2000 o - Ap 14 2 1 D +R Z 2000 o - O 6 1 0 S +R Z 2001 o - Ap 9 1 1 D +R Z 2001 o - S 24 1 0 S +R Z 2002 o - Mar 29 1 1 D +R Z 2002 o - O 7 1 0 S +R Z 2003 o - Mar 28 1 1 D +R Z 2003 o - O 3 1 0 S +R Z 2004 o - Ap 7 1 1 D +R Z 2004 o - S 22 1 0 S +R Z 2005 2012 - Ap F<=1 2 1 D +R Z 2005 o - O 9 2 0 S +R Z 2006 o - O 1 2 0 S +R Z 2007 o - S 16 2 0 S +R Z 2008 o - O 5 2 0 S +R Z 2009 o - S 27 2 0 S +R Z 2010 o - S 12 2 0 S +R Z 2011 o - O 2 2 0 S +R Z 2012 o - S 23 2 0 S +R Z 2013 ma - Mar F>=23 2 1 D +R Z 2013 ma - O lastSu 2 0 S +Z Asia/Jerusalem 2:20:54 - LMT 1880 +2:20:40 - JMT 1918 +2 Z I%sT +R JP 1948 o - May Sa>=1 24 1 D +R JP 1948 1951 - S Sa>=8 25 0 S +R JP 1949 o - Ap Sa>=1 24 1 D +R JP 1950 1951 - May Sa>=1 24 1 D +Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u +9 JP J%sT +R J 1973 o - Jun 6 0 1 S +R J 1973 1975 - O 1 0 0 - +R J 1974 1977 - May 1 0 1 S +R J 1976 o - N 1 0 0 - +R J 1977 o - O 1 0 0 - +R J 1978 o - Ap 30 0 1 S +R J 1978 o - S 30 0 0 - +R J 1985 o - Ap 1 0 1 S +R J 1985 o - O 1 0 0 - +R J 1986 1988 - Ap F>=1 0 1 S +R J 1986 1990 - O F>=1 0 0 - +R J 1989 o - May 8 0 1 S +R J 1990 o - Ap 27 0 1 S +R J 1991 o - Ap 17 0 1 S +R J 1991 o - S 27 0 0 - +R J 1992 o - Ap 10 0 1 S +R J 1992 1993 - O F>=1 0 0 - +R J 1993 1998 - Ap F>=1 0 1 S +R J 1994 o - S F>=15 0 0 - +R J 1995 1998 - S F>=15 0s 0 - +R J 1999 o - Jul 1 0s 1 S +R J 1999 2002 - S lastF 0s 0 - +R J 2000 2001 - Mar lastTh 0s 1 S +R J 2002 2012 - Mar lastTh 24 1 S +R J 2003 o - O 24 0s 0 - +R J 2004 o - O 15 0s 0 - +R J 2005 o - S lastF 0s 0 - +R J 2006 2011 - O lastF 0s 0 - +R J 2013 o - D 20 0 0 - +R J 2014 2021 - Mar lastTh 24 1 S +R J 2014 ma - O lastF 0s 0 - +R J 2022 ma - F lastTh 24 1 S +Z Asia/Amman 2:23:44 - LMT 1931 +2 J EE%sT +Z Asia/Almaty 5:7:48 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 2004 O 31 2s +6 - +06 +Z Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1991 S 29 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 1992 Mar 29 2s +5 R +05/+06 2004 O 31 2s +6 - +06 2018 D 21 +5 - +05 +Z Asia/Qostanay 4:14:28 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2004 O 31 2s +6 - +06 +Z Asia/Aqtobe 3:48:40 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2004 O 31 2s +5 - +05 +Z Asia/Aqtau 3:21:4 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1994 S 25 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +Z Asia/Atyrau 3:27:44 - LMT 1924 May 2 +3 - +03 1930 Jun 21 +5 - +05 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1999 Mar 28 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +Z Asia/Oral 3:25:24 - LMT 1924 May 2 +3 - +03 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1989 Mar 26 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1992 Mar 29 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +R KG 1992 1996 - Ap Su>=7 0s 1 - +R KG 1992 1996 - S lastSu 0 0 - +R KG 1997 2005 - Mar lastSu 2:30 1 - +R KG 1997 2004 - O lastSu 2:30 0 - +Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1991 Au 31 2 +5 KG +05/+06 2005 Au 12 +6 - +06 +R KR 1948 o - Jun 1 0 1 D +R KR 1948 o - S 12 24 0 S +R KR 1949 o - Ap 3 0 1 D +R KR 1949 1951 - S Sa>=7 24 0 S +R KR 1950 o - Ap 1 0 1 D +R KR 1951 o - May 6 0 1 D +R KR 1955 o - May 5 0 1 D +R KR 1955 o - S 8 24 0 S +R KR 1956 o - May 20 0 1 D +R KR 1956 o - S 29 24 0 S +R KR 1957 1960 - May Su>=1 0 1 D +R KR 1957 1960 - S Sa>=17 24 0 S +R KR 1987 1988 - May Su>=8 2 1 D +R KR 1987 1988 - O Su>=8 3 0 S +Z Asia/Seoul 8:27:52 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 S 8 +9 KR K%sT 1954 Mar 21 +8:30 KR K%sT 1961 Au 10 +9 KR K%sT +Z Asia/Pyongyang 8:23 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 Au 24 +9 - KST 2015 Au 15 +8:30 - KST 2018 May 4 23:30 +9 - KST +R l 1920 o - Mar 28 0 1 S +R l 1920 o - O 25 0 0 - +R l 1921 o - Ap 3 0 1 S +R l 1921 o - O 3 0 0 - +R l 1922 o - Mar 26 0 1 S +R l 1922 o - O 8 0 0 - +R l 1923 o - Ap 22 0 1 S +R l 1923 o - S 16 0 0 - +R l 1957 1961 - May 1 0 1 S +R l 1957 1961 - O 1 0 0 - +R l 1972 o - Jun 22 0 1 S +R l 1972 1977 - O 1 0 0 - +R l 1973 1977 - May 1 0 1 S +R l 1978 o - Ap 30 0 1 S +R l 1978 o - S 30 0 0 - +R l 1984 1987 - May 1 0 1 S +R l 1984 1991 - O 16 0 0 - +R l 1988 o - Jun 1 0 1 S +R l 1989 o - May 10 0 1 S +R l 1990 1992 - May 1 0 1 S +R l 1992 o - O 4 0 0 - +R l 1993 ma - Mar lastSu 0 1 S +R l 1993 1998 - S lastSu 0 0 - +R l 1999 ma - O lastSu 0 0 - +Z Asia/Beirut 2:22 - LMT 1880 +2 l EE%sT +R NB 1935 1941 - S 14 0 0:20 - +R NB 1935 1941 - D 14 0 0 - +Z Asia/Kuala_Lumpur 6:46:46 - LMT 1901 +6:55:25 - SMT 1905 Jun +7 - +07 1933 +7 0:20 +0720 1936 +7:20 - +0720 1941 S +7:30 - +0730 1942 F 16 +9 - +09 1945 S 12 +7:30 - +0730 1982 +8 - +08 +Z Asia/Kuching 7:21:20 - LMT 1926 Mar +7:30 - +0730 1933 +8 NB +08/+0820 1942 F 16 +9 - +09 1945 S 12 +8 - +08 +Z Indian/Maldives 4:54 - LMT 1880 +4:54 - MMT 1960 +5 - +05 +R X 1983 1984 - Ap 1 0 1 - +R X 1983 o - O 1 0 0 - +R X 1985 1998 - Mar lastSu 0 1 - +R X 1984 1998 - S lastSu 0 0 - +R X 2001 o - Ap lastSa 2 1 - +R X 2001 2006 - S lastSa 2 0 - +R X 2002 2006 - Mar lastSa 2 1 - +R X 2015 2016 - Mar lastSa 2 1 - +R X 2015 2016 - S lastSa 0 0 - +Z Asia/Hovd 6:6:36 - LMT 1905 Au +6 - +06 1978 +7 X +07/+08 +Z Asia/Ulaanbaatar 7:7:32 - LMT 1905 Au +7 - +07 1978 +8 X +08/+09 +Z Asia/Choibalsan 7:38 - LMT 1905 Au +7 - +07 1978 +8 - +08 1983 Ap +9 X +09/+10 2008 Mar 31 +8 X +08/+09 +Z Asia/Kathmandu 5:41:16 - LMT 1920 +5:30 - +0530 1986 +5:45 - +0545 +R PK 2002 o - Ap Su>=2 0 1 S +R PK 2002 o - O Su>=2 0 0 - +R PK 2008 o - Jun 1 0 1 S +R PK 2008 2009 - N 1 0 0 - +R PK 2009 o - Ap 15 0 1 S +Z Asia/Karachi 4:28:12 - LMT 1907 +5:30 - +0530 1942 S +5:30 1 +0630 1945 O 15 +5:30 - +0530 1951 S 30 +5 - +05 1971 Mar 26 +5 PK PK%sT +R P 1999 2005 - Ap F>=15 0 1 S +R P 1999 2003 - O F>=15 0 0 - +R P 2004 o - O 1 1 0 - +R P 2005 o - O 4 2 0 - +R P 2006 2007 - Ap 1 0 1 S +R P 2006 o - S 22 0 0 - +R P 2007 o - S 13 2 0 - +R P 2008 2009 - Mar lastF 0 1 S +R P 2008 o - S 1 0 0 - +R P 2009 o - S 4 1 0 - +R P 2010 o - Mar 26 0 1 S +R P 2010 o - Au 11 0 0 - +R P 2011 o - Ap 1 0:1 1 S +R P 2011 o - Au 1 0 0 - +R P 2011 o - Au 30 0 1 S +R P 2011 o - S 30 0 0 - +R P 2012 2014 - Mar lastTh 24 1 S +R P 2012 o - S 21 1 0 - +R P 2013 o - S 27 0 0 - +R P 2014 o - O 24 0 0 - +R P 2015 o - Mar 28 0 1 S +R P 2015 o - O 23 1 0 - +R P 2016 2018 - Mar Sa>=24 1 1 S +R P 2016 2018 - O Sa>=24 1 0 - +R P 2019 o - Mar 29 0 1 S +R P 2019 o - O Sa>=24 0 0 - +R P 2020 2021 - Mar Sa>=24 0 1 S +R P 2020 o - O 24 1 0 - +R P 2021 ma - O F>=23 1 0 - +R P 2022 ma - Mar Su>=25 0 1 S +Z Asia/Gaza 2:17:52 - LMT 1900 O +2 Z EET/EEST 1948 May 15 +2 K EE%sT 1967 Jun 5 +2 Z I%sT 1996 +2 J EE%sT 1999 +2 P EE%sT 2008 Au 29 +2 - EET 2008 S +2 P EE%sT 2010 +2 - EET 2010 Mar 27 0:1 +2 P EE%sT 2011 Au +2 - EET 2012 +2 P EE%sT +Z Asia/Hebron 2:20:23 - LMT 1900 O +2 Z EET/EEST 1948 May 15 +2 K EE%sT 1967 Jun 5 +2 Z I%sT 1996 +2 J EE%sT 1999 +2 P EE%sT +R PH 1936 o - N 1 0 1 D +R PH 1937 o - F 1 0 0 S +R PH 1954 o - Ap 12 0 1 D +R PH 1954 o - Jul 1 0 0 S +R PH 1978 o - Mar 22 0 1 D +R PH 1978 o - S 21 0 0 S +Z Asia/Manila -15:56 - LMT 1844 D 31 +8:4 - LMT 1899 May 11 +8 PH P%sT 1942 May +9 - JST 1944 N +8 PH P%sT +Z Asia/Qatar 3:26:8 - LMT 1920 +4 - +04 1972 Jun +3 - +03 +L Asia/Qatar Asia/Bahrain +Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 +3 - +03 +L Asia/Riyadh Antarctica/Syowa +L Asia/Riyadh Asia/Aden +L Asia/Riyadh Asia/Kuwait +Z Asia/Singapore 6:55:25 - LMT 1901 +6:55:25 - SMT 1905 Jun +7 - +07 1933 +7 0:20 +0720 1936 +7:20 - +0720 1941 S +7:30 - +0730 1942 F 16 +9 - +09 1945 S 12 +7:30 - +0730 1982 +8 - +08 +Z Asia/Colombo 5:19:24 - LMT 1880 +5:19:32 - MMT 1906 +5:30 - +0530 1942 Ja 5 +5:30 0:30 +06 1942 S +5:30 1 +0630 1945 O 16 2 +5:30 - +0530 1996 May 25 +6:30 - +0630 1996 O 26 0:30 +6 - +06 2006 Ap 15 0:30 +5:30 - +0530 +R S 1920 1923 - Ap Su>=15 2 1 S +R S 1920 1923 - O Su>=1 2 0 - +R S 1962 o - Ap 29 2 1 S +R S 1962 o - O 1 2 0 - +R S 1963 1965 - May 1 2 1 S +R S 1963 o - S 30 2 0 - +R S 1964 o - O 1 2 0 - +R S 1965 o - S 30 2 0 - +R S 1966 o - Ap 24 2 1 S +R S 1966 1976 - O 1 2 0 - +R S 1967 1978 - May 1 2 1 S +R S 1977 1978 - S 1 2 0 - +R S 1983 1984 - Ap 9 2 1 S +R S 1983 1984 - O 1 2 0 - +R S 1986 o - F 16 2 1 S +R S 1986 o - O 9 2 0 - +R S 1987 o - Mar 1 2 1 S +R S 1987 1988 - O 31 2 0 - +R S 1988 o - Mar 15 2 1 S +R S 1989 o - Mar 31 2 1 S +R S 1989 o - O 1 2 0 - +R S 1990 o - Ap 1 2 1 S +R S 1990 o - S 30 2 0 - +R S 1991 o - Ap 1 0 1 S +R S 1991 1992 - O 1 0 0 - +R S 1992 o - Ap 8 0 1 S +R S 1993 o - Mar 26 0 1 S +R S 1993 o - S 25 0 0 - +R S 1994 1996 - Ap 1 0 1 S +R S 1994 2005 - O 1 0 0 - +R S 1997 1998 - Mar lastM 0 1 S +R S 1999 2006 - Ap 1 0 1 S +R S 2006 o - S 22 0 0 - +R S 2007 o - Mar lastF 0 1 S +R S 2007 o - N F>=1 0 0 - +R S 2008 o - Ap F>=1 0 1 S +R S 2008 o - N 1 0 0 - +R S 2009 o - Mar lastF 0 1 S +R S 2010 2011 - Ap F>=1 0 1 S +R S 2012 ma - Mar lastF 0 1 S +R S 2009 ma - O lastF 0 0 - +Z Asia/Damascus 2:25:12 - LMT 1920 +2 S EE%sT +Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 1 +05/+06 1991 S 9 2s +5 - +05 +Z Asia/Bangkok 6:42:4 - LMT 1880 +6:42:4 - BMT 1920 Ap +7 - +07 +L Asia/Bangkok Asia/Phnom_Penh +L Asia/Bangkok Asia/Vientiane +Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 R +05/+06 1991 Mar 31 2 +4 R +04/+05 1992 Ja 19 2 +5 - +05 +Z Asia/Dubai 3:41:12 - LMT 1920 +4 - +04 +L Asia/Dubai Asia/Muscat +Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1992 +5 - +05 +Z Asia/Tashkent 4:37:11 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2 +5 R +05/+06 1992 +5 - +05 +Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul +7:6:30 - PLMT 1911 May +7 - +07 1942 D 31 23 +8 - +08 1945 Mar 14 23 +9 - +09 1945 S 2 +7 - +07 1947 Ap +8 - +08 1955 Jul +7 - +07 1959 D 31 23 +8 - +08 1975 Jun 13 +7 - +07 +R AU 1917 o - Ja 1 2s 1 D +R AU 1917 o - Mar lastSu 2s 0 S +R AU 1942 o - Ja 1 2s 1 D +R AU 1942 o - Mar lastSu 2s 0 S +R AU 1942 o - S 27 2s 1 D +R AU 1943 1944 - Mar lastSu 2s 0 S +R AU 1943 o - O 3 2s 1 D +Z Australia/Darwin 8:43:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT +R AW 1974 o - O lastSu 2s 1 D +R AW 1975 o - Mar Su>=1 2s 0 S +R AW 1983 o - O lastSu 2s 1 D +R AW 1984 o - Mar Su>=1 2s 0 S +R AW 1991 o - N 17 2s 1 D +R AW 1992 o - Mar Su>=1 2s 0 S +R AW 2006 o - D 3 2s 1 D +R AW 2007 2009 - Mar lastSu 2s 0 S +R AW 2007 2008 - O lastSu 2s 1 D +Z Australia/Perth 7:43:24 - LMT 1895 D +8 AU AW%sT 1943 Jul +8 AW AW%sT +Z Australia/Eucla 8:35:28 - LMT 1895 D +8:45 AU +0845/+0945 1943 Jul +8:45 AW +0845/+0945 +R AQ 1971 o - O lastSu 2s 1 D +R AQ 1972 o - F lastSu 2s 0 S +R AQ 1989 1991 - O lastSu 2s 1 D +R AQ 1990 1992 - Mar Su>=1 2s 0 S +R Ho 1992 1993 - O lastSu 2s 1 D +R Ho 1993 1994 - Mar Su>=1 2s 0 S +Z Australia/Brisbane 10:12:8 - LMT 1895 +10 AU AE%sT 1971 +10 AQ AE%sT +Z Australia/Lindeman 9:55:56 - LMT 1895 +10 AU AE%sT 1971 +10 AQ AE%sT 1992 Jul +10 Ho AE%sT +R AS 1971 1985 - O lastSu 2s 1 D +R AS 1986 o - O 19 2s 1 D +R AS 1987 2007 - O lastSu 2s 1 D +R AS 1972 o - F 27 2s 0 S +R AS 1973 1985 - Mar Su>=1 2s 0 S +R AS 1986 1990 - Mar Su>=15 2s 0 S +R AS 1991 o - Mar 3 2s 0 S +R AS 1992 o - Mar 22 2s 0 S +R AS 1993 o - Mar 7 2s 0 S +R AS 1994 o - Mar 20 2s 0 S +R AS 1995 2005 - Mar lastSu 2s 0 S +R AS 2006 o - Ap 2 2s 0 S +R AS 2007 o - Mar lastSu 2s 0 S +R AS 2008 ma - Ap Su>=1 2s 0 S +R AS 2008 ma - O Su>=1 2s 1 D +Z Australia/Adelaide 9:14:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AS AC%sT +R AT 1916 o - O Su>=1 2s 1 D +R AT 1917 o - Mar lastSu 2s 0 S +R AT 1917 1918 - O Su>=22 2s 1 D +R AT 1918 1919 - Mar Su>=1 2s 0 S +R AT 1967 o - O Su>=1 2s 1 D +R AT 1968 o - Mar Su>=29 2s 0 S +R AT 1968 1985 - O lastSu 2s 1 D +R AT 1969 1971 - Mar Su>=8 2s 0 S +R AT 1972 o - F lastSu 2s 0 S +R AT 1973 1981 - Mar Su>=1 2s 0 S +R AT 1982 1983 - Mar lastSu 2s 0 S +R AT 1984 1986 - Mar Su>=1 2s 0 S +R AT 1986 o - O Su>=15 2s 1 D +R AT 1987 1990 - Mar Su>=15 2s 0 S +R AT 1987 o - O Su>=22 2s 1 D +R AT 1988 1990 - O lastSu 2s 1 D +R AT 1991 1999 - O Su>=1 2s 1 D +R AT 1991 2005 - Mar lastSu 2s 0 S +R AT 2000 o - Au lastSu 2s 1 D +R AT 2001 ma - O Su>=1 2s 1 D +R AT 2006 o - Ap Su>=1 2s 0 S +R AT 2007 o - Mar lastSu 2s 0 S +R AT 2008 ma - Ap Su>=1 2s 0 S +Z Australia/Hobart 9:49:16 - LMT 1895 S +10 AT AE%sT 1919 O 24 +10 AU AE%sT 1967 +10 AT AE%sT +R AV 1971 1985 - O lastSu 2s 1 D +R AV 1972 o - F lastSu 2s 0 S +R AV 1973 1985 - Mar Su>=1 2s 0 S +R AV 1986 1990 - Mar Su>=15 2s 0 S +R AV 1986 1987 - O Su>=15 2s 1 D +R AV 1988 1999 - O lastSu 2s 1 D +R AV 1991 1994 - Mar Su>=1 2s 0 S +R AV 1995 2005 - Mar lastSu 2s 0 S +R AV 2000 o - Au lastSu 2s 1 D +R AV 2001 2007 - O lastSu 2s 1 D +R AV 2006 o - Ap Su>=1 2s 0 S +R AV 2007 o - Mar lastSu 2s 0 S +R AV 2008 ma - Ap Su>=1 2s 0 S +R AV 2008 ma - O Su>=1 2s 1 D +Z Australia/Melbourne 9:39:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AV AE%sT +R AN 1971 1985 - O lastSu 2s 1 D +R AN 1972 o - F 27 2s 0 S +R AN 1973 1981 - Mar Su>=1 2s 0 S +R AN 1982 o - Ap Su>=1 2s 0 S +R AN 1983 1985 - Mar Su>=1 2s 0 S +R AN 1986 1989 - Mar Su>=15 2s 0 S +R AN 1986 o - O 19 2s 1 D +R AN 1987 1999 - O lastSu 2s 1 D +R AN 1990 1995 - Mar Su>=1 2s 0 S +R AN 1996 2005 - Mar lastSu 2s 0 S +R AN 2000 o - Au lastSu 2s 1 D +R AN 2001 2007 - O lastSu 2s 1 D +R AN 2006 o - Ap Su>=1 2s 0 S +R AN 2007 o - Mar lastSu 2s 0 S +R AN 2008 ma - Ap Su>=1 2s 0 S +R AN 2008 ma - O Su>=1 2s 1 D +Z Australia/Sydney 10:4:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AN AE%sT +Z Australia/Broken_Hill 9:25:48 - LMT 1895 F +10 - AEST 1896 Au 23 +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AN AC%sT 2000 +9:30 AS AC%sT +R LH 1981 1984 - O lastSu 2 1 - +R LH 1982 1985 - Mar Su>=1 2 0 - +R LH 1985 o - O lastSu 2 0:30 - +R LH 1986 1989 - Mar Su>=15 2 0 - +R LH 1986 o - O 19 2 0:30 - +R LH 1987 1999 - O lastSu 2 0:30 - +R LH 1990 1995 - Mar Su>=1 2 0 - +R LH 1996 2005 - Mar lastSu 2 0 - +R LH 2000 o - Au lastSu 2 0:30 - +R LH 2001 2007 - O lastSu 2 0:30 - +R LH 2006 o - Ap Su>=1 2 0 - +R LH 2007 o - Mar lastSu 2 0 - +R LH 2008 ma - Ap Su>=1 2 0 - +R LH 2008 ma - O Su>=1 2 0:30 - +Z Australia/Lord_Howe 10:36:20 - LMT 1895 F +10 - AEST 1981 Mar +10:30 LH +1030/+1130 1985 Jul +10:30 LH +1030/+11 +Z Antarctica/Macquarie 0 - -00 1899 N +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1919 Ap 1 0s +0 - -00 1948 Mar 25 +10 AU AE%sT 1967 +10 AT AE%sT 2010 +10 1 AEDT 2011 +10 AT AE%sT +Z Indian/Christmas 7:2:52 - LMT 1895 F +7 - +07 +Z Indian/Cocos 6:27:40 - LMT 1900 +6:30 - +0630 +R FJ 1998 1999 - N Su>=1 2 1 - +R FJ 1999 2000 - F lastSu 3 0 - +R FJ 2009 o - N 29 2 1 - +R FJ 2010 o - Mar lastSu 3 0 - +R FJ 2010 2013 - O Su>=21 2 1 - +R FJ 2011 o - Mar Su>=1 3 0 - +R FJ 2012 2013 - Ja Su>=18 3 0 - +R FJ 2014 o - Ja Su>=18 2 0 - +R FJ 2014 2018 - N Su>=1 2 1 - +R FJ 2015 2021 - Ja Su>=12 3 0 - +R FJ 2019 o - N Su>=8 2 1 - +R FJ 2020 o - D 20 2 1 - +R FJ 2022 ma - N Su>=8 2 1 - +R FJ 2023 ma - Ja Su>=12 3 0 - +Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 +12 FJ +12/+13 +Z Pacific/Gambier -8:59:48 - LMT 1912 O +-9 - -09 +Z Pacific/Marquesas -9:18 - LMT 1912 O +-9:30 - -0930 +Z Pacific/Tahiti -9:58:16 - LMT 1912 O +-10 - -10 +R Gu 1959 o - Jun 27 2 1 D +R Gu 1961 o - Ja 29 2 0 S +R Gu 1967 o - S 1 2 1 D +R Gu 1969 o - Ja 26 0:1 0 S +R Gu 1969 o - Jun 22 2 1 D +R Gu 1969 o - Au 31 2 0 S +R Gu 1970 1971 - Ap lastSu 2 1 D +R Gu 1970 1971 - S Su>=1 2 0 S +R Gu 1973 o - D 16 2 1 D +R Gu 1974 o - F 24 2 0 S +R Gu 1976 o - May 26 2 1 D +R Gu 1976 o - Au 22 2:1 0 S +R Gu 1977 o - Ap 24 2 1 D +R Gu 1977 o - Au 28 2 0 S +Z Pacific/Guam -14:21 - LMT 1844 D 31 +9:39 - LMT 1901 +10 - GST 1941 D 10 +9 - +09 1944 Jul 31 +10 Gu G%sT 2000 D 23 +10 - ChST +L Pacific/Guam Pacific/Saipan +Z Pacific/Tarawa 11:32:4 - LMT 1901 +12 - +12 +Z Pacific/Kanton 0 - -00 1937 Au 31 +-12 - -12 1979 O +-11 - -11 1994 D 31 +13 - +13 +Z Pacific/Kiritimati -10:29:20 - LMT 1901 +-10:40 - -1040 1979 O +-10 - -10 1994 D 31 +14 - +14 +Z Pacific/Majuro 11:24:48 - LMT 1901 +11 - +11 1914 O +9 - +09 1919 F +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1944 Ja 30 +11 - +11 1969 O +12 - +12 +Z Pacific/Kwajalein 11:9:20 - LMT 1901 +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1944 F 6 +11 - +11 1969 O +-12 - -12 1993 Au 20 24 +12 - +12 +Z Pacific/Chuuk -13:52:52 - LMT 1844 D 31 +10:7:8 - LMT 1901 +10 - +10 1914 O +9 - +09 1919 F +10 - +10 1941 Ap +9 - +09 1945 Au +10 - +10 +Z Pacific/Pohnpei -13:27:8 - LMT 1844 D 31 +10:32:52 - LMT 1901 +11 - +11 1914 O +9 - +09 1919 F +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1945 Au +11 - +11 +Z Pacific/Kosrae -13:8:4 - LMT 1844 D 31 +10:51:56 - LMT 1901 +11 - +11 1914 O +9 - +09 1919 F +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1945 Au +11 - +11 1969 O +12 - +12 1999 +11 - +11 +Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 +11:30 - +1130 1942 Au 29 +9 - +09 1945 S 8 +11:30 - +1130 1979 F 10 2 +12 - +12 +R NC 1977 1978 - D Su>=1 0 1 - +R NC 1978 1979 - F 27 0 0 - +R NC 1996 o - D 1 2s 1 - +R NC 1997 o - Mar 2 2s 0 - +Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 +11 NC +11/+12 +R NZ 1927 o - N 6 2 1 S +R NZ 1928 o - Mar 4 2 0 M +R NZ 1928 1933 - O Su>=8 2 0:30 S +R NZ 1929 1933 - Mar Su>=15 2 0 M +R NZ 1934 1940 - Ap lastSu 2 0 M +R NZ 1934 1940 - S lastSu 2 0:30 S +R NZ 1946 o - Ja 1 0 0 S +R NZ 1974 o - N Su>=1 2s 1 D +R k 1974 o - N Su>=1 2:45s 1 - +R NZ 1975 o - F lastSu 2s 0 S +R k 1975 o - F lastSu 2:45s 0 - +R NZ 1975 1988 - O lastSu 2s 1 D +R k 1975 1988 - O lastSu 2:45s 1 - +R NZ 1976 1989 - Mar Su>=1 2s 0 S +R k 1976 1989 - Mar Su>=1 2:45s 0 - +R NZ 1989 o - O Su>=8 2s 1 D +R k 1989 o - O Su>=8 2:45s 1 - +R NZ 1990 2006 - O Su>=1 2s 1 D +R k 1990 2006 - O Su>=1 2:45s 1 - +R NZ 1990 2007 - Mar Su>=15 2s 0 S +R k 1990 2007 - Mar Su>=15 2:45s 0 - +R NZ 2007 ma - S lastSu 2s 1 D +R k 2007 ma - S lastSu 2:45s 1 - +R NZ 2008 ma - Ap Su>=1 2s 0 S +R k 2008 ma - Ap Su>=1 2:45s 0 - +Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 +11:30 NZ NZ%sT 1946 +12 NZ NZ%sT +Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 +12:15 - +1215 1946 +12:45 k +1245/+1345 +L Pacific/Auckland Antarctica/McMurdo +R CK 1978 o - N 12 0 0:30 - +R CK 1979 1991 - Mar Su>=1 0 0 - +R CK 1979 1990 - O lastSu 0 0:30 - +Z Pacific/Rarotonga 13:20:56 - LMT 1899 D 26 +-10:39:4 - LMT 1952 O 16 +-10:30 - -1030 1978 N 12 +-10 CK -10/-0930 +Z Pacific/Niue -11:19:40 - LMT 1952 O 16 +-11:20 - -1120 1964 Jul +-11 - -11 +Z Pacific/Norfolk 11:11:52 - LMT 1901 +11:12 - +1112 1951 +11:30 - +1130 1974 O 27 2s +11:30 1 +1230 1975 Mar 2 2s +11:30 - +1130 2015 O 4 2s +11 - +11 2019 Jul +11 AN +11/+12 +Z Pacific/Palau -15:2:4 - LMT 1844 D 31 +8:57:56 - LMT 1901 +9 - +09 +Z Pacific/Port_Moresby 9:48:40 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 +L Pacific/Port_Moresby Antarctica/DumontDUrville +Z Pacific/Bougainville 10:22:16 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 1942 Jul +9 - +09 1945 Au 21 +10 - +10 2014 D 28 2 +11 - +11 +Z Pacific/Pitcairn -8:40:20 - LMT 1901 +-8:30 - -0830 1998 Ap 27 +-8 - -08 +Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 +-11:22:48 - LMT 1911 +-11 - SST +L Pacific/Pago_Pago Pacific/Midway +R WS 2010 o - S lastSu 0 1 - +R WS 2011 o - Ap Sa>=1 4 0 - +R WS 2011 o - S lastSa 3 1 - +R WS 2012 2021 - Ap Su>=1 4 0 - +R WS 2012 2020 - S lastSu 3 1 - +Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5 +-11:26:56 - LMT 1911 +-11:30 - -1130 1950 +-11 WS -11/-10 2011 D 29 24 +13 WS +13/+14 +Z Pacific/Guadalcanal 10:39:48 - LMT 1912 O +11 - +11 +Z Pacific/Fakaofo -11:24:56 - LMT 1901 +-11 - -11 2011 D 30 +13 - +13 +R TO 1999 o - O 7 2s 1 - +R TO 2000 o - Mar 19 2s 0 - +R TO 2000 2001 - N Su>=1 2 1 - +R TO 2001 2002 - Ja lastSu 2 0 - +R TO 2016 o - N Su>=1 2 1 - +R TO 2017 o - Ja Su>=15 3 0 - +Z Pacific/Tongatapu 12:19:12 - LMT 1945 S 10 +12:20 - +1220 1961 +13 - +13 1999 +13 TO +13/+14 +Z Pacific/Funafuti 11:56:52 - LMT 1901 +12 - +12 +Z Pacific/Wake 11:6:28 - LMT 1901 +12 - +12 +R VU 1973 o - D 22 12u 1 - +R VU 1974 o - Mar 30 12u 0 - +R VU 1983 1991 - S Sa>=22 24 1 - +R VU 1984 1991 - Mar Sa>=22 24 0 - +R VU 1992 1993 - Ja Sa>=22 24 0 - +R VU 1992 o - O Sa>=22 24 1 - +Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 +11 VU +11/+12 +Z Pacific/Wallis 12:15:20 - LMT 1901 +12 - +12 +R G 1916 o - May 21 2s 1 BST +R G 1916 o - O 1 2s 0 GMT +R G 1917 o - Ap 8 2s 1 BST +R G 1917 o - S 17 2s 0 GMT +R G 1918 o - Mar 24 2s 1 BST +R G 1918 o - S 30 2s 0 GMT +R G 1919 o - Mar 30 2s 1 BST +R G 1919 o - S 29 2s 0 GMT +R G 1920 o - Mar 28 2s 1 BST +R G 1920 o - O 25 2s 0 GMT +R G 1921 o - Ap 3 2s 1 BST +R G 1921 o - O 3 2s 0 GMT +R G 1922 o - Mar 26 2s 1 BST +R G 1922 o - O 8 2s 0 GMT +R G 1923 o - Ap Su>=16 2s 1 BST +R G 1923 1924 - S Su>=16 2s 0 GMT +R G 1924 o - Ap Su>=9 2s 1 BST +R G 1925 1926 - Ap Su>=16 2s 1 BST +R G 1925 1938 - O Su>=2 2s 0 GMT +R G 1927 o - Ap Su>=9 2s 1 BST +R G 1928 1929 - Ap Su>=16 2s 1 BST +R G 1930 o - Ap Su>=9 2s 1 BST +R G 1931 1932 - Ap Su>=16 2s 1 BST +R G 1933 o - Ap Su>=9 2s 1 BST +R G 1934 o - Ap Su>=16 2s 1 BST +R G 1935 o - Ap Su>=9 2s 1 BST +R G 1936 1937 - Ap Su>=16 2s 1 BST +R G 1938 o - Ap Su>=9 2s 1 BST +R G 1939 o - Ap Su>=16 2s 1 BST +R G 1939 o - N Su>=16 2s 0 GMT +R G 1940 o - F Su>=23 2s 1 BST +R G 1941 o - May Su>=2 1s 2 BDST +R G 1941 1943 - Au Su>=9 1s 1 BST +R G 1942 1944 - Ap Su>=2 1s 2 BDST +R G 1944 o - S Su>=16 1s 1 BST +R G 1945 o - Ap M>=2 1s 2 BDST +R G 1945 o - Jul Su>=9 1s 1 BST +R G 1945 1946 - O Su>=2 2s 0 GMT +R G 1946 o - Ap Su>=9 2s 1 BST +R G 1947 o - Mar 16 2s 1 BST +R G 1947 o - Ap 13 1s 2 BDST +R G 1947 o - Au 10 1s 1 BST +R G 1947 o - N 2 2s 0 GMT +R G 1948 o - Mar 14 2s 1 BST +R G 1948 o - O 31 2s 0 GMT +R G 1949 o - Ap 3 2s 1 BST +R G 1949 o - O 30 2s 0 GMT +R G 1950 1952 - Ap Su>=14 2s 1 BST +R G 1950 1952 - O Su>=21 2s 0 GMT +R G 1953 o - Ap Su>=16 2s 1 BST +R G 1953 1960 - O Su>=2 2s 0 GMT +R G 1954 o - Ap Su>=9 2s 1 BST +R G 1955 1956 - Ap Su>=16 2s 1 BST +R G 1957 o - Ap Su>=9 2s 1 BST +R G 1958 1959 - Ap Su>=16 2s 1 BST +R G 1960 o - Ap Su>=9 2s 1 BST +R G 1961 1963 - Mar lastSu 2s 1 BST +R G 1961 1968 - O Su>=23 2s 0 GMT +R G 1964 1967 - Mar Su>=19 2s 1 BST +R G 1968 o - F 18 2s 1 BST +R G 1972 1980 - Mar Su>=16 2s 1 BST +R G 1972 1980 - O Su>=23 2s 0 GMT +R G 1981 1995 - Mar lastSu 1u 1 BST +R G 1981 1989 - O Su>=23 1u 0 GMT +R G 1990 1995 - O Su>=22 1u 0 GMT +Z Europe/London -0:1:15 - LMT 1847 D 1 0s +0 G %s 1968 O 27 +1 - BST 1971 O 31 2u +0 G %s 1996 +0 E GMT/BST +L Europe/London Europe/Jersey +L Europe/London Europe/Guernsey +L Europe/London Europe/Isle_of_Man +R IE 1971 o - O 31 2u -1 - +R IE 1972 1980 - Mar Su>=16 2u 0 - +R IE 1972 1980 - O Su>=23 2u -1 - +R IE 1981 ma - Mar lastSu 1u 0 - +R IE 1981 1989 - O Su>=23 1u -1 - +R IE 1990 1995 - O Su>=22 1u -1 - +R IE 1996 ma - O lastSu 1u -1 - +Z Europe/Dublin -0:25 - LMT 1880 Au 2 +-0:25:21 - DMT 1916 May 21 2s +-0:25:21 1 IST 1916 O 1 2s +0 G %s 1921 D 6 +0 G GMT/IST 1940 F 25 2s +0 1 IST 1946 O 6 2s +0 - GMT 1947 Mar 16 2s +0 1 IST 1947 N 2 2s +0 - GMT 1948 Ap 18 2s +0 G GMT/IST 1968 O 27 +1 IE IST/GMT +R E 1977 1980 - Ap Su>=1 1u 1 S +R E 1977 o - S lastSu 1u 0 - +R E 1978 o - O 1 1u 0 - +R E 1979 1995 - S lastSu 1u 0 - +R E 1981 ma - Mar lastSu 1u 1 S +R E 1996 ma - O lastSu 1u 0 - +R W- 1977 1980 - Ap Su>=1 1s 1 S +R W- 1977 o - S lastSu 1s 0 - +R W- 1978 o - O 1 1s 0 - +R W- 1979 1995 - S lastSu 1s 0 - +R W- 1981 ma - Mar lastSu 1s 1 S +R W- 1996 ma - O lastSu 1s 0 - +R c 1916 o - Ap 30 23 1 S +R c 1916 o - O 1 1 0 - +R c 1917 1918 - Ap M>=15 2s 1 S +R c 1917 1918 - S M>=15 2s 0 - +R c 1940 o - Ap 1 2s 1 S +R c 1942 o - N 2 2s 0 - +R c 1943 o - Mar 29 2s 1 S +R c 1943 o - O 4 2s 0 - +R c 1944 1945 - Ap M>=1 2s 1 S +R c 1944 o - O 2 2s 0 - +R c 1945 o - S 16 2s 0 - +R c 1977 1980 - Ap Su>=1 2s 1 S +R c 1977 o - S lastSu 2s 0 - +R c 1978 o - O 1 2s 0 - +R c 1979 1995 - S lastSu 2s 0 - +R c 1981 ma - Mar lastSu 2s 1 S +R c 1996 ma - O lastSu 2s 0 - +R e 1977 1980 - Ap Su>=1 0 1 S +R e 1977 o - S lastSu 0 0 - +R e 1978 o - O 1 0 0 - +R e 1979 1995 - S lastSu 0 0 - +R e 1981 ma - Mar lastSu 0 1 S +R e 1996 ma - O lastSu 0 0 - +R R 1917 o - Jul 1 23 1 MST +R R 1917 o - D 28 0 0 MMT +R R 1918 o - May 31 22 2 MDST +R R 1918 o - S 16 1 1 MST +R R 1919 o - May 31 23 2 MDST +R R 1919 o - Jul 1 0u 1 MSD +R R 1919 o - Au 16 0 0 MSK +R R 1921 o - F 14 23 1 MSD +R R 1921 o - Mar 20 23 2 +05 +R R 1921 o - S 1 0 1 MSD +R R 1921 o - O 1 0 0 - +R R 1981 1984 - Ap 1 0 1 S +R R 1981 1983 - O 1 0 0 - +R R 1984 1995 - S lastSu 2s 0 - +R R 1985 2010 - Mar lastSu 2s 1 S +R R 1996 2010 - O lastSu 2s 0 - +Z WET 0 E WE%sT +Z CET 1 c CE%sT +Z MET 1 c ME%sT +Z EET 2 E EE%sT +R q 1940 o - Jun 16 0 1 S +R q 1942 o - N 2 3 0 - +R q 1943 o - Mar 29 2 1 S +R q 1943 o - Ap 10 3 0 - +R q 1974 o - May 4 0 1 S +R q 1974 o - O 2 0 0 - +R q 1975 o - May 1 0 1 S +R q 1975 o - O 2 0 0 - +R q 1976 o - May 2 0 1 S +R q 1976 o - O 3 0 0 - +R q 1977 o - May 8 0 1 S +R q 1977 o - O 2 0 0 - +R q 1978 o - May 6 0 1 S +R q 1978 o - O 1 0 0 - +R q 1979 o - May 5 0 1 S +R q 1979 o - S 30 0 0 - +R q 1980 o - May 3 0 1 S +R q 1980 o - O 4 0 0 - +R q 1981 o - Ap 26 0 1 S +R q 1981 o - S 27 0 0 - +R q 1982 o - May 2 0 1 S +R q 1982 o - O 3 0 0 - +R q 1983 o - Ap 18 0 1 S +R q 1983 o - O 1 0 0 - +R q 1984 o - Ap 1 0 1 S +Z Europe/Tirane 1:19:20 - LMT 1914 +1 - CET 1940 Jun 16 +1 q CE%sT 1984 Jul +1 E CE%sT +Z Europe/Andorra 0:6:4 - LMT 1901 +0 - WET 1946 S 30 +1 - CET 1985 Mar 31 2 +1 E CE%sT +R a 1920 o - Ap 5 2s 1 S +R a 1920 o - S 13 2s 0 - +R a 1946 o - Ap 14 2s 1 S +R a 1946 o - O 7 2s 0 - +R a 1947 1948 - O Su>=1 2s 0 - +R a 1947 o - Ap 6 2s 1 S +R a 1948 o - Ap 18 2s 1 S +R a 1980 o - Ap 6 0 1 S +R a 1980 o - S 28 0 0 - +Z Europe/Vienna 1:5:21 - LMT 1893 Ap +1 c CE%sT 1920 +1 a CE%sT 1940 Ap 1 2s +1 c CE%sT 1945 Ap 2 2s +1 1 CEST 1945 Ap 12 2s +1 - CET 1946 +1 a CE%sT 1981 +1 E CE%sT +Z Europe/Minsk 1:50:16 - LMT 1880 +1:50 - MMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 Jun 28 +1 c CE%sT 1944 Jul 3 +3 R MSK/MSD 1990 +3 - MSK 1991 Mar 31 2s +2 R EE%sT 2011 Mar 27 2s +3 - +03 +R b 1918 o - Mar 9 0s 1 S +R b 1918 1919 - O Sa>=1 23s 0 - +R b 1919 o - Mar 1 23s 1 S +R b 1920 o - F 14 23s 1 S +R b 1920 o - O 23 23s 0 - +R b 1921 o - Mar 14 23s 1 S +R b 1921 o - O 25 23s 0 - +R b 1922 o - Mar 25 23s 1 S +R b 1922 1927 - O Sa>=1 23s 0 - +R b 1923 o - Ap 21 23s 1 S +R b 1924 o - Mar 29 23s 1 S +R b 1925 o - Ap 4 23s 1 S +R b 1926 o - Ap 17 23s 1 S +R b 1927 o - Ap 9 23s 1 S +R b 1928 o - Ap 14 23s 1 S +R b 1928 1938 - O Su>=2 2s 0 - +R b 1929 o - Ap 21 2s 1 S +R b 1930 o - Ap 13 2s 1 S +R b 1931 o - Ap 19 2s 1 S +R b 1932 o - Ap 3 2s 1 S +R b 1933 o - Mar 26 2s 1 S +R b 1934 o - Ap 8 2s 1 S +R b 1935 o - Mar 31 2s 1 S +R b 1936 o - Ap 19 2s 1 S +R b 1937 o - Ap 4 2s 1 S +R b 1938 o - Mar 27 2s 1 S +R b 1939 o - Ap 16 2s 1 S +R b 1939 o - N 19 2s 0 - +R b 1940 o - F 25 2s 1 S +R b 1944 o - S 17 2s 0 - +R b 1945 o - Ap 2 2s 1 S +R b 1945 o - S 16 2s 0 - +R b 1946 o - May 19 2s 1 S +R b 1946 o - O 7 2s 0 - +Z Europe/Brussels 0:17:30 - LMT 1880 +0:17:30 - BMT 1892 May 1 0:17:30 +0 - WET 1914 N 8 +1 - CET 1916 May +1 c CE%sT 1918 N 11 11u +0 b WE%sT 1940 May 20 2s +1 c CE%sT 1944 S 3 +1 b CE%sT 1977 +1 E CE%sT +R BG 1979 o - Mar 31 23 1 S +R BG 1979 o - O 1 1 0 - +R BG 1980 1982 - Ap Sa>=1 23 1 S +R BG 1980 o - S 29 1 0 - +R BG 1981 o - S 27 2 0 - +Z Europe/Sofia 1:33:16 - LMT 1880 +1:56:56 - IMT 1894 N 30 +2 - EET 1942 N 2 3 +1 c CE%sT 1945 +1 - CET 1945 Ap 2 3 +2 - EET 1979 Mar 31 23 +2 BG EE%sT 1982 S 26 3 +2 c EE%sT 1991 +2 e EE%sT 1997 +2 E EE%sT +R CZ 1945 o - Ap M>=1 2s 1 S +R CZ 1945 o - O 1 2s 0 - +R CZ 1946 o - May 6 2s 1 S +R CZ 1946 1949 - O Su>=1 2s 0 - +R CZ 1947 1948 - Ap Su>=15 2s 1 S +R CZ 1949 o - Ap 9 2s 1 S +Z Europe/Prague 0:57:44 - LMT 1850 +0:57:44 - PMT 1891 O +1 c CE%sT 1945 May 9 +1 CZ CE%sT 1946 D 1 3 +1 -1 GMT 1947 F 23 2 +1 CZ CE%sT 1979 +1 E CE%sT +R D 1916 o - May 14 23 1 S +R D 1916 o - S 30 23 0 - +R D 1940 o - May 15 0 1 S +R D 1945 o - Ap 2 2s 1 S +R D 1945 o - Au 15 2s 0 - +R D 1946 o - May 1 2s 1 S +R D 1946 o - S 1 2s 0 - +R D 1947 o - May 4 2s 1 S +R D 1947 o - Au 10 2s 0 - +R D 1948 o - May 9 2s 1 S +R D 1948 o - Au 8 2s 0 - +Z Europe/Copenhagen 0:50:20 - LMT 1890 +0:50:20 - CMT 1894 +1 D CE%sT 1942 N 2 2s +1 c CE%sT 1945 Ap 2 2 +1 D CE%sT 1980 +1 E CE%sT +Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 +0 - WET 1981 +0 E WE%sT +R Th 1991 1992 - Mar lastSu 2 1 D +R Th 1991 1992 - S lastSu 2 0 S +R Th 1993 2006 - Ap Su>=1 2 1 D +R Th 1993 2006 - O lastSu 2 0 S +R Th 2007 ma - Mar Su>=8 2 1 D +R Th 2007 ma - N Su>=1 2 0 S +Z America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 1996 +0 - GMT +Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 +-2 - -02 1980 Ap 6 2 +-2 c -02/-01 1981 Mar 29 +-1 E -01/+00 +Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 +Z America/Thule -4:35:8 - LMT 1916 Jul 28 +-4 Th A%sT +Z Europe/Tallinn 1:39 - LMT 1880 +1:39 - TMT 1918 F +1 c CE%sT 1919 Jul +1:39 - TMT 1921 May +2 - EET 1940 Au 6 +3 - MSK 1941 S 15 +1 c CE%sT 1944 S 22 +3 R MSK/MSD 1989 Mar 26 2s +2 1 EEST 1989 S 24 2s +2 c EE%sT 1998 S 22 +2 E EE%sT 1999 O 31 4 +2 - EET 2002 F 21 +2 E EE%sT +R FI 1942 o - Ap 2 24 1 S +R FI 1942 o - O 4 1 0 - +R FI 1981 1982 - Mar lastSu 2 1 S +R FI 1981 1982 - S lastSu 3 0 - +Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 +1:39:49 - HMT 1921 May +2 FI EE%sT 1983 +2 E EE%sT +L Europe/Helsinki Europe/Mariehamn +R F 1916 o - Jun 14 23s 1 S +R F 1916 1919 - O Su>=1 23s 0 - +R F 1917 o - Mar 24 23s 1 S +R F 1918 o - Mar 9 23s 1 S +R F 1919 o - Mar 1 23s 1 S +R F 1920 o - F 14 23s 1 S +R F 1920 o - O 23 23s 0 - +R F 1921 o - Mar 14 23s 1 S +R F 1921 o - O 25 23s 0 - +R F 1922 o - Mar 25 23s 1 S +R F 1922 1938 - O Sa>=1 23s 0 - +R F 1923 o - May 26 23s 1 S +R F 1924 o - Mar 29 23s 1 S +R F 1925 o - Ap 4 23s 1 S +R F 1926 o - Ap 17 23s 1 S +R F 1927 o - Ap 9 23s 1 S +R F 1928 o - Ap 14 23s 1 S +R F 1929 o - Ap 20 23s 1 S +R F 1930 o - Ap 12 23s 1 S +R F 1931 o - Ap 18 23s 1 S +R F 1932 o - Ap 2 23s 1 S +R F 1933 o - Mar 25 23s 1 S +R F 1934 o - Ap 7 23s 1 S +R F 1935 o - Mar 30 23s 1 S +R F 1936 o - Ap 18 23s 1 S +R F 1937 o - Ap 3 23s 1 S +R F 1938 o - Mar 26 23s 1 S +R F 1939 o - Ap 15 23s 1 S +R F 1939 o - N 18 23s 0 - +R F 1940 o - F 25 2 1 S +R F 1941 o - May 5 0 2 M +R F 1941 o - O 6 0 1 S +R F 1942 o - Mar 9 0 2 M +R F 1942 o - N 2 3 1 S +R F 1943 o - Mar 29 2 2 M +R F 1943 o - O 4 3 1 S +R F 1944 o - Ap 3 2 2 M +R F 1944 o - O 8 1 1 S +R F 1945 o - Ap 2 2 2 M +R F 1945 o - S 16 3 0 - +R F 1976 o - Mar 28 1 1 S +R F 1976 o - S 26 1 0 - +Z Europe/Paris 0:9:21 - LMT 1891 Mar 16 +0:9:21 - PMT 1911 Mar 11 +0 F WE%sT 1940 Jun 14 23 +1 c CE%sT 1944 Au 25 +0 F WE%sT 1945 S 16 3 +1 F CE%sT 1977 +1 E CE%sT +R DE 1946 o - Ap 14 2s 1 S +R DE 1946 o - O 7 2s 0 - +R DE 1947 1949 - O Su>=1 2s 0 - +R DE 1947 o - Ap 6 3s 1 S +R DE 1947 o - May 11 2s 2 M +R DE 1947 o - Jun 29 3 1 S +R DE 1948 o - Ap 18 2s 1 S +R DE 1949 o - Ap 10 2s 1 S +R So 1945 o - May 24 2 2 M +R So 1945 o - S 24 3 1 S +R So 1945 o - N 18 2s 0 - +Z Europe/Berlin 0:53:28 - LMT 1893 Ap +1 c CE%sT 1945 May 24 2 +1 So CE%sT 1946 +1 DE CE%sT 1980 +1 E CE%sT +L Europe/Zurich Europe/Busingen +Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s +0 G %s 1957 Ap 14 2 +1 - CET 1982 +1 E CE%sT +R g 1932 o - Jul 7 0 1 S +R g 1932 o - S 1 0 0 - +R g 1941 o - Ap 7 0 1 S +R g 1942 o - N 2 3 0 - +R g 1943 o - Mar 30 0 1 S +R g 1943 o - O 4 0 0 - +R g 1952 o - Jul 1 0 1 S +R g 1952 o - N 2 0 0 - +R g 1975 o - Ap 12 0s 1 S +R g 1975 o - N 26 0s 0 - +R g 1976 o - Ap 11 2s 1 S +R g 1976 o - O 10 2s 0 - +R g 1977 1978 - Ap Su>=1 2s 1 S +R g 1977 o - S 26 2s 0 - +R g 1978 o - S 24 4 0 - +R g 1979 o - Ap 1 9 1 S +R g 1979 o - S 29 2 0 - +R g 1980 o - Ap 1 0 1 S +R g 1980 o - S 28 0 0 - +Z Europe/Athens 1:34:52 - LMT 1895 S 14 +1:34:52 - AMT 1916 Jul 28 0:1 +2 g EE%sT 1941 Ap 30 +1 g CE%sT 1944 Ap 4 +2 g EE%sT 1981 +2 E EE%sT +R h 1918 1919 - Ap 15 2 1 S +R h 1918 1920 - S M>=15 3 0 - +R h 1920 o - Ap 5 2 1 S +R h 1945 o - May 1 23 1 S +R h 1945 o - N 1 1 0 - +R h 1946 o - Mar 31 2s 1 S +R h 1946 o - O 7 2 0 - +R h 1947 1949 - Ap Su>=4 2s 1 S +R h 1947 1949 - O Su>=1 2s 0 - +R h 1954 o - May 23 0 1 S +R h 1954 o - O 3 0 0 - +R h 1955 o - May 22 2 1 S +R h 1955 o - O 2 3 0 - +R h 1956 1957 - Jun Su>=1 2 1 S +R h 1956 1957 - S lastSu 3 0 - +R h 1980 o - Ap 6 0 1 S +R h 1980 o - S 28 1 0 - +R h 1981 1983 - Mar lastSu 0 1 S +R h 1981 1983 - S lastSu 1 0 - +Z Europe/Budapest 1:16:20 - LMT 1890 N +1 c CE%sT 1918 +1 h CE%sT 1941 Ap 7 23 +1 c CE%sT 1945 +1 h CE%sT 1984 +1 E CE%sT +R w 1917 1919 - F 19 23 1 - +R w 1917 o - O 21 1 0 - +R w 1918 1919 - N 16 1 0 - +R w 1921 o - Mar 19 23 1 - +R w 1921 o - Jun 23 1 0 - +R w 1939 o - Ap 29 23 1 - +R w 1939 o - O 29 2 0 - +R w 1940 o - F 25 2 1 - +R w 1940 1941 - N Su>=2 1s 0 - +R w 1941 1942 - Mar Su>=2 1s 1 - +R w 1943 1946 - Mar Su>=1 1s 1 - +R w 1942 1948 - O Su>=22 1s 0 - +R w 1947 1967 - Ap Su>=1 1s 1 - +R w 1949 o - O 30 1s 0 - +R w 1950 1966 - O Su>=22 1s 0 - +R w 1967 o - O 29 1s 0 - +Z Atlantic/Reykjavik -1:28 - LMT 1908 +-1 w -01/+00 1968 Ap 7 1s +0 - GMT +R I 1916 o - Jun 3 24 1 S +R I 1916 1917 - S 30 24 0 - +R I 1917 o - Mar 31 24 1 S +R I 1918 o - Mar 9 24 1 S +R I 1918 o - O 6 24 0 - +R I 1919 o - Mar 1 24 1 S +R I 1919 o - O 4 24 0 - +R I 1920 o - Mar 20 24 1 S +R I 1920 o - S 18 24 0 - +R I 1940 o - Jun 14 24 1 S +R I 1942 o - N 2 2s 0 - +R I 1943 o - Mar 29 2s 1 S +R I 1943 o - O 4 2s 0 - +R I 1944 o - Ap 2 2s 1 S +R I 1944 o - S 17 2s 0 - +R I 1945 o - Ap 2 2 1 S +R I 1945 o - S 15 1 0 - +R I 1946 o - Mar 17 2s 1 S +R I 1946 o - O 6 2s 0 - +R I 1947 o - Mar 16 0s 1 S +R I 1947 o - O 5 0s 0 - +R I 1948 o - F 29 2s 1 S +R I 1948 o - O 3 2s 0 - +R I 1966 1968 - May Su>=22 0s 1 S +R I 1966 o - S 24 24 0 - +R I 1967 1969 - S Su>=22 0s 0 - +R I 1969 o - Jun 1 0s 1 S +R I 1970 o - May 31 0s 1 S +R I 1970 o - S lastSu 0s 0 - +R I 1971 1972 - May Su>=22 0s 1 S +R I 1971 o - S lastSu 0s 0 - +R I 1972 o - O 1 0s 0 - +R I 1973 o - Jun 3 0s 1 S +R I 1973 1974 - S lastSu 0s 0 - +R I 1974 o - May 26 0s 1 S +R I 1975 o - Jun 1 0s 1 S +R I 1975 1977 - S lastSu 0s 0 - +R I 1976 o - May 30 0s 1 S +R I 1977 1979 - May Su>=22 0s 1 S +R I 1978 o - O 1 0s 0 - +R I 1979 o - S 30 0s 0 - +Z Europe/Rome 0:49:56 - LMT 1866 D 12 +0:49:56 - RMT 1893 O 31 23:49:56 +1 I CE%sT 1943 S 10 +1 c CE%sT 1944 Jun 4 +1 I CE%sT 1980 +1 E CE%sT +L Europe/Rome Europe/Vatican +L Europe/Rome Europe/San_Marino +R LV 1989 1996 - Mar lastSu 2s 1 S +R LV 1989 1996 - S lastSu 2s 0 - +Z Europe/Riga 1:36:34 - LMT 1880 +1:36:34 - RMT 1918 Ap 15 2 +1:36:34 1 LST 1918 S 16 3 +1:36:34 - RMT 1919 Ap 1 2 +1:36:34 1 LST 1919 May 22 3 +1:36:34 - RMT 1926 May 11 +2 - EET 1940 Au 5 +3 - MSK 1941 Jul +1 c CE%sT 1944 O 13 +3 R MSK/MSD 1989 Mar lastSu 2s +2 1 EEST 1989 S lastSu 2s +2 LV EE%sT 1997 Ja 21 +2 E EE%sT 2000 F 29 +2 - EET 2001 Ja 2 +2 E EE%sT +L Europe/Zurich Europe/Vaduz +Z Europe/Vilnius 1:41:16 - LMT 1880 +1:24 - WMT 1917 +1:35:36 - KMT 1919 O 10 +1 - CET 1920 Jul 12 +2 - EET 1920 O 9 +1 - CET 1940 Au 3 +3 - MSK 1941 Jun 24 +1 c CE%sT 1944 Au +3 R MSK/MSD 1989 Mar 26 2s +2 R EE%sT 1991 S 29 2s +2 c EE%sT 1998 +2 - EET 1998 Mar 29 1u +1 E CE%sT 1999 O 31 1u +2 - EET 2003 +2 E EE%sT +R LX 1916 o - May 14 23 1 S +R LX 1916 o - O 1 1 0 - +R LX 1917 o - Ap 28 23 1 S +R LX 1917 o - S 17 1 0 - +R LX 1918 o - Ap M>=15 2s 1 S +R LX 1918 o - S M>=15 2s 0 - +R LX 1919 o - Mar 1 23 1 S +R LX 1919 o - O 5 3 0 - +R LX 1920 o - F 14 23 1 S +R LX 1920 o - O 24 2 0 - +R LX 1921 o - Mar 14 23 1 S +R LX 1921 o - O 26 2 0 - +R LX 1922 o - Mar 25 23 1 S +R LX 1922 o - O Su>=2 1 0 - +R LX 1923 o - Ap 21 23 1 S +R LX 1923 o - O Su>=2 2 0 - +R LX 1924 o - Mar 29 23 1 S +R LX 1924 1928 - O Su>=2 1 0 - +R LX 1925 o - Ap 5 23 1 S +R LX 1926 o - Ap 17 23 1 S +R LX 1927 o - Ap 9 23 1 S +R LX 1928 o - Ap 14 23 1 S +R LX 1929 o - Ap 20 23 1 S +Z Europe/Luxembourg 0:24:36 - LMT 1904 Jun +1 LX CE%sT 1918 N 25 +0 LX WE%sT 1929 O 6 2s +0 b WE%sT 1940 May 14 3 +1 c WE%sT 1944 S 18 3 +1 b CE%sT 1977 +1 E CE%sT +R MT 1973 o - Mar 31 0s 1 S +R MT 1973 o - S 29 0s 0 - +R MT 1974 o - Ap 21 0s 1 S +R MT 1974 o - S 16 0s 0 - +R MT 1975 1979 - Ap Su>=15 2 1 S +R MT 1975 1980 - S Su>=15 2 0 - +R MT 1980 o - Mar 31 2 1 S +Z Europe/Malta 0:58:4 - LMT 1893 N 2 0s +1 I CE%sT 1973 Mar 31 +1 MT CE%sT 1981 +1 E CE%sT +R MD 1997 ma - Mar lastSu 2 1 S +R MD 1997 ma - O lastSu 3 0 - +Z Europe/Chisinau 1:55:20 - LMT 1880 +1:55 - CMT 1918 F 15 +1:44:24 - BMT 1931 Jul 24 +2 z EE%sT 1940 Au 15 +2 1 EEST 1941 Jul 17 +1 c CE%sT 1944 Au 24 +3 R MSK/MSD 1990 May 6 2 +2 R EE%sT 1992 +2 e EE%sT 1997 +2 MD EE%sT +Z Europe/Monaco 0:29:32 - LMT 1892 Jun +0:9:21 - PMT 1911 Mar 29 +0 F WE%sT 1945 S 16 3 +1 F CE%sT 1977 +1 E CE%sT +R N 1916 o - May 1 0 1 NST +R N 1916 o - O 1 0 0 AMT +R N 1917 o - Ap 16 2s 1 NST +R N 1917 o - S 17 2s 0 AMT +R N 1918 1921 - Ap M>=1 2s 1 NST +R N 1918 1921 - S lastM 2s 0 AMT +R N 1922 o - Mar lastSu 2s 1 NST +R N 1922 1936 - O Su>=2 2s 0 AMT +R N 1923 o - Jun F>=1 2s 1 NST +R N 1924 o - Mar lastSu 2s 1 NST +R N 1925 o - Jun F>=1 2s 1 NST +R N 1926 1931 - May 15 2s 1 NST +R N 1932 o - May 22 2s 1 NST +R N 1933 1936 - May 15 2s 1 NST +R N 1937 o - May 22 2s 1 NST +R N 1937 o - Jul 1 0 1 S +R N 1937 1939 - O Su>=2 2s 0 - +R N 1938 1939 - May 15 2s 1 S +R N 1945 o - Ap 2 2s 1 S +R N 1945 o - S 16 2s 0 - +Z Europe/Amsterdam 0:19:32 - LMT 1835 +0:19:32 N %s 1937 Jul +0:20 N +0020/+0120 1940 May 16 +1 c CE%sT 1945 Ap 2 2 +1 N CE%sT 1977 +1 E CE%sT +R NO 1916 o - May 22 1 1 S +R NO 1916 o - S 30 0 0 - +R NO 1945 o - Ap 2 2s 1 S +R NO 1945 o - O 1 2s 0 - +R NO 1959 1964 - Mar Su>=15 2s 1 S +R NO 1959 1965 - S Su>=15 2s 0 - +R NO 1965 o - Ap 25 2s 1 S +Z Europe/Oslo 0:43 - LMT 1895 +1 NO CE%sT 1940 Au 10 23 +1 c CE%sT 1945 Ap 2 2 +1 NO CE%sT 1980 +1 E CE%sT +L Europe/Oslo Arctic/Longyearbyen +R O 1918 1919 - S 16 2s 0 - +R O 1919 o - Ap 15 2s 1 S +R O 1944 o - Ap 3 2s 1 S +R O 1944 o - O 4 2 0 - +R O 1945 o - Ap 29 0 1 S +R O 1945 o - N 1 0 0 - +R O 1946 o - Ap 14 0s 1 S +R O 1946 o - O 7 2s 0 - +R O 1947 o - May 4 2s 1 S +R O 1947 1949 - O Su>=1 2s 0 - +R O 1948 o - Ap 18 2s 1 S +R O 1949 o - Ap 10 2s 1 S +R O 1957 o - Jun 2 1s 1 S +R O 1957 1958 - S lastSu 1s 0 - +R O 1958 o - Mar 30 1s 1 S +R O 1959 o - May 31 1s 1 S +R O 1959 1961 - O Su>=1 1s 0 - +R O 1960 o - Ap 3 1s 1 S +R O 1961 1964 - May lastSu 1s 1 S +R O 1962 1964 - S lastSu 1s 0 - +Z Europe/Warsaw 1:24 - LMT 1880 +1:24 - WMT 1915 Au 5 +1 c CE%sT 1918 S 16 3 +2 O EE%sT 1922 Jun +1 O CE%sT 1940 Jun 23 2 +1 c CE%sT 1944 O +1 O CE%sT 1977 +1 W- CE%sT 1988 +1 E CE%sT +R p 1916 o - Jun 17 23 1 S +R p 1916 o - N 1 1 0 - +R p 1917 o - F 28 23s 1 S +R p 1917 1921 - O 14 23s 0 - +R p 1918 o - Mar 1 23s 1 S +R p 1919 o - F 28 23s 1 S +R p 1920 o - F 29 23s 1 S +R p 1921 o - F 28 23s 1 S +R p 1924 o - Ap 16 23s 1 S +R p 1924 o - O 14 23s 0 - +R p 1926 o - Ap 17 23s 1 S +R p 1926 1929 - O Sa>=1 23s 0 - +R p 1927 o - Ap 9 23s 1 S +R p 1928 o - Ap 14 23s 1 S +R p 1929 o - Ap 20 23s 1 S +R p 1931 o - Ap 18 23s 1 S +R p 1931 1932 - O Sa>=1 23s 0 - +R p 1932 o - Ap 2 23s 1 S +R p 1934 o - Ap 7 23s 1 S +R p 1934 1938 - O Sa>=1 23s 0 - +R p 1935 o - Mar 30 23s 1 S +R p 1936 o - Ap 18 23s 1 S +R p 1937 o - Ap 3 23s 1 S +R p 1938 o - Mar 26 23s 1 S +R p 1939 o - Ap 15 23s 1 S +R p 1939 o - N 18 23s 0 - +R p 1940 o - F 24 23s 1 S +R p 1940 1941 - O 5 23s 0 - +R p 1941 o - Ap 5 23s 1 S +R p 1942 1945 - Mar Sa>=8 23s 1 S +R p 1942 o - Ap 25 22s 2 M +R p 1942 o - Au 15 22s 1 S +R p 1942 1945 - O Sa>=24 23s 0 - +R p 1943 o - Ap 17 22s 2 M +R p 1943 1945 - Au Sa>=25 22s 1 S +R p 1944 1945 - Ap Sa>=21 22s 2 M +R p 1946 o - Ap Sa>=1 23s 1 S +R p 1946 o - O Sa>=1 23s 0 - +R p 1947 1965 - Ap Su>=1 2s 1 S +R p 1947 1965 - O Su>=1 2s 0 - +R p 1977 o - Mar 27 0s 1 S +R p 1977 o - S 25 0s 0 - +R p 1978 1979 - Ap Su>=1 0s 1 S +R p 1978 o - O 1 0s 0 - +R p 1979 1982 - S lastSu 1s 0 - +R p 1980 o - Mar lastSu 0s 1 S +R p 1981 1982 - Mar lastSu 1s 1 S +R p 1983 o - Mar lastSu 2s 1 S +Z Europe/Lisbon -0:36:45 - LMT 1884 +-0:36:45 - LMT 1912 Ja 1 0u +0 p WE%sT 1966 Ap 3 2 +1 - CET 1976 S 26 1 +0 p WE%sT 1983 S 25 1s +0 W- WE%sT 1992 S 27 1s +1 E CE%sT 1996 Mar 31 1u +0 E WE%sT +Z Atlantic/Azores -1:42:40 - LMT 1884 +-1:54:32 - HMT 1912 Ja 1 2u +-2 p -02/-01 1942 Ap 25 22s +-2 p +00 1942 Au 15 22s +-2 p -02/-01 1943 Ap 17 22s +-2 p +00 1943 Au 28 22s +-2 p -02/-01 1944 Ap 22 22s +-2 p +00 1944 Au 26 22s +-2 p -02/-01 1945 Ap 21 22s +-2 p +00 1945 Au 25 22s +-2 p -02/-01 1966 Ap 3 2 +-1 p -01/+00 1983 S 25 1s +-1 W- -01/+00 1992 S 27 1s +0 E WE%sT 1993 Mar 28 1u +-1 E -01/+00 +Z Atlantic/Madeira -1:7:36 - LMT 1884 +-1:7:36 - FMT 1912 Ja 1 1u +-1 p -01/+00 1942 Ap 25 22s +-1 p +01 1942 Au 15 22s +-1 p -01/+00 1943 Ap 17 22s +-1 p +01 1943 Au 28 22s +-1 p -01/+00 1944 Ap 22 22s +-1 p +01 1944 Au 26 22s +-1 p -01/+00 1945 Ap 21 22s +-1 p +01 1945 Au 25 22s +-1 p -01/+00 1966 Ap 3 2 +0 p WE%sT 1983 S 25 1s +0 E WE%sT +R z 1932 o - May 21 0s 1 S +R z 1932 1939 - O Su>=1 0s 0 - +R z 1933 1939 - Ap Su>=2 0s 1 S +R z 1979 o - May 27 0 1 S +R z 1979 o - S lastSu 0 0 - +R z 1980 o - Ap 5 23 1 S +R z 1980 o - S lastSu 1 0 - +R z 1991 1993 - Mar lastSu 0s 1 S +R z 1991 1993 - S lastSu 0s 0 - +Z Europe/Bucharest 1:44:24 - LMT 1891 O +1:44:24 - BMT 1931 Jul 24 +2 z EE%sT 1981 Mar 29 2s +2 c EE%sT 1991 +2 z EE%sT 1994 +2 e EE%sT 1997 +2 E EE%sT +Z Europe/Kaliningrad 1:22 - LMT 1893 Ap +1 c CE%sT 1945 Ap 10 +2 O EE%sT 1946 Ap 7 +3 R MSK/MSD 1989 Mar 26 2s +2 R EE%sT 2011 Mar 27 2s +3 - +03 2014 O 26 2s +2 - EET +Z Europe/Moscow 2:30:17 - LMT 1880 +2:30:17 - MMT 1916 Jul 3 +2:31:19 R %s 1919 Jul 1 0u +3 R %s 1921 O +3 R MSK/MSD 1922 O +2 - EET 1930 Jun 21 +3 R MSK/MSD 1991 Mar 31 2s +2 R EE%sT 1992 Ja 19 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Simferopol 2:16:24 - LMT 1880 +2:16 - SMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 N +1 c CE%sT 1944 Ap 13 +3 R MSK/MSD 1990 +3 - MSK 1990 Jul 1 2 +2 - EET 1992 Mar 20 +2 c EE%sT 1994 May +3 e MSK/MSD 1996 Mar 31 0s +3 1 MSD 1996 O 27 3s +3 R MSK/MSD 1997 +3 - MSK 1997 Mar lastSu 1u +2 E EE%sT 2014 Mar 30 2 +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Astrakhan 3:12:12 - LMT 1924 May +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 Mar 27 2s +4 - +04 +Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 +3 - +03 1930 Jun 21 +4 - +04 1961 N 11 +4 R +04/+05 1988 Mar 27 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2018 O 28 2s +4 - +04 2020 D 27 2s +3 - +03 +Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1988 Mar 27 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 D 4 2s +4 - +04 +Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 +Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 - +04 1935 Ja 27 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +2 R +02/+03 1991 S 29 2s +3 - +03 1991 O 20 3 +4 R +04/+05 2010 Mar 28 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 +Z Europe/Ulyanovsk 3:13:36 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +2 R +02/+03 1992 Ja 19 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 Mar 27 2s +4 - +04 +Z Asia/Yekaterinburg 4:2:33 - LMT 1916 Jul 3 +3:45:5 - PMT 1919 Jul 15 4 +4 - +04 1930 Jun 21 +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2011 Mar 27 2s +6 - +06 2014 O 26 2s +5 - +05 +Z Asia/Omsk 4:53:30 - LMT 1919 N 14 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 +Z Asia/Barnaul 5:35 - LMT 1919 D 10 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 1995 May 28 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 Mar 27 2s +7 - +07 +Z Asia/Novosibirsk 5:31:40 - LMT 1919 D 14 6 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 1993 May 23 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 Jul 24 2s +7 - +07 +Z Asia/Tomsk 5:39:51 - LMT 1919 D 22 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2002 May 1 3 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 May 29 2s +7 - +07 +Z Asia/Novokuznetsk 5:48:48 - LMT 1924 May +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2010 Mar 28 2s +6 R +06/+07 2011 Mar 27 2s +7 - +07 +Z Asia/Krasnoyarsk 6:11:26 - LMT 1920 Ja 6 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2011 Mar 27 2s +8 - +08 2014 O 26 2s +7 - +07 +Z Asia/Irkutsk 6:57:5 - LMT 1880 +6:57:5 - IMT 1920 Ja 25 +7 - +07 1930 Jun 21 +8 R +08/+09 1991 Mar 31 2s +7 R +07/+08 1992 Ja 19 2s +8 R +08/+09 2011 Mar 27 2s +9 - +09 2014 O 26 2s +8 - +08 +Z Asia/Chita 7:33:52 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2011 Mar 27 2s +10 - +10 2014 O 26 2s +8 - +08 2016 Mar 27 2 +9 - +09 +Z Asia/Yakutsk 8:38:58 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2011 Mar 27 2s +10 - +10 2014 O 26 2s +9 - +09 +Z Asia/Vladivostok 8:47:31 - LMT 1922 N 15 +9 - +09 1930 Jun 21 +10 R +10/+11 1991 Mar 31 2s +9 R +09/+10 1992 Ja 19 2s +10 R +10/+11 2011 Mar 27 2s +11 - +11 2014 O 26 2s +10 - +10 +Z Asia/Khandyga 9:2:13 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2004 +10 R +10/+11 2011 Mar 27 2s +11 - +11 2011 S 13 0s +10 - +10 2014 O 26 2s +9 - +09 +Z Asia/Sakhalin 9:30:48 - LMT 1905 Au 23 +9 - +09 1945 Au 25 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 1997 Mar lastSu 2s +10 R +10/+11 2011 Mar 27 2s +11 - +11 2014 O 26 2s +10 - +10 2016 Mar 27 2s +11 - +11 +Z Asia/Magadan 10:3:12 - LMT 1924 May 2 +10 - +10 1930 Jun 21 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2014 O 26 2s +10 - +10 2016 Ap 24 2s +11 - +11 +Z Asia/Srednekolymsk 10:14:52 - LMT 1924 May 2 +10 - +10 1930 Jun 21 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2014 O 26 2s +11 - +11 +Z Asia/Ust-Nera 9:32:54 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1981 Ap +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2011 S 13 0s +11 - +11 2014 O 26 2s +10 - +10 +Z Asia/Kamchatka 10:34:36 - LMT 1922 N 10 +11 - +11 1930 Jun 21 +12 R +12/+13 1991 Mar 31 2s +11 R +11/+12 1992 Ja 19 2s +12 R +12/+13 2010 Mar 28 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 +Z Asia/Anadyr 11:49:56 - LMT 1924 May 2 +12 - +12 1930 Jun 21 +13 R +13/+14 1982 Ap 1 0s +12 R +12/+13 1991 Mar 31 2s +11 R +11/+12 1992 Ja 19 2s +12 R +12/+13 2010 Mar 28 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 +Z Europe/Belgrade 1:22 - LMT 1884 +1 - CET 1941 Ap 18 23 +1 c CE%sT 1945 +1 - CET 1945 May 8 2s +1 1 CEST 1945 S 16 2s +1 - CET 1982 N 27 +1 E CE%sT +L Europe/Belgrade Europe/Ljubljana +L Europe/Belgrade Europe/Podgorica +L Europe/Belgrade Europe/Sarajevo +L Europe/Belgrade Europe/Skopje +L Europe/Belgrade Europe/Zagreb +L Europe/Prague Europe/Bratislava +R s 1918 o - Ap 15 23 1 S +R s 1918 1919 - O 6 24s 0 - +R s 1919 o - Ap 6 23 1 S +R s 1924 o - Ap 16 23 1 S +R s 1924 o - O 4 24s 0 - +R s 1926 o - Ap 17 23 1 S +R s 1926 1929 - O Sa>=1 24s 0 - +R s 1927 o - Ap 9 23 1 S +R s 1928 o - Ap 15 0 1 S +R s 1929 o - Ap 20 23 1 S +R s 1937 o - Jun 16 23 1 S +R s 1937 o - O 2 24s 0 - +R s 1938 o - Ap 2 23 1 S +R s 1938 o - Ap 30 23 2 M +R s 1938 o - O 2 24 1 S +R s 1939 o - O 7 24s 0 - +R s 1942 o - May 2 23 1 S +R s 1942 o - S 1 1 0 - +R s 1943 1946 - Ap Sa>=13 23 1 S +R s 1943 1944 - O Su>=1 1 0 - +R s 1945 1946 - S lastSu 1 0 - +R s 1949 o - Ap 30 23 1 S +R s 1949 o - O 2 1 0 - +R s 1974 1975 - Ap Sa>=12 23 1 S +R s 1974 1975 - O Su>=1 1 0 - +R s 1976 o - Mar 27 23 1 S +R s 1976 1977 - S lastSu 1 0 - +R s 1977 o - Ap 2 23 1 S +R s 1978 o - Ap 2 2s 1 S +R s 1978 o - O 1 2s 0 - +R Sp 1967 o - Jun 3 12 1 S +R Sp 1967 o - O 1 0 0 - +R Sp 1974 o - Jun 24 0 1 S +R Sp 1974 o - S 1 0 0 - +R Sp 1976 1977 - May 1 0 1 S +R Sp 1976 o - Au 1 0 0 - +R Sp 1977 o - S 28 0 0 - +R Sp 1978 o - Jun 1 0 1 S +R Sp 1978 o - Au 4 0 0 - +Z Europe/Madrid -0:14:44 - LMT 1900 D 31 23:45:16 +0 s WE%sT 1940 Mar 16 23 +1 s CE%sT 1979 +1 E CE%sT +Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 +0 - WET 1918 May 6 23 +0 1 WEST 1918 O 7 23 +0 - WET 1924 +0 s WE%sT 1929 +0 - WET 1967 +0 Sp WE%sT 1984 Mar 16 +1 - CET 1986 +1 E CE%sT +Z Atlantic/Canary -1:1:36 - LMT 1922 Mar +-1 - -01 1946 S 30 1 +0 - WET 1980 Ap 6 0s +0 1 WEST 1980 S 28 1u +0 E WE%sT +Z Europe/Stockholm 1:12:12 - LMT 1879 +1:0:14 - SET 1900 +1 - CET 1916 May 14 23 +1 1 CEST 1916 O 1 1 +1 - CET 1980 +1 E CE%sT +R CH 1941 1942 - May M>=1 1 1 S +R CH 1941 1942 - O M>=1 2 0 - +Z Europe/Zurich 0:34:8 - LMT 1853 Jul 16 +0:29:46 - BMT 1894 Jun +1 CH CE%sT 1981 +1 E CE%sT +R T 1916 o - May 1 0 1 S +R T 1916 o - O 1 0 0 - +R T 1920 o - Mar 28 0 1 S +R T 1920 o - O 25 0 0 - +R T 1921 o - Ap 3 0 1 S +R T 1921 o - O 3 0 0 - +R T 1922 o - Mar 26 0 1 S +R T 1922 o - O 8 0 0 - +R T 1924 o - May 13 0 1 S +R T 1924 1925 - O 1 0 0 - +R T 1925 o - May 1 0 1 S +R T 1940 o - Jul 1 0 1 S +R T 1940 o - O 6 0 0 - +R T 1940 o - D 1 0 1 S +R T 1941 o - S 21 0 0 - +R T 1942 o - Ap 1 0 1 S +R T 1945 o - O 8 0 0 - +R T 1946 o - Jun 1 0 1 S +R T 1946 o - O 1 0 0 - +R T 1947 1948 - Ap Su>=16 0 1 S +R T 1947 1951 - O Su>=2 0 0 - +R T 1949 o - Ap 10 0 1 S +R T 1950 o - Ap 16 0 1 S +R T 1951 o - Ap 22 0 1 S +R T 1962 o - Jul 15 0 1 S +R T 1963 o - O 30 0 0 - +R T 1964 o - May 15 0 1 S +R T 1964 o - O 1 0 0 - +R T 1973 o - Jun 3 1 1 S +R T 1973 1976 - O Su>=31 2 0 - +R T 1974 o - Mar 31 2 1 S +R T 1975 o - Mar 22 2 1 S +R T 1976 o - Mar 21 2 1 S +R T 1977 1978 - Ap Su>=1 2 1 S +R T 1977 1978 - O Su>=15 2 0 - +R T 1978 o - Jun 29 0 0 - +R T 1983 o - Jul 31 2 1 S +R T 1983 o - O 2 2 0 - +R T 1985 o - Ap 20 1s 1 S +R T 1985 o - S 28 1s 0 - +R T 1986 1993 - Mar lastSu 1s 1 S +R T 1986 1995 - S lastSu 1s 0 - +R T 1994 o - Mar 20 1s 1 S +R T 1995 2006 - Mar lastSu 1s 1 S +R T 1996 2006 - O lastSu 1s 0 - +Z Europe/Istanbul 1:55:52 - LMT 1880 +1:56:56 - IMT 1910 O +2 T EE%sT 1978 Jun 29 +3 T +03/+04 1984 N 1 2 +2 T EE%sT 2007 +2 E EE%sT 2011 Mar 27 1u +2 - EET 2011 Mar 28 1u +2 E EE%sT 2014 Mar 30 1u +2 - EET 2014 Mar 31 1u +2 E EE%sT 2015 O 25 1u +2 1 EEST 2015 N 8 1u +2 E EE%sT 2016 S 7 +3 - +03 +L Europe/Istanbul Asia/Istanbul +Z Europe/Kiev 2:2:4 - LMT 1880 +2:2:4 - KMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 S 20 +1 c CE%sT 1943 N 6 +3 R MSK/MSD 1990 Jul 1 2 +2 1 EEST 1991 S 29 3 +2 c EE%sT 1996 May 13 +2 E EE%sT +Z Europe/Uzhgorod 1:29:12 - LMT 1890 O +1 - CET 1940 +1 c CE%sT 1944 O +1 1 CEST 1944 O 26 +1 - CET 1945 Jun 29 +3 R MSK/MSD 1990 +3 - MSK 1990 Jul 1 2 +1 - CET 1991 Mar 31 3 +2 - EET 1992 Mar 20 +2 c EE%sT 1996 May 13 +2 E EE%sT +Z Europe/Zaporozhye 2:20:40 - LMT 1880 +2:20 - +0220 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 Au 25 +1 c CE%sT 1943 O 25 +3 R MSK/MSD 1991 Mar 31 2 +2 e EE%sT 1992 Mar 20 +2 c EE%sT 1996 May 13 +2 E EE%sT +R u 1918 1919 - Mar lastSu 2 1 D +R u 1918 1919 - O lastSu 2 0 S +R u 1942 o - F 9 2 1 W +R u 1945 o - Au 14 23u 1 P +R u 1945 o - S 30 2 0 S +R u 1967 2006 - O lastSu 2 0 S +R u 1967 1973 - Ap lastSu 2 1 D +R u 1974 o - Ja 6 2 1 D +R u 1975 o - F lastSu 2 1 D +R u 1976 1986 - Ap lastSu 2 1 D +R u 1987 2006 - Ap Su>=1 2 1 D +R u 2007 ma - Mar Su>=8 2 1 D +R u 2007 ma - N Su>=1 2 0 S +Z EST -5 - EST +Z MST -7 - MST +Z HST -10 - HST +Z EST5EDT -5 u E%sT +Z CST6CDT -6 u C%sT +Z MST7MDT -7 u M%sT +Z PST8PDT -8 u P%sT +R NY 1920 o - Mar lastSu 2 1 D +R NY 1920 o - O lastSu 2 0 S +R NY 1921 1966 - Ap lastSu 2 1 D +R NY 1921 1954 - S lastSu 2 0 S +R NY 1955 1966 - O lastSu 2 0 S +Z America/New_York -4:56:2 - LMT 1883 N 18 12:3:58 +-5 u E%sT 1920 +-5 NY E%sT 1942 +-5 u E%sT 1946 +-5 NY E%sT 1967 +-5 u E%sT +R Ch 1920 o - Jun 13 2 1 D +R Ch 1920 1921 - O lastSu 2 0 S +R Ch 1921 o - Mar lastSu 2 1 D +R Ch 1922 1966 - Ap lastSu 2 1 D +R Ch 1922 1954 - S lastSu 2 0 S +R Ch 1955 1966 - O lastSu 2 0 S +Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 +-6 u C%sT 1920 +-6 Ch C%sT 1936 Mar 1 2 +-5 - EST 1936 N 15 2 +-6 Ch C%sT 1942 +-6 u C%sT 1946 +-6 Ch C%sT 1967 +-6 u C%sT +Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 12:14:48 +-7 u M%sT 1992 O 25 2 +-6 u C%sT +Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 12:14:21 +-7 u M%sT 2003 O 26 2 +-6 u C%sT +Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 12:12:53 +-7 u M%sT 2010 N 7 2 +-6 u C%sT +R De 1920 1921 - Mar lastSu 2 1 D +R De 1920 o - O lastSu 2 0 S +R De 1921 o - May 22 2 0 S +R De 1965 1966 - Ap lastSu 2 1 D +R De 1965 1966 - O lastSu 2 0 S +Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 +-7 u M%sT 1920 +-7 De M%sT 1942 +-7 u M%sT 1946 +-7 De M%sT 1967 +-7 u M%sT +R CA 1948 o - Mar 14 2:1 1 D +R CA 1949 o - Ja 1 2 0 S +R CA 1950 1966 - Ap lastSu 1 1 D +R CA 1950 1961 - S lastSu 2 0 S +R CA 1962 1966 - O lastSu 2 0 S +Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 12:7:2 +-8 u P%sT 1946 +-8 CA P%sT 1967 +-8 u P%sT +Z America/Juneau 15:2:19 - LMT 1867 O 19 15:33:32 +-8:57:41 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1980 Ap 27 2 +-9 u Y%sT 1980 O 26 2 +-8 u P%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Sitka 14:58:47 - LMT 1867 O 19 15:30 +-9:1:13 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Metlakatla 15:13:42 - LMT 1867 O 19 15:44:55 +-8:46:18 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1983 O 30 2 +-8 - PST 2015 N 1 2 +-9 u AK%sT 2018 N 4 2 +-8 - PST 2019 Ja 20 2 +-9 u AK%sT +Z America/Yakutat 14:41:5 - LMT 1867 O 19 15:12:18 +-9:18:55 - LMT 1900 Au 20 12 +-9 - YST 1942 +-9 u Y%sT 1946 +-9 - YST 1969 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Anchorage 14:0:24 - LMT 1867 O 19 14:31:37 +-9:59:36 - LMT 1900 Au 20 12 +-10 - AST 1942 +-10 u A%sT 1967 Ap +-10 - AHST 1969 +-10 u AH%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Nome 12:58:22 - LMT 1867 O 19 13:29:35 +-11:1:38 - LMT 1900 Au 20 12 +-11 - NST 1942 +-11 u N%sT 1946 +-11 - NST 1967 Ap +-11 - BST 1969 +-11 u B%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Adak 12:13:22 - LMT 1867 O 19 12:44:35 +-11:46:38 - LMT 1900 Au 20 12 +-11 - NST 1942 +-11 u N%sT 1946 +-11 - NST 1967 Ap +-11 - BST 1969 +-11 u B%sT 1983 O 30 2 +-10 u AH%sT 1983 N 30 +-10 u H%sT +Z Pacific/Honolulu -10:31:26 - LMT 1896 Ja 13 12 +-10:30 - HST 1933 Ap 30 2 +-10:30 1 HDT 1933 May 21 12 +-10:30 u H%sT 1947 Jun 8 2 +-10 - HST +Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 +-7 u M%sT 1944 Ja 1 0:1 +-7 - MST 1944 Ap 1 0:1 +-7 u M%sT 1944 O 1 0:1 +-7 - MST 1967 +-7 u M%sT 1968 Mar 21 +-7 - MST +L America/Phoenix America/Creston +Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 +-8 u P%sT 1923 May 13 2 +-7 u M%sT 1974 +-7 - MST 1974 F 3 2 +-7 u M%sT +R In 1941 o - Jun 22 2 1 D +R In 1941 1954 - S lastSu 2 0 S +R In 1946 1954 - Ap lastSu 2 1 D +Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 +-6 u C%sT 1920 +-6 In C%sT 1942 +-6 u C%sT 1946 +-6 In C%sT 1955 Ap 24 2 +-5 - EST 1957 S 29 2 +-6 - CST 1958 Ap 27 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 +-5 u E%sT +R Ma 1951 o - Ap lastSu 2 1 D +R Ma 1951 o - S lastSu 2 0 S +R Ma 1954 1960 - Ap lastSu 2 1 D +R Ma 1954 1960 - S lastSu 2 0 S +Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 +-6 u C%sT 1951 +-6 Ma C%sT 1961 Ap 30 2 +-5 - EST 1969 +-5 u E%sT 1974 Ja 6 2 +-6 1 CDT 1974 O 27 2 +-5 u E%sT 1976 +-5 - EST 2006 +-5 u E%sT +R V 1946 o - Ap lastSu 2 1 D +R V 1946 o - S lastSu 2 0 S +R V 1953 1954 - Ap lastSu 2 1 D +R V 1953 1959 - S lastSu 2 0 S +R V 1955 o - May 1 0 1 D +R V 1956 1963 - Ap lastSu 2 1 D +R V 1960 o - O lastSu 2 0 S +R V 1961 o - S lastSu 2 0 S +R V 1962 1963 - O lastSu 2 0 S +Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 +-6 u C%sT 1946 +-6 V C%sT 1964 Ap 26 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 N 4 2 +-5 u E%sT +R Pe 1955 o - May 1 0 1 D +R Pe 1955 1960 - S lastSu 2 0 S +R Pe 1956 1963 - Ap lastSu 2 1 D +R Pe 1961 1963 - O lastSu 2 0 S +Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 12:12:57 +-6 u C%sT 1946 +-6 Pe C%sT 1964 Ap 26 2 +-5 - EST 1967 O 29 2 +-6 u C%sT 1969 Ap 27 2 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +R Pi 1955 o - May 1 0 1 D +R Pi 1955 1960 - S lastSu 2 0 S +R Pi 1956 1964 - Ap lastSu 2 1 D +R Pi 1961 1964 - O lastSu 2 0 S +Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 +-6 u C%sT 1955 +-6 Pi C%sT 1965 Ap 25 2 +-5 - EST 1966 O 30 2 +-6 u C%sT 1977 O 30 2 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 N 4 2 +-5 u E%sT +R St 1947 1961 - Ap lastSu 2 1 D +R St 1947 1954 - S lastSu 2 0 S +R St 1955 1956 - O lastSu 2 0 S +R St 1957 1958 - S lastSu 2 0 S +R St 1959 1961 - O lastSu 2 0 S +Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 +-6 u C%sT 1947 +-6 St C%sT 1962 Ap 29 2 +-5 - EST 1963 O 27 2 +-6 u C%sT 1991 O 27 2 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +R Pu 1946 1960 - Ap lastSu 2 1 D +R Pu 1946 1954 - S lastSu 2 0 S +R Pu 1955 1956 - O lastSu 2 0 S +R Pu 1957 1960 - S lastSu 2 0 S +Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 +-6 u C%sT 1946 +-6 Pu C%sT 1961 Ap 30 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 Mar 11 2 +-5 u E%sT +Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 +-6 u C%sT 1954 Ap 25 2 +-5 - EST 1969 +-5 u E%sT 1973 +-5 - EST 2006 +-5 u E%sT +R v 1921 o - May 1 2 1 D +R v 1921 o - S 1 2 0 S +R v 1941 o - Ap lastSu 2 1 D +R v 1941 o - S lastSu 2 0 S +R v 1946 o - Ap lastSu 0:1 1 D +R v 1946 o - Jun 2 2 0 S +R v 1950 1961 - Ap lastSu 2 1 D +R v 1950 1955 - S lastSu 2 0 S +R v 1956 1961 - O lastSu 2 0 S +Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 +-6 u C%sT 1921 +-6 v C%sT 1942 +-6 u C%sT 1946 +-6 v C%sT 1961 Jul 23 2 +-5 - EST 1968 +-5 u E%sT 1974 Ja 6 2 +-6 1 CDT 1974 O 27 2 +-5 u E%sT +Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 12:20:36 +-6 u C%sT 1946 +-6 - CST 1968 +-6 u C%sT 2000 O 29 2 +-5 u E%sT +R Dt 1948 o - Ap lastSu 2 1 D +R Dt 1948 o - S lastSu 2 0 S +Z America/Detroit -5:32:11 - LMT 1905 +-6 - CST 1915 May 15 2 +-5 - EST 1942 +-5 u E%sT 1946 +-5 Dt E%sT 1967 Jun 14 0:1 +-5 u E%sT 1969 +-5 - EST 1973 +-5 u E%sT 1975 +-5 - EST 1975 Ap 27 2 +-5 u E%sT +R Me 1946 o - Ap lastSu 2 1 D +R Me 1946 o - S lastSu 2 0 S +R Me 1966 o - Ap lastSu 2 1 D +R Me 1966 o - O lastSu 2 0 S +Z America/Menominee -5:50:27 - LMT 1885 S 18 12 +-6 u C%sT 1946 +-6 Me C%sT 1969 Ap 27 2 +-5 - EST 1973 Ap 29 2 +-6 u C%sT +R C 1918 o - Ap 14 2 1 D +R C 1918 o - O 27 2 0 S +R C 1942 o - F 9 2 1 W +R C 1945 o - Au 14 23u 1 P +R C 1945 o - S 30 2 0 S +R C 1974 1986 - Ap lastSu 2 1 D +R C 1974 2006 - O lastSu 2 0 S +R C 1987 2006 - Ap Su>=1 2 1 D +R C 2007 ma - Mar Su>=8 2 1 D +R C 2007 ma - N Su>=1 2 0 S +R j 1917 o - Ap 8 2 1 D +R j 1917 o - S 17 2 0 S +R j 1919 o - May 5 23 1 D +R j 1919 o - Au 12 23 0 S +R j 1920 1935 - May Su>=1 23 1 D +R j 1920 1935 - O lastSu 23 0 S +R j 1936 1941 - May M>=9 0 1 D +R j 1936 1941 - O M>=2 0 0 S +R j 1946 1950 - May Su>=8 2 1 D +R j 1946 1950 - O Su>=2 2 0 S +R j 1951 1986 - Ap lastSu 2 1 D +R j 1951 1959 - S lastSu 2 0 S +R j 1960 1986 - O lastSu 2 0 S +R j 1987 o - Ap Su>=1 0:1 1 D +R j 1987 2006 - O lastSu 0:1 0 S +R j 1988 o - Ap Su>=1 0:1 2 DD +R j 1989 2006 - Ap Su>=1 0:1 1 D +R j 2007 2011 - Mar Su>=8 0:1 1 D +R j 2007 2010 - N Su>=1 0:1 0 S +Z America/St_Johns -3:30:52 - LMT 1884 +-3:30:52 j N%sT 1918 +-3:30:52 C N%sT 1919 +-3:30:52 j N%sT 1935 Mar 30 +-3:30 j N%sT 1942 May 11 +-3:30 C N%sT 1946 +-3:30 j N%sT 2011 N +-3:30 C N%sT +Z America/Goose_Bay -4:1:40 - LMT 1884 +-3:30:52 - NST 1918 +-3:30:52 C N%sT 1919 +-3:30:52 - NST 1935 Mar 30 +-3:30 - NST 1936 +-3:30 j N%sT 1942 May 11 +-3:30 C N%sT 1946 +-3:30 j N%sT 1966 Mar 15 2 +-4 j A%sT 2011 N +-4 C A%sT +R H 1916 o - Ap 1 0 1 D +R H 1916 o - O 1 0 0 S +R H 1920 o - May 9 0 1 D +R H 1920 o - Au 29 0 0 S +R H 1921 o - May 6 0 1 D +R H 1921 1922 - S 5 0 0 S +R H 1922 o - Ap 30 0 1 D +R H 1923 1925 - May Su>=1 0 1 D +R H 1923 o - S 4 0 0 S +R H 1924 o - S 15 0 0 S +R H 1925 o - S 28 0 0 S +R H 1926 o - May 16 0 1 D +R H 1926 o - S 13 0 0 S +R H 1927 o - May 1 0 1 D +R H 1927 o - S 26 0 0 S +R H 1928 1931 - May Su>=8 0 1 D +R H 1928 o - S 9 0 0 S +R H 1929 o - S 3 0 0 S +R H 1930 o - S 15 0 0 S +R H 1931 1932 - S M>=24 0 0 S +R H 1932 o - May 1 0 1 D +R H 1933 o - Ap 30 0 1 D +R H 1933 o - O 2 0 0 S +R H 1934 o - May 20 0 1 D +R H 1934 o - S 16 0 0 S +R H 1935 o - Jun 2 0 1 D +R H 1935 o - S 30 0 0 S +R H 1936 o - Jun 1 0 1 D +R H 1936 o - S 14 0 0 S +R H 1937 1938 - May Su>=1 0 1 D +R H 1937 1941 - S M>=24 0 0 S +R H 1939 o - May 28 0 1 D +R H 1940 1941 - May Su>=1 0 1 D +R H 1946 1949 - Ap lastSu 2 1 D +R H 1946 1949 - S lastSu 2 0 S +R H 1951 1954 - Ap lastSu 2 1 D +R H 1951 1954 - S lastSu 2 0 S +R H 1956 1959 - Ap lastSu 2 1 D +R H 1956 1959 - S lastSu 2 0 S +R H 1962 1973 - Ap lastSu 2 1 D +R H 1962 1973 - O lastSu 2 0 S +Z America/Halifax -4:14:24 - LMT 1902 Jun 15 +-4 H A%sT 1918 +-4 C A%sT 1919 +-4 H A%sT 1942 F 9 2s +-4 C A%sT 1946 +-4 H A%sT 1974 +-4 C A%sT +Z America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 +-4 C A%sT 1953 +-4 H A%sT 1954 +-4 - AST 1972 +-4 H A%sT 1974 +-4 C A%sT +R o 1933 1935 - Jun Su>=8 1 1 D +R o 1933 1935 - S Su>=8 1 0 S +R o 1936 1938 - Jun Su>=1 1 1 D +R o 1936 1938 - S Su>=1 1 0 S +R o 1939 o - May 27 1 1 D +R o 1939 1941 - S Sa>=21 1 0 S +R o 1940 o - May 19 1 1 D +R o 1941 o - May 4 1 1 D +R o 1946 1972 - Ap lastSu 2 1 D +R o 1946 1956 - S lastSu 2 0 S +R o 1957 1972 - O lastSu 2 0 S +R o 1993 2006 - Ap Su>=1 0:1 1 D +R o 1993 2006 - O lastSu 0:1 0 S +Z America/Moncton -4:19:8 - LMT 1883 D 9 +-5 - EST 1902 Jun 15 +-4 C A%sT 1933 +-4 o A%sT 1942 +-4 C A%sT 1946 +-4 o A%sT 1973 +-4 C A%sT 1993 +-4 o A%sT 2007 +-4 C A%sT +R t 1919 o - Mar 30 23:30 1 D +R t 1919 o - O 26 0 0 S +R t 1920 o - May 2 2 1 D +R t 1920 o - S 26 0 0 S +R t 1921 o - May 15 2 1 D +R t 1921 o - S 15 2 0 S +R t 1922 1923 - May Su>=8 2 1 D +R t 1922 1926 - S Su>=15 2 0 S +R t 1924 1927 - May Su>=1 2 1 D +R t 1927 1937 - S Su>=25 2 0 S +R t 1928 1937 - Ap Su>=25 2 1 D +R t 1938 1940 - Ap lastSu 2 1 D +R t 1938 1939 - S lastSu 2 0 S +R t 1945 1946 - S lastSu 2 0 S +R t 1946 o - Ap lastSu 2 1 D +R t 1947 1949 - Ap lastSu 0 1 D +R t 1947 1948 - S lastSu 0 0 S +R t 1949 o - N lastSu 0 0 S +R t 1950 1973 - Ap lastSu 2 1 D +R t 1950 o - N lastSu 2 0 S +R t 1951 1956 - S lastSu 2 0 S +R t 1957 1973 - O lastSu 2 0 S +Z America/Toronto -5:17:32 - LMT 1895 +-5 C E%sT 1919 +-5 t E%sT 1942 F 9 2s +-5 C E%sT 1946 +-5 t E%sT 1974 +-5 C E%sT +L America/Toronto America/Nassau +Z America/Thunder_Bay -5:57 - LMT 1895 +-6 - CST 1910 +-5 - EST 1942 +-5 C E%sT 1970 +-5 t E%sT 1973 +-5 - EST 1974 +-5 C E%sT +Z America/Nipigon -5:53:4 - LMT 1895 +-5 C E%sT 1940 S 29 +-5 1 EDT 1942 F 9 2s +-5 C E%sT +Z America/Rainy_River -6:18:16 - LMT 1895 +-6 C C%sT 1940 S 29 +-6 1 CDT 1942 F 9 2s +-6 C C%sT +R W 1916 o - Ap 23 0 1 D +R W 1916 o - S 17 0 0 S +R W 1918 o - Ap 14 2 1 D +R W 1918 o - O 27 2 0 S +R W 1937 o - May 16 2 1 D +R W 1937 o - S 26 2 0 S +R W 1942 o - F 9 2 1 W +R W 1945 o - Au 14 23u 1 P +R W 1945 o - S lastSu 2 0 S +R W 1946 o - May 12 2 1 D +R W 1946 o - O 13 2 0 S +R W 1947 1949 - Ap lastSu 2 1 D +R W 1947 1949 - S lastSu 2 0 S +R W 1950 o - May 1 2 1 D +R W 1950 o - S 30 2 0 S +R W 1951 1960 - Ap lastSu 2 1 D +R W 1951 1958 - S lastSu 2 0 S +R W 1959 o - O lastSu 2 0 S +R W 1960 o - S lastSu 2 0 S +R W 1963 o - Ap lastSu 2 1 D +R W 1963 o - S 22 2 0 S +R W 1966 1986 - Ap lastSu 2s 1 D +R W 1966 2005 - O lastSu 2s 0 S +R W 1987 2005 - Ap Su>=1 2s 1 D +Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 +-6 W C%sT 2006 +-6 C C%sT +R r 1918 o - Ap 14 2 1 D +R r 1918 o - O 27 2 0 S +R r 1930 1934 - May Su>=1 0 1 D +R r 1930 1934 - O Su>=1 0 0 S +R r 1937 1941 - Ap Su>=8 0 1 D +R r 1937 o - O Su>=8 0 0 S +R r 1938 o - O Su>=1 0 0 S +R r 1939 1941 - O Su>=8 0 0 S +R r 1942 o - F 9 2 1 W +R r 1945 o - Au 14 23u 1 P +R r 1945 o - S lastSu 2 0 S +R r 1946 o - Ap Su>=8 2 1 D +R r 1946 o - O Su>=8 2 0 S +R r 1947 1957 - Ap lastSu 2 1 D +R r 1947 1957 - S lastSu 2 0 S +R r 1959 o - Ap lastSu 2 1 D +R r 1959 o - O lastSu 2 0 S +R Sw 1957 o - Ap lastSu 2 1 D +R Sw 1957 o - O lastSu 2 0 S +R Sw 1959 1961 - Ap lastSu 2 1 D +R Sw 1959 o - O lastSu 2 0 S +R Sw 1960 1961 - S lastSu 2 0 S +Z America/Regina -6:58:36 - LMT 1905 S +-7 r M%sT 1960 Ap lastSu 2 +-6 - CST +Z America/Swift_Current -7:11:20 - LMT 1905 S +-7 C M%sT 1946 Ap lastSu 2 +-7 r M%sT 1950 +-7 Sw M%sT 1972 Ap lastSu 2 +-6 - CST +R Ed 1918 1919 - Ap Su>=8 2 1 D +R Ed 1918 o - O 27 2 0 S +R Ed 1919 o - May 27 2 0 S +R Ed 1920 1923 - Ap lastSu 2 1 D +R Ed 1920 o - O lastSu 2 0 S +R Ed 1921 1923 - S lastSu 2 0 S +R Ed 1942 o - F 9 2 1 W +R Ed 1945 o - Au 14 23u 1 P +R Ed 1945 o - S lastSu 2 0 S +R Ed 1947 o - Ap lastSu 2 1 D +R Ed 1947 o - S lastSu 2 0 S +R Ed 1972 1986 - Ap lastSu 2 1 D +R Ed 1972 2006 - O lastSu 2 0 S +Z America/Edmonton -7:33:52 - LMT 1906 S +-7 Ed M%sT 1987 +-7 C M%sT +R Va 1918 o - Ap 14 2 1 D +R Va 1918 o - O 27 2 0 S +R Va 1942 o - F 9 2 1 W +R Va 1945 o - Au 14 23u 1 P +R Va 1945 o - S 30 2 0 S +R Va 1946 1986 - Ap lastSu 2 1 D +R Va 1946 o - S 29 2 0 S +R Va 1947 1961 - S lastSu 2 0 S +R Va 1962 2006 - O lastSu 2 0 S +Z America/Vancouver -8:12:28 - LMT 1884 +-8 Va P%sT 1987 +-8 C P%sT +Z America/Dawson_Creek -8:0:56 - LMT 1884 +-8 C P%sT 1947 +-8 Va P%sT 1972 Au 30 2 +-7 - MST +Z America/Fort_Nelson -8:10:47 - LMT 1884 +-8 Va P%sT 1946 +-8 - PST 1947 +-8 Va P%sT 1987 +-8 C P%sT 2015 Mar 8 2 +-7 - MST +R Y 1918 o - Ap 14 2 1 D +R Y 1918 o - O 27 2 0 S +R Y 1919 o - May 25 2 1 D +R Y 1919 o - N 1 0 0 S +R Y 1942 o - F 9 2 1 W +R Y 1945 o - Au 14 23u 1 P +R Y 1945 o - S 30 2 0 S +R Y 1965 o - Ap lastSu 0 2 DD +R Y 1965 o - O lastSu 2 0 S +R Y 1980 1986 - Ap lastSu 2 1 D +R Y 1980 2006 - O lastSu 2 0 S +R Y 1987 2006 - Ap Su>=1 2 1 D +Z America/Pangnirtung 0 - -00 1921 +-4 Y A%sT 1995 Ap Su>=1 2 +-5 C E%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 C E%sT +Z America/Iqaluit 0 - -00 1942 Au +-5 Y E%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 C E%sT +Z America/Resolute 0 - -00 1947 Au 31 +-6 Y C%sT 2000 O 29 2 +-5 - EST 2001 Ap 1 3 +-6 C C%sT 2006 O 29 2 +-5 - EST 2007 Mar 11 3 +-6 C C%sT +Z America/Rankin_Inlet 0 - -00 1957 +-6 Y C%sT 2000 O 29 2 +-5 - EST 2001 Ap 1 3 +-6 C C%sT +Z America/Cambridge_Bay 0 - -00 1920 +-7 Y M%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 - EST 2000 N 5 +-6 - CST 2001 Ap 1 3 +-7 C M%sT +Z America/Yellowknife 0 - -00 1935 +-7 Y M%sT 1980 +-7 C M%sT +Z America/Inuvik 0 - -00 1953 +-8 Y P%sT 1979 Ap lastSu 2 +-7 Y M%sT 1980 +-7 C M%sT +Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 +-9 Y Y%sT 1967 May 28 +-8 Y P%sT 1980 +-8 C P%sT 2020 N +-7 - MST +Z America/Dawson -9:17:40 - LMT 1900 Au 20 +-9 Y Y%sT 1973 O 28 +-8 Y P%sT 1980 +-8 C P%sT 2020 N +-7 - MST +R m 1939 o - F 5 0 1 D +R m 1939 o - Jun 25 0 0 S +R m 1940 o - D 9 0 1 D +R m 1941 o - Ap 1 0 0 S +R m 1943 o - D 16 0 1 W +R m 1944 o - May 1 0 0 S +R m 1950 o - F 12 0 1 D +R m 1950 o - Jul 30 0 0 S +R m 1996 2000 - Ap Su>=1 2 1 D +R m 1996 2000 - O lastSu 2 0 S +R m 2001 o - May Su>=1 2 1 D +R m 2001 o - S lastSu 2 0 S +R m 2002 ma - Ap Su>=1 2 1 D +R m 2002 ma - O lastSu 2 0 S +Z America/Cancun -5:47:4 - LMT 1922 Ja 1 0:12:56 +-6 - CST 1981 D 23 +-5 m E%sT 1998 Au 2 2 +-6 m C%sT 2015 F 1 2 +-5 - EST +Z America/Merida -5:58:28 - LMT 1922 Ja 1 0:1:32 +-6 - CST 1981 D 23 +-5 - EST 1982 D 2 +-6 m C%sT +Z America/Matamoros -6:40 - LMT 1921 D 31 23:20 +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT 2010 +-6 u C%sT +Z America/Monterrey -6:41:16 - LMT 1921 D 31 23:18:44 +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT +Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 0:23:24 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 m C%sT 2001 S 30 2 +-6 - CST 2002 F 20 +-6 m C%sT +Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 0:2:20 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Su>=1 3 +-7 m M%sT 2010 +-7 u M%sT +Z America/Chihuahua -7:4:20 - LMT 1921 D 31 23:55:40 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Su>=1 3 +-7 m M%sT +Z America/Hermosillo -7:23:52 - LMT 1921 D 31 23:36:8 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 1999 +-7 - MST +Z America/Mazatlan -7:5:40 - LMT 1921 D 31 23:54:20 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT +Z America/Bahia_Banderas -7:1 - LMT 1921 D 31 23:59 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 2010 Ap 4 2 +-6 m C%sT +Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 +-7 - MST 1924 +-8 - PST 1927 Jun 10 23 +-7 - MST 1930 N 15 +-8 - PST 1931 Ap +-8 1 PDT 1931 S 30 +-8 - PST 1942 Ap 24 +-8 1 PWT 1945 Au 14 23u +-8 1 PPT 1945 N 12 +-8 - PST 1948 Ap 5 +-8 1 PDT 1949 Ja 14 +-8 - PST 1954 +-8 CA P%sT 1961 +-8 - PST 1976 +-8 u P%sT 1996 +-8 m P%sT 2001 +-8 u P%sT 2002 F 20 +-8 m P%sT 2010 +-8 u P%sT +R BB 1942 o - Ap 19 5u 1 D +R BB 1942 o - Au 31 6u 0 S +R BB 1943 o - May 2 5u 1 D +R BB 1943 o - S 5 6u 0 S +R BB 1944 o - Ap 10 5u 0:30 - +R BB 1944 o - S 10 6u 0 S +R BB 1977 o - Jun 12 2 1 D +R BB 1977 1978 - O Su>=1 2 0 S +R BB 1978 1980 - Ap Su>=15 2 1 D +R BB 1979 o - S 30 2 0 S +R BB 1980 o - S 25 2 0 S +Z America/Barbados -3:58:29 - LMT 1911 Au 28 +-4 BB A%sT 1944 +-4 BB AST/-0330 1945 +-4 BB A%sT +R BZ 1918 1941 - O Sa>=1 24 0:30 -0530 +R BZ 1919 1942 - F Sa>=8 24 0 CST +R BZ 1942 o - Jun 27 24 1 CWT +R BZ 1945 o - Au 14 23u 1 CPT +R BZ 1945 o - D 15 24 0 CST +R BZ 1947 1967 - O Sa>=1 24 0:30 -0530 +R BZ 1948 1968 - F Sa>=8 24 0 CST +R BZ 1973 o - D 5 0 1 CDT +R BZ 1974 o - F 9 0 0 CST +R BZ 1982 o - D 18 0 1 CDT +R BZ 1983 o - F 12 0 0 CST +Z America/Belize -5:52:48 - LMT 1912 Ap +-6 BZ %s +R Be 1917 o - Ap 5 24 1 - +R Be 1917 o - S 30 24 0 - +R Be 1918 o - Ap 13 24 1 - +R Be 1918 o - S 15 24 0 S +R Be 1942 o - Ja 11 2 1 D +R Be 1942 o - O 18 2 0 S +R Be 1943 o - Mar 21 2 1 D +R Be 1943 o - O 31 2 0 S +R Be 1944 1945 - Mar Su>=8 2 1 D +R Be 1944 1945 - N Su>=1 2 0 S +R Be 1947 o - May Su>=15 2 1 D +R Be 1947 o - S Su>=8 2 0 S +R Be 1948 1952 - May Su>=22 2 1 D +R Be 1948 1952 - S Su>=1 2 0 S +R Be 1956 o - May Su>=22 2 1 D +R Be 1956 o - O lastSu 2 0 S +Z Atlantic/Bermuda -4:19:18 - LMT 1890 +-4:19:18 Be BMT/BST 1930 Ja 1 2 +-4 Be A%sT 1974 Ap 28 2 +-4 C A%sT 1976 +-4 u A%sT +R CR 1979 1980 - F lastSu 0 1 D +R CR 1979 1980 - Jun Su>=1 0 0 S +R CR 1991 1992 - Ja Sa>=15 0 1 D +R CR 1991 o - Jul 1 0 0 S +R CR 1992 o - Mar 15 0 0 S +Z America/Costa_Rica -5:36:13 - LMT 1890 +-5:36:13 - SJMT 1921 Ja 15 +-6 CR C%sT +R Q 1928 o - Jun 10 0 1 D +R Q 1928 o - O 10 0 0 S +R Q 1940 1942 - Jun Su>=1 0 1 D +R Q 1940 1942 - S Su>=1 0 0 S +R Q 1945 1946 - Jun Su>=1 0 1 D +R Q 1945 1946 - S Su>=1 0 0 S +R Q 1965 o - Jun 1 0 1 D +R Q 1965 o - S 30 0 0 S +R Q 1966 o - May 29 0 1 D +R Q 1966 o - O 2 0 0 S +R Q 1967 o - Ap 8 0 1 D +R Q 1967 1968 - S Su>=8 0 0 S +R Q 1968 o - Ap 14 0 1 D +R Q 1969 1977 - Ap lastSu 0 1 D +R Q 1969 1971 - O lastSu 0 0 S +R Q 1972 1974 - O 8 0 0 S +R Q 1975 1977 - O lastSu 0 0 S +R Q 1978 o - May 7 0 1 D +R Q 1978 1990 - O Su>=8 0 0 S +R Q 1979 1980 - Mar Su>=15 0 1 D +R Q 1981 1985 - May Su>=5 0 1 D +R Q 1986 1989 - Mar Su>=14 0 1 D +R Q 1990 1997 - Ap Su>=1 0 1 D +R Q 1991 1995 - O Su>=8 0s 0 S +R Q 1996 o - O 6 0s 0 S +R Q 1997 o - O 12 0s 0 S +R Q 1998 1999 - Mar lastSu 0s 1 D +R Q 1998 2003 - O lastSu 0s 0 S +R Q 2000 2003 - Ap Su>=1 0s 1 D +R Q 2004 o - Mar lastSu 0s 1 D +R Q 2006 2010 - O lastSu 0s 0 S +R Q 2007 o - Mar Su>=8 0s 1 D +R Q 2008 o - Mar Su>=15 0s 1 D +R Q 2009 2010 - Mar Su>=8 0s 1 D +R Q 2011 o - Mar Su>=15 0s 1 D +R Q 2011 o - N 13 0s 0 S +R Q 2012 o - Ap 1 0s 1 D +R Q 2012 ma - N Su>=1 0s 0 S +R Q 2013 ma - Mar Su>=8 0s 1 D +Z America/Havana -5:29:28 - LMT 1890 +-5:29:36 - HMT 1925 Jul 19 12 +-5 Q C%sT +R DO 1966 o - O 30 0 1 EDT +R DO 1967 o - F 28 0 0 EST +R DO 1969 1973 - O lastSu 0 0:30 -0430 +R DO 1970 o - F 21 0 0 EST +R DO 1971 o - Ja 20 0 0 EST +R DO 1972 1974 - Ja 21 0 0 EST +Z America/Santo_Domingo -4:39:36 - LMT 1890 +-4:40 - SDMT 1933 Ap 1 12 +-5 DO %s 1974 O 27 +-4 - AST 2000 O 29 2 +-5 u E%sT 2000 D 3 1 +-4 - AST +R SV 1987 1988 - May Su>=1 0 1 D +R SV 1987 1988 - S lastSu 0 0 S +Z America/El_Salvador -5:56:48 - LMT 1921 +-6 SV C%sT +R GT 1973 o - N 25 0 1 D +R GT 1974 o - F 24 0 0 S +R GT 1983 o - May 21 0 1 D +R GT 1983 o - S 22 0 0 S +R GT 1991 o - Mar 23 0 1 D +R GT 1991 o - S 7 0 0 S +R GT 2006 o - Ap 30 0 1 D +R GT 2006 o - O 1 0 0 S +Z America/Guatemala -6:2:4 - LMT 1918 O 5 +-6 GT C%sT +R HT 1983 o - May 8 0 1 D +R HT 1984 1987 - Ap lastSu 0 1 D +R HT 1983 1987 - O lastSu 0 0 S +R HT 1988 1997 - Ap Su>=1 1s 1 D +R HT 1988 1997 - O lastSu 1s 0 S +R HT 2005 2006 - Ap Su>=1 0 1 D +R HT 2005 2006 - O lastSu 0 0 S +R HT 2012 2015 - Mar Su>=8 2 1 D +R HT 2012 2015 - N Su>=1 2 0 S +R HT 2017 ma - Mar Su>=8 2 1 D +R HT 2017 ma - N Su>=1 2 0 S +Z America/Port-au-Prince -4:49:20 - LMT 1890 +-4:49 - PPMT 1917 Ja 24 12 +-5 HT E%sT +R HN 1987 1988 - May Su>=1 0 1 D +R HN 1987 1988 - S lastSu 0 0 S +R HN 2006 o - May Su>=1 0 1 D +R HN 2006 o - Au M>=1 0 0 S +Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap +-6 HN C%sT +Z America/Jamaica -5:7:10 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1974 +-5 u E%sT 1984 +-5 - EST +Z America/Martinique -4:4:20 - LMT 1890 +-4:4:20 - FFMT 1911 May +-4 - AST 1980 Ap 6 +-4 1 ADT 1980 S 28 +-4 - AST +R NI 1979 1980 - Mar Su>=16 0 1 D +R NI 1979 1980 - Jun M>=23 0 0 S +R NI 2005 o - Ap 10 0 1 D +R NI 2005 o - O Su>=1 0 0 S +R NI 2006 o - Ap 30 2 1 D +R NI 2006 o - O Su>=1 1 0 S +Z America/Managua -5:45:8 - LMT 1890 +-5:45:12 - MMT 1934 Jun 23 +-6 - CST 1973 May +-5 - EST 1975 F 16 +-6 NI C%sT 1992 Ja 1 4 +-5 - EST 1992 S 24 +-6 - CST 1993 +-5 - EST 1997 +-6 NI C%sT +Z America/Panama -5:18:8 - LMT 1890 +-5:19:36 - CMT 1908 Ap 22 +-5 - EST +L America/Panama America/Atikokan +L America/Panama America/Cayman +Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 +-4 - AST 1942 May 3 +-4 u A%sT 1946 +-4 - AST +L America/Puerto_Rico America/Anguilla +L America/Puerto_Rico America/Antigua +L America/Puerto_Rico America/Aruba +L America/Puerto_Rico America/Curacao +L America/Puerto_Rico America/Blanc-Sablon +L America/Puerto_Rico America/Dominica +L America/Puerto_Rico America/Grenada +L America/Puerto_Rico America/Guadeloupe +L America/Puerto_Rico America/Kralendijk +L America/Puerto_Rico America/Lower_Princes +L America/Puerto_Rico America/Marigot +L America/Puerto_Rico America/Montserrat +L America/Puerto_Rico America/Port_of_Spain +L America/Puerto_Rico America/St_Barthelemy +L America/Puerto_Rico America/St_Kitts +L America/Puerto_Rico America/St_Lucia +L America/Puerto_Rico America/St_Thomas +L America/Puerto_Rico America/St_Vincent +L America/Puerto_Rico America/Tortola +Z America/Miquelon -3:44:40 - LMT 1911 May 15 +-4 - AST 1980 May +-3 - -03 1987 +-3 C -03/-02 +Z America/Grand_Turk -4:44:32 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1979 +-5 u E%sT 2015 Mar 8 2 +-4 - AST 2018 Mar 11 3 +-5 u E%sT +R A 1930 o - D 1 0 1 - +R A 1931 o - Ap 1 0 0 - +R A 1931 o - O 15 0 1 - +R A 1932 1940 - Mar 1 0 0 - +R A 1932 1939 - N 1 0 1 - +R A 1940 o - Jul 1 0 1 - +R A 1941 o - Jun 15 0 0 - +R A 1941 o - O 15 0 1 - +R A 1943 o - Au 1 0 0 - +R A 1943 o - O 15 0 1 - +R A 1946 o - Mar 1 0 0 - +R A 1946 o - O 1 0 1 - +R A 1963 o - O 1 0 0 - +R A 1963 o - D 15 0 1 - +R A 1964 1966 - Mar 1 0 0 - +R A 1964 1966 - O 15 0 1 - +R A 1967 o - Ap 2 0 0 - +R A 1967 1968 - O Su>=1 0 1 - +R A 1968 1969 - Ap Su>=1 0 0 - +R A 1974 o - Ja 23 0 1 - +R A 1974 o - May 1 0 0 - +R A 1988 o - D 1 0 1 - +R A 1989 1993 - Mar Su>=1 0 0 - +R A 1989 1992 - O Su>=15 0 1 - +R A 1999 o - O Su>=1 0 1 - +R A 2000 o - Mar 3 0 0 - +R A 2007 o - D 30 0 1 - +R A 2008 2009 - Mar Su>=15 0 0 - +R A 2008 o - O Su>=15 0 1 - +Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 +Z America/Argentina/Cordoba -4:16:48 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 +Z America/Argentina/Salta -4:21:40 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Tucuman -4:20:52 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 13 +-3 A -03/-02 +Z America/Argentina/La_Rioja -4:27:24 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar +-4 - -04 1991 May 7 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/San_Juan -4:34:4 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar +-4 - -04 1991 May 7 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 31 +-4 - -04 2004 Jul 25 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Jujuy -4:21:12 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 Mar 4 +-4 - -04 1990 O 28 +-4 1 -03 1991 Mar 17 +-4 - -04 1991 O 6 +-3 1 -02 1992 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Catamarca -4:23:8 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Mendoza -4:35:16 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 Mar 4 +-4 - -04 1990 O 15 +-4 1 -03 1991 Mar +-4 - -04 1991 O 15 +-4 1 -03 1992 Mar +-4 - -04 1992 O 18 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 23 +-4 - -04 2004 S 26 +-3 A -03/-02 2008 O 18 +-3 - -03 +R Sa 2008 2009 - Mar Su>=8 0 0 - +R Sa 2007 2008 - O Su>=8 0 1 - +Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 +-3 1 -02 1990 Mar 14 +-4 - -04 1990 O 15 +-4 1 -03 1991 Mar +-4 - -04 1991 Jun +-3 - -03 1999 O 3 +-4 1 -03 2000 Mar 3 +-3 - -03 2004 May 31 +-4 - -04 2004 Jul 25 +-3 A -03/-02 2008 Ja 21 +-4 Sa -04/-03 2009 O 11 +-3 - -03 +Z America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Ushuaia -4:33:12 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 30 +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/La_Paz -4:32:36 - LMT 1890 +-4:32:36 - CMT 1931 O 15 +-4:32:36 1 BST 1932 Mar 21 +-4 - -04 +R B 1931 o - O 3 11 1 - +R B 1932 1933 - Ap 1 0 0 - +R B 1932 o - O 3 0 1 - +R B 1949 1952 - D 1 0 1 - +R B 1950 o - Ap 16 1 0 - +R B 1951 1952 - Ap 1 0 0 - +R B 1953 o - Mar 1 0 0 - +R B 1963 o - D 9 0 1 - +R B 1964 o - Mar 1 0 0 - +R B 1965 o - Ja 31 0 1 - +R B 1965 o - Mar 31 0 0 - +R B 1965 o - D 1 0 1 - +R B 1966 1968 - Mar 1 0 0 - +R B 1966 1967 - N 1 0 1 - +R B 1985 o - N 2 0 1 - +R B 1986 o - Mar 15 0 0 - +R B 1986 o - O 25 0 1 - +R B 1987 o - F 14 0 0 - +R B 1987 o - O 25 0 1 - +R B 1988 o - F 7 0 0 - +R B 1988 o - O 16 0 1 - +R B 1989 o - Ja 29 0 0 - +R B 1989 o - O 15 0 1 - +R B 1990 o - F 11 0 0 - +R B 1990 o - O 21 0 1 - +R B 1991 o - F 17 0 0 - +R B 1991 o - O 20 0 1 - +R B 1992 o - F 9 0 0 - +R B 1992 o - O 25 0 1 - +R B 1993 o - Ja 31 0 0 - +R B 1993 1995 - O Su>=11 0 1 - +R B 1994 1995 - F Su>=15 0 0 - +R B 1996 o - F 11 0 0 - +R B 1996 o - O 6 0 1 - +R B 1997 o - F 16 0 0 - +R B 1997 o - O 6 0 1 - +R B 1998 o - Mar 1 0 0 - +R B 1998 o - O 11 0 1 - +R B 1999 o - F 21 0 0 - +R B 1999 o - O 3 0 1 - +R B 2000 o - F 27 0 0 - +R B 2000 2001 - O Su>=8 0 1 - +R B 2001 2006 - F Su>=15 0 0 - +R B 2002 o - N 3 0 1 - +R B 2003 o - O 19 0 1 - +R B 2004 o - N 2 0 1 - +R B 2005 o - O 16 0 1 - +R B 2006 o - N 5 0 1 - +R B 2007 o - F 25 0 0 - +R B 2007 o - O Su>=8 0 1 - +R B 2008 2017 - O Su>=15 0 1 - +R B 2008 2011 - F Su>=15 0 0 - +R B 2012 o - F Su>=22 0 0 - +R B 2013 2014 - F Su>=15 0 0 - +R B 2015 o - F Su>=22 0 0 - +R B 2016 2019 - F Su>=15 0 0 - +R B 2018 o - N Su>=1 0 1 - +Z America/Noronha -2:9:40 - LMT 1914 +-2 B -02/-01 1990 S 17 +-2 - -02 1999 S 30 +-2 B -02/-01 2000 O 15 +-2 - -02 2001 S 13 +-2 B -02/-01 2002 O +-2 - -02 +Z America/Belem -3:13:56 - LMT 1914 +-3 B -03/-02 1988 S 12 +-3 - -03 +Z America/Santarem -3:38:48 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 2008 Jun 24 +-3 - -03 +Z America/Fortaleza -2:34 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 22 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Recife -2:19:36 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 15 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Araguaina -3:12:48 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1995 S 14 +-3 B -03/-02 2003 S 24 +-3 - -03 2012 O 21 +-3 B -03/-02 2013 S +-3 - -03 +Z America/Maceio -2:22:52 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1995 O 13 +-3 B -03/-02 1996 S 4 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 22 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Bahia -2:34:4 - LMT 1914 +-3 B -03/-02 2003 S 24 +-3 - -03 2011 O 16 +-3 B -03/-02 2012 O 21 +-3 - -03 +Z America/Sao_Paulo -3:6:28 - LMT 1914 +-3 B -03/-02 1963 O 23 +-3 1 -02 1964 +-3 B -03/-02 +Z America/Campo_Grande -3:38:28 - LMT 1914 +-4 B -04/-03 +Z America/Cuiaba -3:44:20 - LMT 1914 +-4 B -04/-03 2003 S 24 +-4 - -04 2004 O +-4 B -04/-03 +Z America/Porto_Velho -4:15:36 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 +Z America/Boa_Vista -4:2:40 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 1999 S 30 +-4 B -04/-03 2000 O 15 +-4 - -04 +Z America/Manaus -4:0:4 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 1993 S 28 +-4 B -04/-03 1994 S 22 +-4 - -04 +Z America/Eirunepe -4:39:28 - LMT 1914 +-5 B -05/-04 1988 S 12 +-5 - -05 1993 S 28 +-5 B -05/-04 1994 S 22 +-5 - -05 2008 Jun 24 +-4 - -04 2013 N 10 +-5 - -05 +Z America/Rio_Branco -4:31:12 - LMT 1914 +-5 B -05/-04 1988 S 12 +-5 - -05 2008 Jun 24 +-4 - -04 2013 N 10 +-5 - -05 +R x 1927 1931 - S 1 0 1 - +R x 1928 1932 - Ap 1 0 0 - +R x 1968 o - N 3 4u 1 - +R x 1969 o - Mar 30 3u 0 - +R x 1969 o - N 23 4u 1 - +R x 1970 o - Mar 29 3u 0 - +R x 1971 o - Mar 14 3u 0 - +R x 1970 1972 - O Su>=9 4u 1 - +R x 1972 1986 - Mar Su>=9 3u 0 - +R x 1973 o - S 30 4u 1 - +R x 1974 1987 - O Su>=9 4u 1 - +R x 1987 o - Ap 12 3u 0 - +R x 1988 1990 - Mar Su>=9 3u 0 - +R x 1988 1989 - O Su>=9 4u 1 - +R x 1990 o - S 16 4u 1 - +R x 1991 1996 - Mar Su>=9 3u 0 - +R x 1991 1997 - O Su>=9 4u 1 - +R x 1997 o - Mar 30 3u 0 - +R x 1998 o - Mar Su>=9 3u 0 - +R x 1998 o - S 27 4u 1 - +R x 1999 o - Ap 4 3u 0 - +R x 1999 2010 - O Su>=9 4u 1 - +R x 2000 2007 - Mar Su>=9 3u 0 - +R x 2008 o - Mar 30 3u 0 - +R x 2009 o - Mar Su>=9 3u 0 - +R x 2010 o - Ap Su>=1 3u 0 - +R x 2011 o - May Su>=2 3u 0 - +R x 2011 o - Au Su>=16 4u 1 - +R x 2012 2014 - Ap Su>=23 3u 0 - +R x 2012 2014 - S Su>=2 4u 1 - +R x 2016 2018 - May Su>=9 3u 0 - +R x 2016 2018 - Au Su>=9 4u 1 - +R x 2019 ma - Ap Su>=2 3u 0 - +R x 2019 ma - S Su>=2 4u 1 - +Z America/Santiago -4:42:45 - LMT 1890 +-4:42:45 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:45 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:45 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1946 Jul 15 +-4 1 -03 1946 S +-4 - -04 1947 Ap +-5 - -05 1947 May 21 23 +-4 x -04/-03 +Z America/Punta_Arenas -4:43:40 - LMT 1890 +-4:42:45 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:45 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:45 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1947 Ap +-5 - -05 1947 May 21 23 +-4 x -04/-03 2016 D 4 +-3 - -03 +Z Pacific/Easter -7:17:28 - LMT 1890 +-7:17:28 - EMT 1932 S +-7 x -07/-06 1982 Mar 14 3u +-6 x -06/-05 +Z Antarctica/Palmer 0 - -00 1965 +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1982 May +-4 x -04/-03 2016 D 4 +-3 - -03 +R CO 1992 o - May 3 0 1 - +R CO 1993 o - Ap 4 0 0 - +Z America/Bogota -4:56:16 - LMT 1884 Mar 13 +-4:56:16 - BMT 1914 N 23 +-5 CO -05/-04 +R EC 1992 o - N 28 0 1 - +R EC 1993 o - F 5 0 0 - +Z America/Guayaquil -5:19:20 - LMT 1890 +-5:14 - QMT 1931 +-5 EC -05/-04 +Z Pacific/Galapagos -5:58:24 - LMT 1931 +-5 - -05 1986 +-6 EC -06/-05 +R FK 1937 1938 - S lastSu 0 1 - +R FK 1938 1942 - Mar Su>=19 0 0 - +R FK 1939 o - O 1 0 1 - +R FK 1940 1942 - S lastSu 0 1 - +R FK 1943 o - Ja 1 0 0 - +R FK 1983 o - S lastSu 0 1 - +R FK 1984 1985 - Ap lastSu 0 0 - +R FK 1984 o - S 16 0 1 - +R FK 1985 2000 - S Su>=9 0 1 - +R FK 1986 2000 - Ap Su>=16 0 0 - +R FK 2001 2010 - Ap Su>=15 2 0 - +R FK 2001 2010 - S Su>=1 2 1 - +Z Atlantic/Stanley -3:51:24 - LMT 1890 +-3:51:24 - SMT 1912 Mar 12 +-4 FK -04/-03 1983 May +-3 FK -03/-02 1985 S 15 +-4 FK -04/-03 2010 S 5 2 +-3 - -03 +Z America/Cayenne -3:29:20 - LMT 1911 Jul +-4 - -04 1967 O +-3 - -03 +Z America/Guyana -3:52:39 - LMT 1911 Au +-4 - -04 1915 Mar +-3:45 - -0345 1975 Au +-3 - -03 1992 Mar 29 1 +-4 - -04 +R y 1975 1988 - O 1 0 1 - +R y 1975 1978 - Mar 1 0 0 - +R y 1979 1991 - Ap 1 0 0 - +R y 1989 o - O 22 0 1 - +R y 1990 o - O 1 0 1 - +R y 1991 o - O 6 0 1 - +R y 1992 o - Mar 1 0 0 - +R y 1992 o - O 5 0 1 - +R y 1993 o - Mar 31 0 0 - +R y 1993 1995 - O 1 0 1 - +R y 1994 1995 - F lastSu 0 0 - +R y 1996 o - Mar 1 0 0 - +R y 1996 2001 - O Su>=1 0 1 - +R y 1997 o - F lastSu 0 0 - +R y 1998 2001 - Mar Su>=1 0 0 - +R y 2002 2004 - Ap Su>=1 0 0 - +R y 2002 2003 - S Su>=1 0 1 - +R y 2004 2009 - O Su>=15 0 1 - +R y 2005 2009 - Mar Su>=8 0 0 - +R y 2010 ma - O Su>=1 0 1 - +R y 2010 2012 - Ap Su>=8 0 0 - +R y 2013 ma - Mar Su>=22 0 0 - +Z America/Asuncion -3:50:40 - LMT 1890 +-3:50:40 - AMT 1931 O 10 +-4 - -04 1972 O +-3 - -03 1974 Ap +-4 y -04/-03 +R PE 1938 o - Ja 1 0 1 - +R PE 1938 o - Ap 1 0 0 - +R PE 1938 1939 - S lastSu 0 1 - +R PE 1939 1940 - Mar Su>=24 0 0 - +R PE 1986 1987 - Ja 1 0 1 - +R PE 1986 1987 - Ap 1 0 0 - +R PE 1990 o - Ja 1 0 1 - +R PE 1990 o - Ap 1 0 0 - +R PE 1994 o - Ja 1 0 1 - +R PE 1994 o - Ap 1 0 0 - +Z America/Lima -5:8:12 - LMT 1890 +-5:8:36 - LMT 1908 Jul 28 +-5 PE -05/-04 +Z Atlantic/South_Georgia -2:26:8 - LMT 1890 +-2 - -02 +Z America/Paramaribo -3:40:40 - LMT 1911 +-3:40:52 - PMT 1935 +-3:40:36 - PMT 1945 O +-3:30 - -0330 1984 O +-3 - -03 +R U 1923 1925 - O 1 0 0:30 - +R U 1924 1926 - Ap 1 0 0 - +R U 1933 1938 - O lastSu 0 0:30 - +R U 1934 1941 - Mar lastSa 24 0 - +R U 1939 o - O 1 0 0:30 - +R U 1940 o - O 27 0 0:30 - +R U 1941 o - Au 1 0 0:30 - +R U 1942 o - D 14 0 0:30 - +R U 1943 o - Mar 14 0 0 - +R U 1959 o - May 24 0 0:30 - +R U 1959 o - N 15 0 0 - +R U 1960 o - Ja 17 0 1 - +R U 1960 o - Mar 6 0 0 - +R U 1965 o - Ap 4 0 1 - +R U 1965 o - S 26 0 0 - +R U 1968 o - May 27 0 0:30 - +R U 1968 o - D 1 0 0 - +R U 1970 o - Ap 25 0 1 - +R U 1970 o - Jun 14 0 0 - +R U 1972 o - Ap 23 0 1 - +R U 1972 o - Jul 16 0 0 - +R U 1974 o - Ja 13 0 1:30 - +R U 1974 o - Mar 10 0 0:30 - +R U 1974 o - S 1 0 0 - +R U 1974 o - D 22 0 1 - +R U 1975 o - Mar 30 0 0 - +R U 1976 o - D 19 0 1 - +R U 1977 o - Mar 6 0 0 - +R U 1977 o - D 4 0 1 - +R U 1978 1979 - Mar Su>=1 0 0 - +R U 1978 o - D 17 0 1 - +R U 1979 o - Ap 29 0 1 - +R U 1980 o - Mar 16 0 0 - +R U 1987 o - D 14 0 1 - +R U 1988 o - F 28 0 0 - +R U 1988 o - D 11 0 1 - +R U 1989 o - Mar 5 0 0 - +R U 1989 o - O 29 0 1 - +R U 1990 o - F 25 0 0 - +R U 1990 1991 - O Su>=21 0 1 - +R U 1991 1992 - Mar Su>=1 0 0 - +R U 1992 o - O 18 0 1 - +R U 1993 o - F 28 0 0 - +R U 2004 o - S 19 0 1 - +R U 2005 o - Mar 27 2 0 - +R U 2005 o - O 9 2 1 - +R U 2006 2015 - Mar Su>=8 2 0 - +R U 2006 2014 - O Su>=1 2 1 - +Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 +-3:44:51 - MMT 1920 May +-4 - -04 1923 O +-3:30 U -0330/-03 1942 D 14 +-3 U -03/-0230 1960 +-3 U -03/-02 1968 +-3 U -03/-0230 1970 +-3 U -03/-02 1974 +-3 U -03/-0130 1974 Mar 10 +-3 U -03/-0230 1974 D 22 +-3 U -03/-02 +Z America/Caracas -4:27:44 - LMT 1890 +-4:27:40 - CMT 1912 F 12 +-4:30 - -0430 1965 +-4 - -04 2007 D 9 3 +-4:30 - -0430 2016 May 1 2:30 +-4 - -04 +Z Etc/GMT 0 - GMT +Z Etc/UTC 0 - UTC +L Etc/GMT GMT +L Etc/UTC Etc/Universal +L Etc/UTC Etc/Zulu +L Etc/GMT Etc/Greenwich +L Etc/GMT Etc/GMT-0 +L Etc/GMT Etc/GMT+0 +L Etc/GMT Etc/GMT0 +Z Etc/GMT-14 14 - +14 +Z Etc/GMT-13 13 - +13 +Z Etc/GMT-12 12 - +12 +Z Etc/GMT-11 11 - +11 +Z Etc/GMT-10 10 - +10 +Z Etc/GMT-9 9 - +09 +Z Etc/GMT-8 8 - +08 +Z Etc/GMT-7 7 - +07 +Z Etc/GMT-6 6 - +06 +Z Etc/GMT-5 5 - +05 +Z Etc/GMT-4 4 - +04 +Z Etc/GMT-3 3 - +03 +Z Etc/GMT-2 2 - +02 +Z Etc/GMT-1 1 - +01 +Z Etc/GMT+1 -1 - -01 +Z Etc/GMT+2 -2 - -02 +Z Etc/GMT+3 -3 - -03 +Z Etc/GMT+4 -4 - -04 +Z Etc/GMT+5 -5 - -05 +Z Etc/GMT+6 -6 - -06 +Z Etc/GMT+7 -7 - -07 +Z Etc/GMT+8 -8 - -08 +Z Etc/GMT+9 -9 - -09 +Z Etc/GMT+10 -10 - -10 +Z Etc/GMT+11 -11 - -11 +Z Etc/GMT+12 -12 - -12 +Z Factory 0 - -00 +L Africa/Nairobi Africa/Asmera +L Africa/Abidjan Africa/Timbuktu +L America/Argentina/Catamarca America/Argentina/ComodRivadavia +L America/Adak America/Atka +L America/Argentina/Buenos_Aires America/Buenos_Aires +L America/Argentina/Catamarca America/Catamarca +L America/Panama America/Coral_Harbour +L America/Argentina/Cordoba America/Cordoba +L America/Tijuana America/Ensenada +L America/Indiana/Indianapolis America/Fort_Wayne +L America/Nuuk America/Godthab +L America/Indiana/Indianapolis America/Indianapolis +L America/Argentina/Jujuy America/Jujuy +L America/Indiana/Knox America/Knox_IN +L America/Kentucky/Louisville America/Louisville +L America/Argentina/Mendoza America/Mendoza +L America/Toronto America/Montreal +L America/Rio_Branco America/Porto_Acre +L America/Argentina/Cordoba America/Rosario +L America/Tijuana America/Santa_Isabel +L America/Denver America/Shiprock +L America/Puerto_Rico America/Virgin +L Pacific/Auckland Antarctica/South_Pole +L Asia/Ashgabat Asia/Ashkhabad +L Asia/Kolkata Asia/Calcutta +L Asia/Shanghai Asia/Chongqing +L Asia/Shanghai Asia/Chungking +L Asia/Dhaka Asia/Dacca +L Asia/Shanghai Asia/Harbin +L Asia/Urumqi Asia/Kashgar +L Asia/Kathmandu Asia/Katmandu +L Asia/Macau Asia/Macao +L Asia/Yangon Asia/Rangoon +L Asia/Ho_Chi_Minh Asia/Saigon +L Asia/Jerusalem Asia/Tel_Aviv +L Asia/Thimphu Asia/Thimbu +L Asia/Makassar Asia/Ujung_Pandang +L Asia/Ulaanbaatar Asia/Ulan_Bator +L Atlantic/Faroe Atlantic/Faeroe +L Europe/Oslo Atlantic/Jan_Mayen +L Australia/Sydney Australia/ACT +L Australia/Sydney Australia/Canberra +L Australia/Hobart Australia/Currie +L Australia/Lord_Howe Australia/LHI +L Australia/Sydney Australia/NSW +L Australia/Darwin Australia/North +L Australia/Brisbane Australia/Queensland +L Australia/Adelaide Australia/South +L Australia/Hobart Australia/Tasmania +L Australia/Melbourne Australia/Victoria +L Australia/Perth Australia/West +L Australia/Broken_Hill Australia/Yancowinna +L America/Rio_Branco Brazil/Acre +L America/Noronha Brazil/DeNoronha +L America/Sao_Paulo Brazil/East +L America/Manaus Brazil/West +L America/Halifax Canada/Atlantic +L America/Winnipeg Canada/Central +L America/Toronto Canada/Eastern +L America/Edmonton Canada/Mountain +L America/St_Johns Canada/Newfoundland +L America/Vancouver Canada/Pacific +L America/Regina Canada/Saskatchewan +L America/Whitehorse Canada/Yukon +L America/Santiago Chile/Continental +L Pacific/Easter Chile/EasterIsland +L America/Havana Cuba +L Africa/Cairo Egypt +L Europe/Dublin Eire +L Etc/UTC Etc/UCT +L Europe/London Europe/Belfast +L Europe/Chisinau Europe/Tiraspol +L Europe/London GB +L Europe/London GB-Eire +L Etc/GMT GMT+0 +L Etc/GMT GMT-0 +L Etc/GMT GMT0 +L Etc/GMT Greenwich +L Asia/Hong_Kong Hongkong +L Atlantic/Reykjavik Iceland +L Asia/Tehran Iran +L Asia/Jerusalem Israel +L America/Jamaica Jamaica +L Asia/Tokyo Japan +L Pacific/Kwajalein Kwajalein +L Africa/Tripoli Libya +L America/Tijuana Mexico/BajaNorte +L America/Mazatlan Mexico/BajaSur +L America/Mexico_City Mexico/General +L Pacific/Auckland NZ +L Pacific/Chatham NZ-CHAT +L America/Denver Navajo +L Asia/Shanghai PRC +L Pacific/Kanton Pacific/Enderbury +L Pacific/Honolulu Pacific/Johnston +L Pacific/Pohnpei Pacific/Ponape +L Pacific/Pago_Pago Pacific/Samoa +L Pacific/Chuuk Pacific/Truk +L Pacific/Chuuk Pacific/Yap +L Europe/Warsaw Poland +L Europe/Lisbon Portugal +L Asia/Taipei ROC +L Asia/Seoul ROK +L Asia/Singapore Singapore +L Europe/Istanbul Turkey +L Etc/UTC UCT +L America/Anchorage US/Alaska +L America/Adak US/Aleutian +L America/Phoenix US/Arizona +L America/Chicago US/Central +L America/Indiana/Indianapolis US/East-Indiana +L America/New_York US/Eastern +L Pacific/Honolulu US/Hawaii +L America/Indiana/Knox US/Indiana-Starke +L America/Detroit US/Michigan +L America/Denver US/Mountain +L America/Los_Angeles US/Pacific +L Pacific/Pago_Pago US/Samoa +L Etc/UTC UTC +L Etc/UTC Universal +L Europe/Moscow W-SU +L Etc/UTC Zulu diff --git a/src/timezone/known_abbrevs.txt b/src/timezone/known_abbrevs.txt new file mode 100644 index 0000000..6539e1c --- /dev/null +++ b/src/timezone/known_abbrevs.txt @@ -0,0 +1,104 @@ ++00 0 ++00 0 D ++01 3600 ++02 7200 ++02 7200 D ++03 10800 ++0330 12600 ++04 14400 ++0430 16200 ++0430 16200 D ++05 18000 ++0530 19800 ++0545 20700 ++06 21600 ++0630 23400 ++07 25200 ++08 28800 ++0845 31500 ++09 32400 ++10 36000 ++1030 37800 ++11 39600 ++11 39600 D ++12 43200 ++12 43200 D ++1245 45900 ++13 46800 ++13 46800 D ++1345 49500 D ++14 50400 +-00 0 +-01 -3600 +-02 -7200 +-02 -7200 D +-03 -10800 +-03 -10800 D +-04 -14400 +-05 -18000 +-05 -18000 D +-06 -21600 +-07 -25200 +-08 -28800 +-09 -32400 +-0930 -34200 +-10 -36000 +-11 -39600 +-12 -43200 +ACDT 37800 D +ACST 34200 +ADT -10800 D +AEDT 39600 D +AEST 36000 +AKDT -28800 D +AKST -32400 +AST -14400 +AWST 28800 +BST 3600 D +CAT 7200 +CDT -14400 D +CDT -18000 D +CEST 7200 D +CET 3600 +CST -18000 +CST -21600 +CST 28800 +ChST 36000 +EAT 10800 +EDT -14400 D +EEST 10800 D +EET 7200 +EST -18000 +GMT 0 +GMT 0 D +HDT -32400 D +HKT 28800 +HST -36000 +IDT 10800 D +IST 19800 +IST 3600 +IST 7200 +JST 32400 +KST 32400 +MDT -21600 D +MEST 7200 D +MET 3600 +MSK 10800 +MST -25200 +NDT -9000 D +NST -12600 +NZDT 46800 D +NZST 43200 +PDT -25200 D +PKT 18000 +PST -28800 +PST 28800 +SAST 7200 +SST -39600 +UTC 0 +WAT 3600 +WEST 3600 D +WET 0 +WIB 25200 +WIT 32400 +WITA 28800 diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c new file mode 100644 index 0000000..fa3c059 --- /dev/null +++ b/src/timezone/localtime.c @@ -0,0 +1,1904 @@ +/* Convert timestamp from pg_time_t to struct pg_tm. */ + +/* + * This file is in the public domain, so clarified as of + * 1996-06-05 by Arthur David Olson. + * + * IDENTIFICATION + * src/timezone/localtime.c + */ + +/* + * Leap second handling from Bradley White. + * POSIX-style TZ environment variable handling from Guy Harris. + */ + +/* this file needs to build in both frontend and backend contexts */ +#include "c.h" + +#include <fcntl.h> + +#include "datatype/timestamp.h" +#include "pgtz.h" + +#include "private.h" +#include "tzfile.h" + + +#ifndef WILDABBR +/* + * Someone might make incorrect use of a time zone abbreviation: + * 1. They might reference tzname[0] before calling tzset (explicitly + * or implicitly). + * 2. They might reference tzname[1] before calling tzset (explicitly + * or implicitly). + * 3. They might reference tzname[1] after setting to a time zone + * in which Daylight Saving Time is never observed. + * 4. They might reference tzname[0] after setting to a time zone + * in which Standard Time is never observed. + * 5. They might reference tm.tm_zone after calling offtime. + * What's best to do in the above cases is open to debate; + * for now, we just set things up so that in any of the five cases + * WILDABBR is used. Another possibility: initialize tzname[0] to the + * string "tzname[0] used before set", and similarly for the other cases. + * And another: initialize tzname[0] to "ERA", with an explanation in the + * manual page of what this "time zone abbreviation" means (doing this so + * that tzname[0] has the "normal" length of three characters). + */ +#define WILDABBR " " +#endif /* !defined WILDABBR */ + +static const char wildabbr[] = WILDABBR; + +static const char gmt[] = "GMT"; + +/* + * The DST rules to use if a POSIX TZ string has no rules. + * Default to US rules as of 2017-05-07. + * POSIX does not specify the default DST rules; + * for historical reasons, US rules are a common default. + */ +#define TZDEFRULESTRING ",M3.2.0,M11.1.0" + +/* structs ttinfo, lsinfo, state have been moved to pgtz.h */ + +enum r_type +{ + JULIAN_DAY, /* Jn = Julian day */ + DAY_OF_YEAR, /* n = day of year */ + MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ +}; + +struct rule +{ + enum r_type r_type; /* type of rule */ + int r_day; /* day number of rule */ + int r_week; /* week number of rule */ + int r_mon; /* month number of rule */ + int32 r_time; /* transition time of rule */ +}; + +/* + * Prototypes for static functions. + */ + +static struct pg_tm *gmtsub(pg_time_t const *, int32, struct pg_tm *); +static bool increment_overflow(int *, int); +static bool increment_overflow_time(pg_time_t *, int32); +static int64 leapcorr(struct state const *, pg_time_t); +static struct pg_tm *timesub(pg_time_t const *, int32, struct state const *, + struct pg_tm *); +static bool typesequiv(struct state const *, int, int); + + +/* + * Section 4.12.3 of X3.159-1989 requires that + * Except for the strftime function, these functions [asctime, + * ctime, gmtime, localtime] return values in one of two static + * objects: a broken-down time structure and an array of char. + * Thanks to Paul Eggert for noting this. + */ + +static struct pg_tm tm; + +/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ +static void +init_ttinfo(struct ttinfo *s, int32 utoff, bool isdst, int desigidx) +{ + s->tt_utoff = utoff; + s->tt_isdst = isdst; + s->tt_desigidx = desigidx; + s->tt_ttisstd = false; + s->tt_ttisut = false; +} + +static int32 +detzcode(const char *const codep) +{ + int32 result; + int i; + int32 one = 1; + int32 halfmaxval = one << (32 - 2); + int32 maxval = halfmaxval - 1 + halfmaxval; + int32 minval = -1 - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) + { + /* + * Do two's-complement negation even on non-two's-complement machines. + * If the result would be minval - 1, return minval. + */ + result -= !TWOS_COMPLEMENT(int32) && result != 0; + result += minval; + } + return result; +} + +static int64 +detzcode64(const char *const codep) +{ + uint64 result; + int i; + int64 one = 1; + int64 halfmaxval = one << (64 - 2); + int64 maxval = halfmaxval - 1 + halfmaxval; + int64 minval = -TWOS_COMPLEMENT(int64) - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 8; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) + { + /* + * Do two's-complement negation even on non-two's-complement machines. + * If the result would be minval - 1, return minval. + */ + result -= !TWOS_COMPLEMENT(int64) && result != 0; + result += minval; + } + return result; +} + +static bool +differ_by_repeat(const pg_time_t t1, const pg_time_t t0) +{ + if (TYPE_BIT(pg_time_t) - TYPE_SIGNED(pg_time_t) < SECSPERREPEAT_BITS) + return 0; + return t1 - t0 == SECSPERREPEAT; +} + +/* Input buffer for data read from a compiled tz file. */ +union input_buffer +{ + /* The first part of the buffer, interpreted as a header. */ + struct tzhead tzhead; + + /* The entire buffer. */ + char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state) + + 4 * TZ_MAX_TIMES]; +}; + +/* Local storage needed for 'tzloadbody'. */ +union local_storage +{ + /* The results of analyzing the file's contents after it is opened. */ + struct file_analysis + { + /* The input buffer. */ + union input_buffer u; + + /* A temporary state used for parsing a TZ string in the file. */ + struct state st; + } u; + + /* We don't need the "fullname" member */ +}; + +/* Load tz data from the file named NAME into *SP. Read extended + * format if DOEXTEND. Use *LSP for temporary storage. Return 0 on + * success, an errno value on failure. + * PG: If "canonname" is not NULL, then on success the canonical spelling of + * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!). + */ +static int +tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend, + union local_storage *lsp) +{ + int i; + int fid; + int stored; + ssize_t nread; + union input_buffer *up = &lsp->u.u; + int tzheadsize = sizeof(struct tzhead); + + sp->goback = sp->goahead = false; + + if (!name) + { + name = TZDEFAULT; + if (!name) + return EINVAL; + } + + if (name[0] == ':') + ++name; + + fid = pg_open_tzfile(name, canonname); + if (fid < 0) + return ENOENT; /* pg_open_tzfile may not set errno */ + + nread = read(fid, up->buf, sizeof up->buf); + if (nread < tzheadsize) + { + int err = nread < 0 ? errno : EINVAL; + + close(fid); + return err; + } + if (close(fid) < 0) + return errno; + for (stored = 4; stored <= 8; stored *= 2) + { + int32 ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); + int32 ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); + int64 prevtr = 0; + int32 prevcorr = 0; + int32 leapcnt = detzcode(up->tzhead.tzh_leapcnt); + int32 timecnt = detzcode(up->tzhead.tzh_timecnt); + int32 typecnt = detzcode(up->tzhead.tzh_typecnt); + int32 charcnt = detzcode(up->tzhead.tzh_charcnt); + char const *p = up->buf + tzheadsize; + + /* + * Although tzfile(5) currently requires typecnt to be nonzero, + * support future formats that may allow zero typecnt in files that + * have a TZ string and no transitions. + */ + if (!(0 <= leapcnt && leapcnt < TZ_MAX_LEAPS + && 0 <= typecnt && typecnt < TZ_MAX_TYPES + && 0 <= timecnt && timecnt < TZ_MAX_TIMES + && 0 <= charcnt && charcnt < TZ_MAX_CHARS + && (ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisutcnt == typecnt || ttisutcnt == 0))) + return EINVAL; + if (nread + < (tzheadsize /* struct tzhead */ + + timecnt * stored /* ats */ + + timecnt /* types */ + + typecnt * 6 /* ttinfos */ + + charcnt /* chars */ + + leapcnt * (stored + 4) /* lsinfos */ + + ttisstdcnt /* ttisstds */ + + ttisutcnt)) /* ttisuts */ + return EINVAL; + sp->leapcnt = leapcnt; + sp->timecnt = timecnt; + sp->typecnt = typecnt; + sp->charcnt = charcnt; + + /* + * Read transitions, discarding those out of pg_time_t range. But + * pretend the last transition before TIME_T_MIN occurred at + * TIME_T_MIN. + */ + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) + { + int64 at + = stored == 4 ? detzcode(p) : detzcode64(p); + + sp->types[i] = at <= TIME_T_MAX; + if (sp->types[i]) + { + pg_time_t attime + = ((TYPE_SIGNED(pg_time_t) ? at < TIME_T_MIN : at < 0) + ? TIME_T_MIN : at); + + if (timecnt && attime <= sp->ats[timecnt - 1]) + { + if (attime < sp->ats[timecnt - 1]) + return EINVAL; + sp->types[i - 1] = 0; + timecnt--; + } + sp->ats[timecnt++] = attime; + } + p += stored; + } + + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) + { + unsigned char typ = *p++; + + if (sp->typecnt <= typ) + return EINVAL; + if (sp->types[i]) + sp->types[timecnt++] = typ; + } + sp->timecnt = timecnt; + for (i = 0; i < sp->typecnt; ++i) + { + struct ttinfo *ttisp; + unsigned char isdst, + desigidx; + + ttisp = &sp->ttis[i]; + ttisp->tt_utoff = detzcode(p); + p += 4; + isdst = *p++; + if (!(isdst < 2)) + return EINVAL; + ttisp->tt_isdst = isdst; + desigidx = *p++; + if (!(desigidx < sp->charcnt)) + return EINVAL; + ttisp->tt_desigidx = desigidx; + } + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = *p++; + sp->chars[i] = '\0'; /* ensure '\0' at end */ + + /* Read leap seconds, discarding those out of pg_time_t range. */ + leapcnt = 0; + for (i = 0; i < sp->leapcnt; ++i) + { + int64 tr = stored == 4 ? detzcode(p) : detzcode64(p); + int32 corr = detzcode(p + stored); + + p += stored + 4; + /* Leap seconds cannot occur before the Epoch. */ + if (tr < 0) + return EINVAL; + if (tr <= TIME_T_MAX) + { + /* + * Leap seconds cannot occur more than once per UTC month, and + * UTC months are at least 28 days long (minus 1 second for a + * negative leap second). Each leap second's correction must + * differ from the previous one's by 1 second. + */ + if (tr - prevtr < 28 * SECSPERDAY - 1 + || (corr != prevcorr - 1 && corr != prevcorr + 1)) + return EINVAL; + sp->lsis[leapcnt].ls_trans = prevtr = tr; + sp->lsis[leapcnt].ls_corr = prevcorr = corr; + leapcnt++; + } + } + sp->leapcnt = leapcnt; + + for (i = 0; i < sp->typecnt; ++i) + { + struct ttinfo *ttisp; + + ttisp = &sp->ttis[i]; + if (ttisstdcnt == 0) + ttisp->tt_ttisstd = false; + else + { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisstd = *p++; + } + } + for (i = 0; i < sp->typecnt; ++i) + { + struct ttinfo *ttisp; + + ttisp = &sp->ttis[i]; + if (ttisutcnt == 0) + ttisp->tt_ttisut = false; + else + { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisut = *p++; + } + } + + /* + * If this is an old file, we're done. + */ + if (up->tzhead.tzh_version[0] == '\0') + break; + nread -= p - up->buf; + memmove(up->buf, p, nread); + } + if (doextend && nread > 2 && + up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && + sp->typecnt + 2 <= TZ_MAX_TYPES) + { + struct state *ts = &lsp->u.st; + + up->buf[nread - 1] = '\0'; + if (tzparse(&up->buf[1], ts, false)) + { + /* + * Attempt to reuse existing abbreviations. Without this, + * America/Anchorage would be right on the edge after 2037 when + * TZ_MAX_CHARS is 50, as sp->charcnt equals 40 (for LMT AST AWT + * APT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for + * AKST AKDT). Reusing means sp->charcnt can stay 40 in this + * example. + */ + int gotabbr = 0; + int charcnt = sp->charcnt; + + for (i = 0; i < ts->typecnt; i++) + { + char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx; + int j; + + for (j = 0; j < charcnt; j++) + if (strcmp(sp->chars + j, tsabbr) == 0) + { + ts->ttis[i].tt_desigidx = j; + gotabbr++; + break; + } + if (!(j < charcnt)) + { + int tsabbrlen = strlen(tsabbr); + + if (j + tsabbrlen < TZ_MAX_CHARS) + { + strcpy(sp->chars + j, tsabbr); + charcnt = j + tsabbrlen + 1; + ts->ttis[i].tt_desigidx = j; + gotabbr++; + } + } + } + if (gotabbr == ts->typecnt) + { + sp->charcnt = charcnt; + + /* + * Ignore any trailing, no-op transitions generated by zic as + * they don't help here and can run afoul of bugs in zic 2016j + * or earlier. + */ + while (1 < sp->timecnt + && (sp->types[sp->timecnt - 1] + == sp->types[sp->timecnt - 2])) + sp->timecnt--; + + for (i = 0; i < ts->timecnt; i++) + if (sp->timecnt == 0 + || (sp->ats[sp->timecnt - 1] + < ts->ats[i] + leapcorr(sp, ts->ats[i]))) + break; + while (i < ts->timecnt + && sp->timecnt < TZ_MAX_TIMES) + { + sp->ats[sp->timecnt] + = ts->ats[i] + leapcorr(sp, ts->ats[i]); + sp->types[sp->timecnt] = (sp->typecnt + + ts->types[i]); + sp->timecnt++; + i++; + } + for (i = 0; i < ts->typecnt; i++) + sp->ttis[sp->typecnt++] = ts->ttis[i]; + } + } + } + if (sp->typecnt == 0) + return EINVAL; + if (sp->timecnt > 1) + { + for (i = 1; i < sp->timecnt; ++i) + if (typesequiv(sp, sp->types[i], sp->types[0]) && + differ_by_repeat(sp->ats[i], sp->ats[0])) + { + sp->goback = true; + break; + } + for (i = sp->timecnt - 2; i >= 0; --i) + if (typesequiv(sp, sp->types[sp->timecnt - 1], + sp->types[i]) && + differ_by_repeat(sp->ats[sp->timecnt - 1], + sp->ats[i])) + { + sp->goahead = true; + break; + } + } + + /* + * Infer sp->defaulttype from the data. Although this default type is + * always zero for data from recent tzdb releases, things are trickier for + * data from tzdb 2018e or earlier. + * + * The first set of heuristics work around bugs in 32-bit data generated + * by tzdb 2013c or earlier. The workaround is for zones like + * Australia/Macquarie where timestamps before the first transition have a + * time type that is not the earliest standard-time type. See: + * https://mm.icann.org/pipermail/tz/2013-May/019368.html + */ + + /* + * If type 0 is unused in transitions, it's the type to use for early + * times. + */ + for (i = 0; i < sp->timecnt; ++i) + if (sp->types[i] == 0) + break; + i = i < sp->timecnt ? -1 : 0; + + /* + * Absent the above, if there are transition times and the first + * transition is to a daylight time find the standard type less than and + * closest to the type of the first transition. + */ + if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst) + { + i = sp->types[0]; + while (--i >= 0) + if (!sp->ttis[i].tt_isdst) + break; + } + + /* + * The next heuristics are for data generated by tzdb 2018e or earlier, + * for zones like EST5EDT where the first transition is to DST. + */ + + /* + * If no result yet, find the first standard type. If there is none, punt + * to type zero. + */ + if (i < 0) + { + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) + { + i = 0; + break; + } + } + + /* + * A simple 'sp->defaulttype = 0;' would suffice here if we didn't have to + * worry about 2018e-or-earlier data. Even simpler would be to remove the + * defaulttype member and just use 0 in its place. + */ + sp->defaulttype = i; + + return 0; +} + +/* Load tz data from the file named NAME into *SP. Read extended + * format if DOEXTEND. Return 0 on success, an errno value on failure. + * PG: If "canonname" is not NULL, then on success the canonical spelling of + * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!). + */ +int +tzload(const char *name, char *canonname, struct state *sp, bool doextend) +{ + union local_storage *lsp = malloc(sizeof *lsp); + + if (!lsp) + return errno; + else + { + int err = tzloadbody(name, canonname, sp, doextend, lsp); + + free(lsp); + return err; + } +} + +static bool +typesequiv(const struct state *sp, int a, int b) +{ + bool result; + + if (sp == NULL || + a < 0 || a >= sp->typecnt || + b < 0 || b >= sp->typecnt) + result = false; + else + { + const struct ttinfo *ap = &sp->ttis[a]; + const struct ttinfo *bp = &sp->ttis[b]; + + result = (ap->tt_utoff == bp->tt_utoff + && ap->tt_isdst == bp->tt_isdst + && ap->tt_ttisstd == bp->tt_ttisstd + && ap->tt_ttisut == bp->tt_ttisut + && (strcmp(&sp->chars[ap->tt_desigidx], + &sp->chars[bp->tt_desigidx]) + == 0)); + } + return result; +} + +static const int mon_lengths[2][MONSPERYEAR] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +/* + * Given a pointer into a timezone string, scan until a character that is not + * a valid character in a time zone abbreviation is found. + * Return a pointer to that character. + */ + +static const char * +getzname(const char *strp) +{ + char c; + + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && + c != '+') + ++strp; + return strp; +} + +/* + * Given a pointer into an extended timezone string, scan until the ending + * delimiter of the time zone abbreviation is located. + * Return a pointer to the delimiter. + * + * As with getzname above, the legal character set is actually quite + * restricted, with other characters producing undefined results. + * We don't do any checking here; checking is done later in common-case code. + */ + +static const char * +getqzname(const char *strp, const int delim) +{ + int c; + + while ((c = *strp) != '\0' && c != delim) + ++strp; + return strp; +} + +/* + * Given a pointer into a timezone string, extract a number from that string. + * Check that the number is within a specified range; if it is not, return + * NULL. + * Otherwise, return a pointer to the first character not part of the number. + */ + +static const char * +getnum(const char *strp, int *const nump, const int min, const int max) +{ + char c; + int num; + + if (strp == NULL || !is_digit(c = *strp)) + return NULL; + num = 0; + do + { + num = num * 10 + (c - '0'); + if (num > max) + return NULL; /* illegal value */ + c = *++strp; + } while (is_digit(c)); + if (num < min) + return NULL; /* illegal value */ + *nump = num; + return strp; +} + +/* + * Given a pointer into a timezone string, extract a number of seconds, + * in hh[:mm[:ss]] form, from the string. + * If any error occurs, return NULL. + * Otherwise, return a pointer to the first character not part of the number + * of seconds. + */ + +static const char * +getsecs(const char *strp, int32 *const secsp) +{ + int num; + + /* + * 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like + * "M10.4.6/26", which does not conform to Posix, but which specifies the + * equivalent of "02:00 on the first Sunday on or after 23 Oct". + */ + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); + if (strp == NULL) + return NULL; + *secsp = num * (int32) SECSPERHOUR; + if (*strp == ':') + { + ++strp; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + if (strp == NULL) + return NULL; + *secsp += num * SECSPERMIN; + if (*strp == ':') + { + ++strp; + /* 'SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); + if (strp == NULL) + return NULL; + *secsp += num; + } + } + return strp; +} + +/* + * Given a pointer into a timezone string, extract an offset, in + * [+-]hh[:mm[:ss]] form, from the string. + * If any error occurs, return NULL. + * Otherwise, return a pointer to the first character not part of the time. + */ + +static const char * +getoffset(const char *strp, int32 *const offsetp) +{ + bool neg = false; + + if (*strp == '-') + { + neg = true; + ++strp; + } + else if (*strp == '+') + ++strp; + strp = getsecs(strp, offsetp); + if (strp == NULL) + return NULL; /* illegal time */ + if (neg) + *offsetp = -*offsetp; + return strp; +} + +/* + * Given a pointer into a timezone string, extract a rule in the form + * date[/time]. See POSIX section 8 for the format of "date" and "time". + * If a valid rule is not found, return NULL. + * Otherwise, return a pointer to the first character not part of the rule. + */ + +static const char * +getrule(const char *strp, struct rule *const rulep) +{ + if (*strp == 'J') + { + /* + * Julian day. + */ + rulep->r_type = JULIAN_DAY; + ++strp; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } + else if (*strp == 'M') + { + /* + * Month, week, day. + */ + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + ++strp; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_week, 1, 5); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } + else if (is_digit(*strp)) + { + /* + * Day of year. + */ + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } + else + return NULL; /* invalid format */ + if (strp == NULL) + return NULL; + if (*strp == '/') + { + /* + * Time specified. + */ + ++strp; + strp = getoffset(strp, &rulep->r_time); + } + else + rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ + return strp; +} + +/* + * Given a year, a rule, and the offset from UT at the time that rule takes + * effect, calculate the year-relative time that rule takes effect. + */ + +static int32 +transtime(const int year, const struct rule *const rulep, + const int32 offset) +{ + bool leapyear; + int32 value; + int i; + int d, + m1, + yy0, + yy1, + yy2, + dow; + + INITIALIZE(value); + leapyear = isleap(year); + switch (rulep->r_type) + { + + case JULIAN_DAY: + + /* + * Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + * years. In non-leap years, or if the day number is 59 or less, + * just add SECSPERDAY times the day number-1 to the time of + * January 1, midnight, to get the day. + */ + value = (rulep->r_day - 1) * SECSPERDAY; + if (leapyear && rulep->r_day >= 60) + value += SECSPERDAY; + break; + + case DAY_OF_YEAR: + + /* + * n - day of year. Just add SECSPERDAY times the day number to + * the time of January 1, midnight, to get the day. + */ + value = rulep->r_day * SECSPERDAY; + break; + + case MONTH_NTH_DAY_OF_WEEK: + + /* + * Mm.n.d - nth "dth day" of month m. + */ + + /* + * Use Zeller's Congruence to get day-of-week of first day of + * month. + */ + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + yy1 = yy0 / 100; + yy2 = yy0 % 100; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + if (dow < 0) + dow += DAYSPERWEEK; + + /* + * "dow" is the day-of-week of the first day of the month. Get the + * day-of-month (zero-origin) of the first "dow" day of the month. + */ + d = rulep->r_day - dow; + if (d < 0) + d += DAYSPERWEEK; + for (i = 1; i < rulep->r_week; ++i) + { + if (d + DAYSPERWEEK >= + mon_lengths[(int) leapyear][rulep->r_mon - 1]) + break; + d += DAYSPERWEEK; + } + + /* + * "d" is the day-of-month (zero-origin) of the day we want. + */ + value = d * SECSPERDAY; + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[(int) leapyear][i] * SECSPERDAY; + break; + } + + /* + * "value" is the year-relative time of 00:00:00 UT on the day in + * question. To get the year-relative time of the specified local time on + * that day, add the transition time and the current offset from UT. + */ + return value + rulep->r_time + offset; +} + +/* + * Given a POSIX section 8-style TZ string, fill in the rule tables as + * appropriate. + * Returns true on success, false on failure. + */ +bool +tzparse(const char *name, struct state *sp, bool lastditch) +{ + const char *stdname; + const char *dstname = NULL; + size_t stdlen; + size_t dstlen; + size_t charcnt; + int32 stdoffset; + int32 dstoffset; + char *cp; + bool load_ok; + + stdname = name; + if (lastditch) + { + /* Unlike IANA, don't assume name is exactly "GMT" */ + stdlen = strlen(name); /* length of standard zone name */ + name += stdlen; + stdoffset = 0; + } + else + { + if (*name == '<') + { + name++; + stdname = name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + stdlen = name - stdname; + name++; + } + else + { + name = getzname(name); + stdlen = name - stdname; + } + if (*name == '\0') /* we allow empty STD abbrev, unlike IANA */ + return false; + name = getoffset(name, &stdoffset); + if (name == NULL) + return false; + } + charcnt = stdlen + 1; + if (sizeof sp->chars < charcnt) + return false; + + /* + * The IANA code always tries to tzload(TZDEFRULES) here. We do not want + * to do that; it would be bad news in the lastditch case, where we can't + * assume pg_open_tzfile() is sane yet. Moreover, if we did load it and + * it contains leap-second-dependent info, that would cause problems too. + * Finally, IANA has deprecated the TZDEFRULES feature, so it presumably + * will die at some point. Desupporting it now seems like good + * future-proofing. + */ + load_ok = false; + sp->goback = sp->goahead = false; /* simulate failed tzload() */ + sp->leapcnt = 0; /* intentionally assume no leap seconds */ + + if (*name != '\0') + { + if (*name == '<') + { + dstname = ++name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + dstlen = name - dstname; + name++; + } + else + { + dstname = name; + name = getzname(name); + dstlen = name - dstname; /* length of DST abbr. */ + } + if (!dstlen) + return false; + charcnt += dstlen + 1; + if (sizeof sp->chars < charcnt) + return false; + if (*name != '\0' && *name != ',' && *name != ';') + { + name = getoffset(name, &dstoffset); + if (name == NULL) + return false; + } + else + dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && !load_ok) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') + { + struct rule start; + struct rule end; + int year; + int yearlim; + int timecnt; + pg_time_t janfirst; + int32 janoffset = 0; + int yearbeg; + + ++name; + if ((name = getrule(name, &start)) == NULL) + return false; + if (*name++ != ',') + return false; + if ((name = getrule(name, &end)) == NULL) + return false; + if (*name != '\0') + return false; + sp->typecnt = 2; /* standard time and DST */ + + /* + * Two transitions per year, from EPOCH_YEAR forward. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + sp->defaulttype = 0; + timecnt = 0; + janfirst = 0; + yearbeg = EPOCH_YEAR; + + do + { + int32 yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + + yearbeg--; + if (increment_overflow_time(&janfirst, -yearsecs)) + { + janoffset = -yearsecs; + break; + } + } while (EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + yearlim = yearbeg + YEARSPERREPEAT + 1; + for (year = yearbeg; year < yearlim; year++) + { + int32 + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset); + int32 + yearsecs = (year_lengths[isleap(year)] + * SECSPERDAY); + bool reversed = endtime < starttime; + + if (reversed) + { + int32 swap = starttime; + + starttime = endtime; + endtime = swap; + } + if (reversed + || (starttime < endtime + && (endtime - starttime + < (yearsecs + + (stdoffset - dstoffset))))) + { + if (TZ_MAX_TIMES - 2 < timecnt) + break; + sp->ats[timecnt] = janfirst; + if (!increment_overflow_time + (&sp->ats[timecnt], + janoffset + starttime)) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (!increment_overflow_time + (&sp->ats[timecnt], + janoffset + endtime)) + { + sp->types[timecnt++] = reversed; + yearlim = year + YEARSPERREPEAT + 1; + } + } + if (increment_overflow_time + (&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; + } + sp->timecnt = timecnt; + if (!timecnt) + { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } + else if (YEARSPERREPEAT < year - yearbeg) + sp->goback = sp->goahead = true; + } + else + { + int32 theirstdoffset; + int32 theirdstoffset; + int32 theiroffset; + bool isdst; + int i; + int j; + + if (*name != '\0') + return false; + + /* + * Initial values of theirstdoffset and theirdstoffset. + */ + theirstdoffset = 0; + for (i = 0; i < sp->timecnt; ++i) + { + j = sp->types[i]; + if (!sp->ttis[j].tt_isdst) + { + theirstdoffset = + -sp->ttis[j].tt_utoff; + break; + } + } + theirdstoffset = 0; + for (i = 0; i < sp->timecnt; ++i) + { + j = sp->types[i]; + if (sp->ttis[j].tt_isdst) + { + theirdstoffset = + -sp->ttis[j].tt_utoff; + break; + } + } + + /* + * Initially we're assumed to be in standard time. + */ + isdst = false; + theiroffset = theirstdoffset; + + /* + * Now juggle transition times and types tracking offsets as you + * do. + */ + for (i = 0; i < sp->timecnt; ++i) + { + j = sp->types[i]; + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisut) + { + /* No adjustment to transition time */ + } + else + { + /* + * If daylight saving time is in effect, and the + * transition time was not specified as standard time, add + * the daylight saving time offset to the transition time; + * otherwise, add the standard time offset to the + * transition time. + */ + /* + * Transitions from DST to DDST will effectively disappear + * since POSIX provides for only one DST offset. + */ + if (isdst && !sp->ttis[j].tt_ttisstd) + { + sp->ats[i] += dstoffset - + theirdstoffset; + } + else + { + sp->ats[i] += stdoffset - + theirstdoffset; + } + } + theiroffset = -sp->ttis[j].tt_utoff; + if (sp->ttis[j].tt_isdst) + theirdstoffset = theiroffset; + else + theirstdoffset = theiroffset; + } + + /* + * Finally, fill in ttis. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + sp->typecnt = 2; + sp->defaulttype = 0; + } + } + else + { + dstlen = 0; + sp->typecnt = 1; /* only standard time */ + sp->timecnt = 0; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + sp->defaulttype = 0; + } + sp->charcnt = charcnt; + cp = sp->chars; + memcpy(cp, stdname, stdlen); + cp += stdlen; + *cp++ = '\0'; + if (dstlen != 0) + { + memcpy(cp, dstname, dstlen); + *(cp + dstlen) = '\0'; + } + return true; +} + +static void +gmtload(struct state *const sp) +{ + if (tzload(gmt, NULL, sp, true) != 0) + tzparse(gmt, sp, true); +} + + +/* + * The easy way to behave "as if no library function calls" localtime + * is to not call it, so we drop its guts into "localsub", which can be + * freely called. (And no, the PANS doesn't require the above behavior, + * but it *is* desirable.) + */ +static struct pg_tm * +localsub(struct state const *sp, pg_time_t const *timep, + struct pg_tm *const tmp) +{ + const struct ttinfo *ttisp; + int i; + struct pg_tm *result; + const pg_time_t t = *timep; + + if (sp == NULL) + return gmtsub(timep, 0, tmp); + if ((sp->goback && t < sp->ats[0]) || + (sp->goahead && t > sp->ats[sp->timecnt - 1])) + { + pg_time_t newt = t; + pg_time_t seconds; + pg_time_t years; + + if (t < sp->ats[0]) + seconds = sp->ats[0] - t; + else + seconds = t - sp->ats[sp->timecnt - 1]; + --seconds; + years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT; + seconds = years * AVGSECSPERYEAR; + if (t < sp->ats[0]) + newt += seconds; + else + newt -= seconds; + if (newt < sp->ats[0] || + newt > sp->ats[sp->timecnt - 1]) + return NULL; /* "cannot happen" */ + result = localsub(sp, &newt, tmp); + if (result) + { + int64 newy; + + newy = result->tm_year; + if (t < sp->ats[0]) + newy -= years; + else + newy += years; + if (!(INT_MIN <= newy && newy <= INT_MAX)) + return NULL; + result->tm_year = newy; + } + return result; + } + if (sp->timecnt == 0 || t < sp->ats[0]) + { + i = sp->defaulttype; + } + else + { + int lo = 1; + int hi = sp->timecnt; + + while (lo < hi) + { + int mid = (lo + hi) >> 1; + + if (t < sp->ats[mid]) + hi = mid; + else + lo = mid + 1; + } + i = (int) sp->types[lo - 1]; + } + ttisp = &sp->ttis[i]; + + /* + * To get (wrong) behavior that's compatible with System V Release 2.0 + * you'd replace the statement below with t += ttisp->tt_utoff; + * timesub(&t, 0L, sp, tmp); + */ + result = timesub(&t, ttisp->tt_utoff, sp, tmp); + if (result) + { + result->tm_isdst = ttisp->tt_isdst; + result->tm_zone = unconstify(char *, &sp->chars[ttisp->tt_desigidx]); + } + return result; +} + + +struct pg_tm * +pg_localtime(const pg_time_t *timep, const pg_tz *tz) +{ + return localsub(&tz->state, timep, &tm); +} + + +/* + * gmtsub is to gmtime as localsub is to localtime. + * + * Except we have a private "struct state" for GMT, so no sp is passed in. + */ + +static struct pg_tm * +gmtsub(pg_time_t const *timep, int32 offset, + struct pg_tm *tmp) +{ + struct pg_tm *result; + + /* GMT timezone state data is kept here */ + static struct state *gmtptr = NULL; + + if (gmtptr == NULL) + { + /* Allocate on first use */ + gmtptr = (struct state *) malloc(sizeof(struct state)); + if (gmtptr == NULL) + return NULL; /* errno should be set by malloc */ + gmtload(gmtptr); + } + + result = timesub(timep, offset, gmtptr, tmp); + + /* + * Could get fancy here and deliver something such as "+xx" or "-xx" if + * offset is non-zero, but this is no time for a treasure hunt. + */ + if (offset != 0) + tmp->tm_zone = wildabbr; + else + tmp->tm_zone = gmtptr->chars; + + return result; +} + +struct pg_tm * +pg_gmtime(const pg_time_t *timep) +{ + return gmtsub(timep, 0, &tm); +} + +/* + * Return the number of leap years through the end of the given year + * where, to make the math easy, the answer for year zero is defined as zero. + */ + +static int +leaps_thru_end_of_nonneg(int y) +{ + return y / 4 - y / 100 + y / 400; +} + +static int +leaps_thru_end_of(const int y) +{ + return (y < 0 + ? -1 - leaps_thru_end_of_nonneg(-1 - y) + : leaps_thru_end_of_nonneg(y)); +} + +static struct pg_tm * +timesub(const pg_time_t *timep, int32 offset, + const struct state *sp, struct pg_tm *tmp) +{ + const struct lsinfo *lp; + pg_time_t tdays; + int idays; /* unsigned would be so 2003 */ + int64 rem; + int y; + const int *ip; + int64 corr; + bool hit; + int i; + + corr = 0; + hit = false; + i = (sp == NULL) ? 0 : sp->leapcnt; + while (--i >= 0) + { + lp = &sp->lsis[i]; + if (*timep >= lp->ls_trans) + { + corr = lp->ls_corr; + hit = (*timep == lp->ls_trans + && (i == 0 ? 0 : lp[-1].ls_corr) < corr); + break; + } + } + y = EPOCH_YEAR; + tdays = *timep / SECSPERDAY; + rem = *timep % SECSPERDAY; + while (tdays < 0 || tdays >= year_lengths[isleap(y)]) + { + int newy; + pg_time_t tdelta; + int idelta; + int leapdays; + + tdelta = tdays / DAYSPERLYEAR; + if (!((!TYPE_SIGNED(pg_time_t) || INT_MIN <= tdelta) + && tdelta <= INT_MAX)) + goto out_of_range; + idelta = tdelta; + if (idelta == 0) + idelta = (tdays < 0) ? -1 : 1; + newy = y; + if (increment_overflow(&newy, idelta)) + goto out_of_range; + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + tdays -= ((pg_time_t) newy - y) * DAYSPERNYEAR; + tdays -= leapdays; + y = newy; + } + + /* + * Given the range, we can now fearlessly cast... + */ + idays = tdays; + rem += offset - corr; + while (rem < 0) + { + rem += SECSPERDAY; + --idays; + } + while (rem >= SECSPERDAY) + { + rem -= SECSPERDAY; + ++idays; + } + while (idays < 0) + { + if (increment_overflow(&y, -1)) + goto out_of_range; + idays += year_lengths[isleap(y)]; + } + while (idays >= year_lengths[isleap(y)]) + { + idays -= year_lengths[isleap(y)]; + if (increment_overflow(&y, 1)) + goto out_of_range; + } + tmp->tm_year = y; + if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) + goto out_of_range; + tmp->tm_yday = idays; + + /* + * The "extra" mods below avoid overflow problems. + */ + tmp->tm_wday = EPOCH_WDAY + + ((y - EPOCH_YEAR) % DAYSPERWEEK) * + (DAYSPERNYEAR % DAYSPERWEEK) + + leaps_thru_end_of(y - 1) - + leaps_thru_end_of(EPOCH_YEAR - 1) + + idays; + tmp->tm_wday %= DAYSPERWEEK; + if (tmp->tm_wday < 0) + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = (int) (rem / SECSPERHOUR); + rem %= SECSPERHOUR; + tmp->tm_min = (int) (rem / SECSPERMIN); + + /* + * A positive leap second requires a special representation. This uses + * "... ??:59:60" et seq. + */ + tmp->tm_sec = (int) (rem % SECSPERMIN) + hit; + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = (int) (idays + 1); + tmp->tm_isdst = 0; + tmp->tm_gmtoff = offset; + return tmp; + +out_of_range: + errno = EOVERFLOW; + return NULL; +} + +/* + * Normalize logic courtesy Paul Eggert. + */ + +static bool +increment_overflow(int *ip, int j) +{ + int const i = *ip; + + /*---------- + * If i >= 0 there can only be overflow if i + j > INT_MAX + * or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. + * If i < 0 there can only be overflow if i + j < INT_MIN + * or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. + *---------- + */ + if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) + return true; + *ip += j; + return false; +} + +static bool +increment_overflow_time(pg_time_t *tp, int32 j) +{ + /*---------- + * This is like + * 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...', + * except that it does the right thing even if *tp + j would overflow. + *---------- + */ + if (!(j < 0 + ? (TYPE_SIGNED(pg_time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp) + : *tp <= TIME_T_MAX - j)) + return true; + *tp += j; + return false; +} + +static int64 +leapcorr(struct state const *sp, pg_time_t t) +{ + struct lsinfo const *lp; + int i; + + i = sp->leapcnt; + while (--i >= 0) + { + lp = &sp->lsis[i]; + if (t >= lp->ls_trans) + return lp->ls_corr; + } + return 0; +} + +/* + * Find the next DST transition time in the given zone after the given time + * + * *timep and *tz are input arguments, the other parameters are output values. + * + * When the function result is 1, *boundary is set to the pg_time_t + * representation of the next DST transition time after *timep, + * *before_gmtoff and *before_isdst are set to the GMT offset and isdst + * state prevailing just before that boundary (in particular, the state + * prevailing at *timep), and *after_gmtoff and *after_isdst are set to + * the state prevailing just after that boundary. + * + * When the function result is 0, there is no known DST transition + * after *timep, but *before_gmtoff and *before_isdst indicate the GMT + * offset and isdst state prevailing at *timep. (This would occur in + * DST-less time zones, or if a zone has permanently ceased using DST.) + * + * A function result of -1 indicates failure (this case does not actually + * occur in our current implementation). + */ +int +pg_next_dst_boundary(const pg_time_t *timep, + long int *before_gmtoff, + int *before_isdst, + pg_time_t *boundary, + long int *after_gmtoff, + int *after_isdst, + const pg_tz *tz) +{ + const struct state *sp; + const struct ttinfo *ttisp; + int i; + int j; + const pg_time_t t = *timep; + + sp = &tz->state; + if (sp->timecnt == 0) + { + /* non-DST zone, use lowest-numbered standard type */ + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) + { + i = 0; + break; + } + ttisp = &sp->ttis[i]; + *before_gmtoff = ttisp->tt_utoff; + *before_isdst = ttisp->tt_isdst; + return 0; + } + if ((sp->goback && t < sp->ats[0]) || + (sp->goahead && t > sp->ats[sp->timecnt - 1])) + { + /* For values outside the transition table, extrapolate */ + pg_time_t newt = t; + pg_time_t seconds; + pg_time_t tcycles; + int64 icycles; + int result; + + if (t < sp->ats[0]) + seconds = sp->ats[0] - t; + else + seconds = t - sp->ats[sp->timecnt - 1]; + --seconds; + tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR; + ++tcycles; + icycles = tcycles; + if (tcycles - icycles >= 1 || icycles - tcycles >= 1) + return -1; + seconds = icycles; + seconds *= YEARSPERREPEAT; + seconds *= AVGSECSPERYEAR; + if (t < sp->ats[0]) + newt += seconds; + else + newt -= seconds; + if (newt < sp->ats[0] || + newt > sp->ats[sp->timecnt - 1]) + return -1; /* "cannot happen" */ + + result = pg_next_dst_boundary(&newt, before_gmtoff, + before_isdst, + boundary, + after_gmtoff, + after_isdst, + tz); + if (t < sp->ats[0]) + *boundary -= seconds; + else + *boundary += seconds; + return result; + } + + if (t >= sp->ats[sp->timecnt - 1]) + { + /* No known transition > t, so use last known segment's type */ + i = sp->types[sp->timecnt - 1]; + ttisp = &sp->ttis[i]; + *before_gmtoff = ttisp->tt_utoff; + *before_isdst = ttisp->tt_isdst; + return 0; + } + if (t < sp->ats[0]) + { + /* For "before", use lowest-numbered standard type */ + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) + { + i = 0; + break; + } + ttisp = &sp->ttis[i]; + *before_gmtoff = ttisp->tt_utoff; + *before_isdst = ttisp->tt_isdst; + *boundary = sp->ats[0]; + /* And for "after", use the first segment's type */ + i = sp->types[0]; + ttisp = &sp->ttis[i]; + *after_gmtoff = ttisp->tt_utoff; + *after_isdst = ttisp->tt_isdst; + return 1; + } + /* Else search to find the boundary following t */ + { + int lo = 1; + int hi = sp->timecnt - 1; + + while (lo < hi) + { + int mid = (lo + hi) >> 1; + + if (t < sp->ats[mid]) + hi = mid; + else + lo = mid + 1; + } + i = lo; + } + j = sp->types[i - 1]; + ttisp = &sp->ttis[j]; + *before_gmtoff = ttisp->tt_utoff; + *before_isdst = ttisp->tt_isdst; + *boundary = sp->ats[i]; + j = sp->types[i]; + ttisp = &sp->ttis[j]; + *after_gmtoff = ttisp->tt_utoff; + *after_isdst = ttisp->tt_isdst; + return 1; +} + +/* + * Identify a timezone abbreviation's meaning in the given zone + * + * Determine the GMT offset and DST flag associated with the abbreviation. + * This is generally used only when the abbreviation has actually changed + * meaning over time; therefore, we also take a UTC cutoff time, and return + * the meaning in use at or most recently before that time, or the meaning + * in first use after that time if the abbrev was never used before that. + * + * On success, returns true and sets *gmtoff and *isdst. If the abbreviation + * was never used at all in this zone, returns false. + * + * Note: abbrev is matched case-sensitively; it should be all-upper-case. + */ +bool +pg_interpret_timezone_abbrev(const char *abbrev, + const pg_time_t *timep, + long int *gmtoff, + int *isdst, + const pg_tz *tz) +{ + const struct state *sp; + const char *abbrs; + const struct ttinfo *ttisp; + int abbrind; + int cutoff; + int i; + const pg_time_t t = *timep; + + sp = &tz->state; + + /* + * 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; /* not there! */ + + /* + * Unlike pg_next_dst_boundary, we needn't sweat about extrapolation + * (goback/goahead zones). Finding the newest or oldest meaning of the + * abbreviation should get us what we want, since extrapolation would just + * be repeating the newest or oldest meanings. + * + * Use binary search to locate the first transition > cutoff time. + */ + { + int lo = 0; + int hi = sp->timecnt; + + while (lo < hi) + { + int mid = (lo + hi) >> 1; + + if (t < sp->ats[mid]) + hi = mid; + else + lo = mid + 1; + } + cutoff = lo; + } + + /* + * Scan backwards to find the latest interval using the given abbrev + * before the cutoff time. + */ + for (i = cutoff - 1; i >= 0; i--) + { + ttisp = &sp->ttis[sp->types[i]]; + if (ttisp->tt_desigidx == abbrind) + { + *gmtoff = ttisp->tt_utoff; + *isdst = ttisp->tt_isdst; + return true; + } + } + + /* + * Not there, so scan forwards to find the first one after. + */ + for (i = cutoff; i < sp->timecnt; i++) + { + ttisp = &sp->ttis[sp->types[i]]; + if (ttisp->tt_desigidx == abbrind) + { + *gmtoff = ttisp->tt_utoff; + *isdst = ttisp->tt_isdst; + return true; + } + } + + return false; /* hm, not actually used in any interval? */ +} + +/* + * If the given timezone uses only one GMT offset, store that offset + * into *gmtoff and return true, else return false. + */ +bool +pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff) +{ + /* + * The zone could have more than one ttinfo, if it's historically used + * more than one abbreviation. We return true as long as they all have + * the same gmtoff. + */ + const struct state *sp; + int i; + + sp = &tz->state; + for (i = 1; i < sp->typecnt; i++) + { + if (sp->ttis[i].tt_utoff != sp->ttis[0].tt_utoff) + return false; + } + *gmtoff = sp->ttis[0].tt_utoff; + return true; +} + +/* + * Return the name of the current timezone + */ +const char * +pg_get_timezone_name(pg_tz *tz) +{ + if (tz) + return tz->TZname; + return NULL; +} + +/* + * Check whether timezone is acceptable. + * + * What we are doing here is checking for leap-second-aware timekeeping. + * We need to reject such TZ settings because they'll wreak havoc with our + * date/time arithmetic. + */ +bool +pg_tz_acceptable(pg_tz *tz) +{ + struct pg_tm *tt; + pg_time_t time2000; + + /* + * To detect leap-second timekeeping, run pg_localtime for what should be + * GMT midnight, 2000-01-01. Insist that the tm_sec value be zero; any + * other result has to be due to leap seconds. + */ + time2000 = (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY; + tt = pg_localtime(&time2000, tz); + if (!tt || tt->tm_sec != 0) + return false; + + return true; +} diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c new file mode 100644 index 0000000..82f14ee --- /dev/null +++ b/src/timezone/pgtz.c @@ -0,0 +1,502 @@ +/*------------------------------------------------------------------------- + * + * pgtz.c + * Timezone Library Integration Functions + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/timezone/pgtz.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <ctype.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <time.h> + +#include "datatype/timestamp.h" +#include "miscadmin.h" +#include "pgtz.h" +#include "storage/fd.h" +#include "utils/hsearch.h" + + +/* Current session timezone (controlled by TimeZone GUC) */ +pg_tz *session_timezone = NULL; + +/* Current log timezone (controlled by log_timezone GUC) */ +pg_tz *log_timezone = NULL; + + +static bool scan_directory_ci(const char *dirname, + const char *fname, int fnamelen, + char *canonname, int canonnamelen); + + +/* + * Return full pathname of timezone data directory + */ +static const char * +pg_TZDIR(void) +{ +#ifndef SYSTEMTZDIR + /* normal case: timezone stuff is under our share dir */ + static bool done_tzdir = false; + static char tzdir[MAXPGPATH]; + + if (done_tzdir) + return tzdir; + + get_share_path(my_exec_path, tzdir); + strlcpy(tzdir + strlen(tzdir), "/timezone", MAXPGPATH - strlen(tzdir)); + + done_tzdir = true; + return tzdir; +#else + /* we're configured to use system's timezone database */ + return SYSTEMTZDIR; +#endif +} + + +/* + * 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 (the buffer must be > TZ_STRLEN_MAX bytes!). + */ +int +pg_open_tzfile(const char *name, char *canonname) +{ + const char *fname; + char fullname[MAXPGPATH]; + int fullnamelen; + int orignamelen; + + /* Initialize fullname with base name of tzdata directory */ + strlcpy(fullname, pg_TZDIR(), sizeof(fullname)); + orignamelen = fullnamelen = strlen(fullname); + + if (fullnamelen + 1 + strlen(name) >= MAXPGPATH) + return -1; /* not gonna fit */ + + /* + * If the caller doesn't need the canonical spelling, first just try to + * open the name as-is. This can be expected to succeed if the given name + * is already case-correct, or if the filesystem is case-insensitive; and + * we don't need to distinguish those situations if we aren't tasked with + * reporting the canonical spelling. + */ + if (canonname == NULL) + { + int result; + + fullname[fullnamelen] = '/'; + /* test above ensured this will fit: */ + strcpy(fullname + fullnamelen + 1, name); + result = open(fullname, O_RDONLY | PG_BINARY, 0); + if (result >= 0) + return result; + /* If that didn't work, fall through to do it the hard way */ + fullname[fullnamelen] = '\0'; + } + + /* + * Loop to split the given name into directory levels; for each level, + * search using scan_directory_ci(). + */ + fname = name; + for (;;) + { + const char *slashptr; + int fnamelen; + + slashptr = strchr(fname, '/'); + if (slashptr) + fnamelen = slashptr - fname; + else + fnamelen = strlen(fname); + 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); + + while ((direntry = ReadDirExtended(dirdesc, dirname, LOG)) != 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; +} + + +/* + * We keep loaded timezones in a hashtable so we don't have to + * 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) +{ + HASHCTL hash_ctl; + + hash_ctl.keysize = TZ_STRLEN_MAX + 1; + hash_ctl.entrysize = sizeof(pg_tz_cache); + + timezone_cache = hash_create("Timezones", + 4, + &hash_ctl, + HASH_ELEM | HASH_STRINGS); + if (!timezone_cache) + return false; + + return true; +} + +/* + * Load a timezone from file or from cache. + * Does not verify that the timezone is acceptable! + * + * "GMT" is always interpreted as the tzparse() definition, without attempting + * to load a definition from the filesystem. This has a number of benefits: + * 1. It's guaranteed to succeed, so we don't have the failure mode wherein + * the bootstrap default timezone setting doesn't work (as could happen if + * the OS attempts to supply a leap-second-aware version of "GMT"). + * 2. Because we aren't accessing the filesystem, we can safely initialize + * the "GMT" zone definition before my_exec_path is known. + * 3. It's quick enough that we don't waste much time when the bootstrap + * default timezone setting is later overridden from postgresql.conf. + */ +pg_tz * +pg_tzset(const char *name) +{ + 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 */ + + if (!timezone_cache) + if (!init_timezone_hashtable()) + return 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->tz; + } + + /* + * "GMT" is always sent to tzparse(), as per discussion above. + */ + if (strcmp(uppername, "GMT") == 0) + { + if (!tzparse(uppername, &tzstate, true)) + { + /* This really, really should not happen ... */ + elog(ERROR, "could not initialize GMT time zone"); + } + /* Use uppercase name as canonical */ + strcpy(canonname, uppername); + } + else if (tzload(uppername, canonname, &tzstate, true) != 0) + { + if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false)) + { + /* Unknown timezone. Fail our call instead of loading GMT! */ + return NULL; + } + /* For POSIX timezone specs, use uppercase name as canonical */ + strcpy(canonname, uppername); + } + + /* Save timezone in the cache */ + tzp = (pg_tz_cache *) hash_search(timezone_cache, + uppername, + HASH_ENTER, + NULL); + + /* hash_search already copied uppername into the hash key */ + strcpy(tzp->tz.TZname, canonname); + memcpy(&tzp->tz.state, &tzstate, sizeof(tzstate)); + + return &tzp->tz; +} + +/* + * Load a fixed-GMT-offset timezone. + * This is used for SQL-spec SET TIME ZONE INTERVAL 'foo' cases. + * It's otherwise equivalent to pg_tzset(). + * + * The GMT offset is specified in seconds, positive values meaning west of + * Greenwich (ie, POSIX not ISO sign convention). However, we use ISO + * sign convention in the displayable abbreviation for the zone. + * + * Caution: this can fail (return NULL) if the specified offset is outside + * the range allowed by the zic library. + */ +pg_tz * +pg_tzset_offset(long gmtoffset) +{ + long absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset; + char offsetstr[64]; + char tzname[128]; + + snprintf(offsetstr, sizeof(offsetstr), + "%02ld", absoffset / SECS_PER_HOUR); + absoffset %= SECS_PER_HOUR; + if (absoffset != 0) + { + snprintf(offsetstr + strlen(offsetstr), + sizeof(offsetstr) - strlen(offsetstr), + ":%02ld", absoffset / SECS_PER_MINUTE); + absoffset %= SECS_PER_MINUTE; + if (absoffset != 0) + snprintf(offsetstr + strlen(offsetstr), + sizeof(offsetstr) - strlen(offsetstr), + ":%02ld", absoffset); + } + if (gmtoffset > 0) + snprintf(tzname, sizeof(tzname), "<-%s>+%s", + offsetstr, offsetstr); + else + snprintf(tzname, sizeof(tzname), "<+%s>-%s", + offsetstr, offsetstr); + + return pg_tzset(tzname); +} + + +/* + * Initialize timezone library + * + * This is called before GUC variable initialization begins. Its purpose + * is to ensure that log_timezone has a valid value before any logging GUC + * variables could become set to values that require elog.c to provide + * timestamps (e.g., log_line_prefix). We may as well initialize + * session_timezone to something valid, too. + */ +void +pg_timezone_initialize(void) +{ + /* + * We may not yet know where PGSHAREDIR is (in particular this is true in + * an EXEC_BACKEND subprocess). So use "GMT", which pg_tzset forces to be + * interpreted without reference to the filesystem. This corresponds to + * the bootstrap default for these variables in guc.c, although in + * principle it could be different. + */ + session_timezone = pg_tzset("GMT"); + log_timezone = session_timezone; +} + + +/* + * 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 * 2]; + 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, sizeof(fullname), "%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_internal("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. Also, don't ask for the canonical spelling: we already + * know it, and pg_open_tzfile's way of finding it out is pretty + * inefficient. + */ + if (tzload(fullname + dir->baselen, NULL, &dir->tz.state, true) != 0) + { + /* Zone could not be loaded, ignore it */ + continue; + } + + if (!pg_tz_acceptable(&dir->tz)) + { + /* Ignore leap-second zones */ + continue; + } + + /* OK, return the canonical zone name spelling. */ + strlcpy(dir->tz.TZname, fullname + dir->baselen, + sizeof(dir->tz.TZname)); + + /* Timezone loaded OK. */ + return &dir->tz; + } + + /* Nothing more found */ + return NULL; +} diff --git a/src/timezone/pgtz.h b/src/timezone/pgtz.h new file mode 100644 index 0000000..ca01722 --- /dev/null +++ b/src/timezone/pgtz.h @@ -0,0 +1,81 @@ +/*------------------------------------------------------------------------- + * + * pgtz.h + * Timezone Library Integration Functions + * + * Note: this file contains only definitions that are private to the + * timezone library. Public definitions are in pgtime.h. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/timezone/pgtz.h + * + *------------------------------------------------------------------------- + */ +#ifndef _PGTZ_H +#define _PGTZ_H + +#include "pgtime.h" +#include "tzfile.h" + + +#define SMALLEST(a, b) (((a) < (b)) ? (a) : (b)) +#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) + +struct ttinfo +{ /* time type information */ + int32 tt_utoff; /* UT offset in seconds */ + bool tt_isdst; /* used to set tm_isdst */ + int tt_desigidx; /* abbreviation list index */ + bool tt_ttisstd; /* transition is std time */ + bool tt_ttisut; /* transition is UT */ +}; + +struct lsinfo +{ /* leap second information */ + pg_time_t ls_trans; /* transition time */ + int64 ls_corr; /* correction to apply */ +}; + +struct state +{ + int leapcnt; + int timecnt; + int typecnt; + int charcnt; + bool goback; + bool goahead; + pg_time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + struct ttinfo ttis[TZ_MAX_TYPES]; + char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, 4 /* sizeof gmt */ ), + (2 * (TZ_STRLEN_MAX + 1)))]; + struct lsinfo lsis[TZ_MAX_LEAPS]; + + /* + * The time type to use for early times or if no transitions. It is always + * zero for recent tzdb releases. It might be nonzero for data from tzdb + * 2018e or earlier. + */ + int defaulttype; +}; + + +struct pg_tz +{ + /* TZname contains the canonically-cased name of the timezone */ + char TZname[TZ_STRLEN_MAX + 1]; + struct state state; +}; + + +/* 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, + bool doextend); +extern bool tzparse(const char *name, struct state *sp, bool lastditch); + +#endif /* _PGTZ_H */ diff --git a/src/timezone/private.h b/src/timezone/private.h new file mode 100644 index 0000000..533e3d9 --- /dev/null +++ b/src/timezone/private.h @@ -0,0 +1,163 @@ +/* Private header for tzdb code. */ + +#ifndef PRIVATE_H + +#define PRIVATE_H + +/* + * This file is in the public domain, so clarified as of + * 1996-06-05 by Arthur David Olson. + * + * IDENTIFICATION + * src/timezone/private.h + */ + +/* + * This header is for use ONLY with the time conversion code. + * There is no guarantee that it will remain unchanged, + * or that it will remain at all. + * Do NOT copy it to any system include directory. + * Thank you! + */ + +#include <limits.h> /* for CHAR_BIT et al. */ +#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */ +#include <unistd.h> /* for F_OK and R_OK */ + +#include "pgtime.h" + +/* This string was in the Factory zone through version 2016f. */ +#define GRANDPARENTED "Local time zone must be set--see zic manual page" + +/* + * IANA has a bunch of HAVE_FOO #defines here, but in PG we want pretty + * much all of that to be done by PG's configure script. + */ + +#ifndef ENOTSUP +#define ENOTSUP EINVAL +#endif +#ifndef EOVERFLOW +#define EOVERFLOW EINVAL +#endif + +/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */ +#define is_digit(c) ((unsigned)(c) - '0' <= 9) + +/* PG doesn't currently rely on <inttypes.h>, so work around strtoimax() */ +#undef strtoimax +#ifdef HAVE_STRTOLL +#define strtoimax strtoll +#else +#define strtoimax strtol +#endif + + +/* + * Finally, some convenience items. + */ + +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#define TYPE_SIGNED(type) (((type) -1) < 0) +#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) + +/* + * Max and min values of the integer type T, of which only the bottom + * B bits are used, and where the highest-order used bit is considered + * to be a sign bit if T is signed. + */ +#define MAXVAL(t, b) \ + ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ + - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) +#define MINVAL(t, b) \ + ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) + +/* The extreme time values, assuming no padding. */ +#define TIME_T_MIN MINVAL(pg_time_t, TYPE_BIT(pg_time_t)) +#define TIME_T_MAX MAXVAL(pg_time_t, TYPE_BIT(pg_time_t)) + +/* + * 302 / 1000 is log10(2.0) rounded up. + * Subtract one for the sign bit if the type is signed; + * add one for integer division truncation; + * add one more for a minus sign if the type is signed. + */ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) + +/* + * INITIALIZE(x) + */ +#define INITIALIZE(x) ((x) = 0) + +#undef _ +#define _(msgid) (msgid) + +/* Handy macros that are independent of tzfile implementation. */ + +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int32) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* + * Since everything in isleap is modulo 400 (or a factor of 400), we know that + * isleap(y) == isleap(y % 400) + * and so + * isleap(a + b) == isleap((a + b) % 400) + * or + * isleap(a + b) == isleap(a % 400 + b % 400) + * This is true even if % means modulo rather than Fortran remainder + * (which is allowed by C89 but not by C99 or later). + * We use this to avoid addition overflow problems. + */ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + + +/* + * The Gregorian year averages 365.2425 days, which is 31556952 seconds. + */ + +#define AVGSECSPERYEAR 31556952L +#define SECSPERREPEAT \ + ((int64) YEARSPERREPEAT * (int64) AVGSECSPERYEAR) +#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ + +#endif /* !defined PRIVATE_H */ diff --git a/src/timezone/strftime.c b/src/timezone/strftime.c new file mode 100644 index 0000000..dd6c7db --- /dev/null +++ b/src/timezone/strftime.c @@ -0,0 +1,571 @@ +/* Convert a broken-down timestamp to a string. */ + +/* + * Copyright 1989 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Based on the UCB version with the copyright notice appearing above. + * + * This is ANSIish only when "multibyte character == plain character". + * + * IDENTIFICATION + * src/timezone/strftime.c + */ + +#include "postgres.h" + +#include <fcntl.h> + +#include "private.h" + + +struct lc_time_T +{ + const char *mon[MONSPERYEAR]; + const char *month[MONSPERYEAR]; + const char *wday[DAYSPERWEEK]; + const char *weekday[DAYSPERWEEK]; + const char *X_fmt; + const char *x_fmt; + const char *c_fmt; + const char *am; + const char *pm; + const char *date_fmt; +}; + +#define Locale (&C_time_locale) + +static const struct lc_time_T C_time_locale = { + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }, { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }, { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" + }, { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }, + + /* X_fmt */ + "%H:%M:%S", + + /* + * x_fmt + * + * C99 and later require this format. Using just numbers (as here) makes + * Quakers happier; it's also compatible with SVR4. + */ + "%m/%d/%y", + + /* + * c_fmt + * + * C99 and later require this format. Previously this code used "%D %X", + * but we now conform to C99. Note that "%a %b %d %H:%M:%S %Y" is used by + * Solaris 2.3. + */ + "%a %b %e %T %Y", + + /* am */ + "AM", + + /* pm */ + "PM", + + /* date_fmt */ + "%a %b %e %H:%M:%S %Z %Y" +}; + +enum warn +{ + IN_NONE, IN_SOME, IN_THIS, IN_ALL +}; + +static char *_add(const char *, char *, const char *); +static char *_conv(int, const char *, char *, const char *); +static char *_fmt(const char *, const struct pg_tm *, char *, const char *, + enum warn *); +static char *_yconv(int, int, bool, bool, char *, char const *); + + +/* + * Convert timestamp t to string s, a caller-allocated buffer of size maxsize, + * using the given format pattern. + * + * See also timestamptz_to_str. + */ +size_t +pg_strftime(char *s, size_t maxsize, const char *format, const struct pg_tm *t) +{ + char *p; + int saved_errno = errno; + enum warn warn = IN_NONE; + + p = _fmt(format, t, s, s + maxsize, &warn); + if (!p) + { + errno = EOVERFLOW; + return 0; + } + if (p == s + maxsize) + { + errno = ERANGE; + return 0; + } + *p = '\0'; + errno = saved_errno; + return p - s; +} + +static char * +_fmt(const char *format, const struct pg_tm *t, char *pt, + const char *ptlim, enum warn *warnp) +{ + for (; *format; ++format) + { + if (*format == '%') + { + label: + switch (*++format) + { + case '\0': + --format; + break; + case 'A': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->weekday[t->tm_wday], + pt, ptlim); + continue; + case 'a': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->wday[t->tm_wday], + pt, ptlim); + continue; + case 'B': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->month[t->tm_mon], + pt, ptlim); + continue; + case 'b': + case 'h': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->mon[t->tm_mon], + pt, ptlim); + continue; + case 'C': + + /* + * %C used to do a... _fmt("%a %b %e %X %Y", t); + * ...whereas now POSIX 1003.2 calls for something + * completely different. (ado, 1993-05-24) + */ + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, false, pt, ptlim); + continue; + case 'c': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'D': + pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); + continue; + case 'd': + pt = _conv(t->tm_mday, "%02d", pt, ptlim); + continue; + case 'E': + case 'O': + + /* + * Locale modifiers of C99 and later. The sequences %Ec + * %EC %Ex %EX %Ey %EY %Od %oe %OH %OI %Om %OM %OS %Ou %OU + * %OV %Ow %OW %Oy are supposed to provide alternative + * representations. + */ + goto label; + case 'e': + pt = _conv(t->tm_mday, "%2d", pt, ptlim); + continue; + case 'F': + pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); + continue; + case 'H': + pt = _conv(t->tm_hour, "%02d", pt, ptlim); + continue; + case 'I': + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%02d", pt, ptlim); + continue; + case 'j': + pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); + continue; + case 'k': + + /* + * This used to be... _conv(t->tm_hour % 12 ? t->tm_hour % + * 12 : 12, 2, ' '); ...and has been changed to the below + * to match SunOS 4.1.1 and Arnold Robbins' strftime + * version 3.0. That is, "%k" and "%l" have been swapped. + * (ado, 1993-05-24) + */ + pt = _conv(t->tm_hour, "%2d", pt, ptlim); + continue; +#ifdef KITCHEN_SINK + case 'K': + + /* + * After all this time, still unclaimed! + */ + pt = _add("kitchen sink", pt, ptlim); + continue; +#endif /* defined KITCHEN_SINK */ + case 'l': + + /* + * This used to be... _conv(t->tm_hour, 2, ' '); ...and + * has been changed to the below to match SunOS 4.1.1 and + * Arnold Robbin's strftime version 3.0. That is, "%k" and + * "%l" have been swapped. (ado, 1993-05-24) + */ + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%2d", pt, ptlim); + continue; + case 'M': + pt = _conv(t->tm_min, "%02d", pt, ptlim); + continue; + case 'm': + pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); + continue; + case 'n': + pt = _add("\n", pt, ptlim); + continue; + case 'p': + pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? + Locale->pm : + Locale->am, + pt, ptlim); + continue; + case 'R': + pt = _fmt("%H:%M", t, pt, ptlim, warnp); + continue; + case 'r': + pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); + continue; + case 'S': + pt = _conv(t->tm_sec, "%02d", pt, ptlim); + continue; + case 'T': + pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); + continue; + case 't': + pt = _add("\t", pt, ptlim); + continue; + case 'U': + pt = _conv((t->tm_yday + DAYSPERWEEK - + t->tm_wday) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'u': + + /* + * From Arnold Robbins' strftime version 3.0: "ISO 8601: + * Weekday as a decimal number [1 (Monday) - 7]" (ado, + * 1993-05-24) + */ + pt = _conv((t->tm_wday == 0) ? + DAYSPERWEEK : t->tm_wday, + "%d", pt, ptlim); + continue; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ +/* + * From Arnold Robbins' strftime version 3.0: "the week number of the + * year (the first Monday as the first day of week 1) as a decimal number + * (01-53)." + * (ado, 1993-05-24) + * + * From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: + * "Week 01 of a year is per definition the first week which has the + * Thursday in this year, which is equivalent to the week which contains + * the fourth day of January. In other words, the first week of a new year + * is the week which has the majority of its days in the new year. Week 01 + * might also contain days from the previous year and the week before week + * 01 of a year is the last week (52 or 53) of the previous year even if + * it contains days from the new year. A week starts with Monday (day 1) + * and ends with Sunday (day 7). For example, the first week of the year + * 1997 lasts from 1996-12-30 to 1997-01-05..." + * (ado, 1996-01-02) + */ + { + int year; + int base; + int yday; + int wday; + int w; + + year = t->tm_year; + base = TM_YEAR_BASE; + yday = t->tm_yday; + wday = t->tm_wday; + for (;;) + { + int len; + int bot; + int top; + + len = isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + + /* + * What yday (-3 ... 3) does the ISO year begin + * on? + */ + bot = ((yday + 11 - wday) % + DAYSPERWEEK) - 3; + + /* + * What yday does the NEXT ISO year begin on? + */ + top = bot - + (len % DAYSPERWEEK); + if (top < -3) + top += DAYSPERWEEK; + top += len; + if (yday >= top) + { + ++base; + w = 1; + break; + } + if (yday >= bot) + { + w = 1 + ((yday - bot) / + DAYSPERWEEK); + break; + } + --base; + yday += isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + } + if (*format == 'V') + pt = _conv(w, "%02d", + pt, ptlim); + else if (*format == 'g') + { + *warnp = IN_ALL; + pt = _yconv(year, base, + false, true, + pt, ptlim); + } + else + pt = _yconv(year, base, + true, true, + pt, ptlim); + } + continue; + case 'v': + + /* + * From Arnold Robbins' strftime version 3.0: "date as + * dd-bbb-YYYY" (ado, 1993-05-24) + */ + pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); + continue; + case 'W': + pt = _conv((t->tm_yday + DAYSPERWEEK - + (t->tm_wday ? + (t->tm_wday - 1) : + (DAYSPERWEEK - 1))) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'w': + pt = _conv(t->tm_wday, "%d", pt, ptlim); + continue; + case 'X': + pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); + continue; + case 'x': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'y': + *warnp = IN_ALL; + pt = _yconv(t->tm_year, TM_YEAR_BASE, + false, true, + pt, ptlim); + continue; + case 'Y': + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, true, + pt, ptlim); + continue; + case 'Z': + if (t->tm_zone != NULL) + pt = _add(t->tm_zone, pt, ptlim); + + /* + * C99 and later say that %Z must be replaced by the empty + * string if the time zone abbreviation is not + * determinable. + */ + continue; + case 'z': + { + long diff; + char const *sign; + bool negative; + + if (t->tm_isdst < 0) + continue; + diff = t->tm_gmtoff; + negative = diff < 0; + if (diff == 0) + { + if (t->tm_zone != NULL) + negative = t->tm_zone[0] == '-'; + } + if (negative) + { + sign = "-"; + diff = -diff; + } + else + sign = "+"; + pt = _add(sign, pt, ptlim); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + + (diff % MINSPERHOUR); + pt = _conv(diff, "%04d", pt, ptlim); + } + continue; + case '+': + pt = _fmt(Locale->date_fmt, t, pt, ptlim, + warnp); + continue; + case '%': + + /* + * X311J/88-090 (4.12.3.5): if conversion char is + * undefined, behavior is undefined. Print out the + * character itself as printf(3) also does. + */ + default: + break; + } + } + if (pt == ptlim) + break; + *pt++ = *format; + } + return pt; +} + +static char * +_conv(int n, const char *format, char *pt, const char *ptlim) +{ + char buf[INT_STRLEN_MAXIMUM(int) + 1]; + + sprintf(buf, format, n); + return _add(buf, pt, ptlim); +} + +static char * +_add(const char *str, char *pt, const char *ptlim) +{ + while (pt < ptlim && (*pt = *str++) != '\0') + ++pt; + return pt; +} + +/* + * POSIX and the C Standard are unclear or inconsistent about + * what %C and %y do if the year is negative or exceeds 9999. + * Use the convention that %C concatenated with %y yields the + * same output as %Y, and that %Y contains at least 4 bytes, + * with more only if necessary. + */ + +static char * +_yconv(int a, int b, bool convert_top, bool convert_yy, + char *pt, const char *ptlim) +{ + int lead; + int trail; + +#define DIVISOR 100 + trail = a % DIVISOR + b % DIVISOR; + lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) + { + trail += DIVISOR; + --lead; + } + else if (lead < 0 && trail > 0) + { + trail -= DIVISOR; + ++lead; + } + if (convert_top) + { + if (lead == 0 && trail < 0) + pt = _add("-0", pt, ptlim); + else + pt = _conv(lead, "%02d", pt, ptlim); + } + if (convert_yy) + pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); + return pt; +} diff --git a/src/timezone/tzfile.h b/src/timezone/tzfile.h new file mode 100644 index 0000000..8f3eb6b --- /dev/null +++ b/src/timezone/tzfile.h @@ -0,0 +1,110 @@ +/* Layout and location of TZif files. */ + +#ifndef TZFILE_H + +#define TZFILE_H + +/* + * This file is in the public domain, so clarified as of + * 1996-06-05 by Arthur David Olson. + * + * IDENTIFICATION + * src/timezone/tzfile.h + */ + +/* + * This header is for use ONLY with the time conversion code. + * There is no guarantee that it will remain unchanged, + * or that it will remain at all. + * Do NOT copy it to any system include directory. + * Thank you! + */ + +/* + * Information about time zone files. + */ + +#define TZDEFAULT "/etc/localtime" +#define TZDEFRULES "posixrules" + + +/* See Internet RFC 8536 for more details about the following format. */ + +/* + * Each file begins with. . . + */ + +#define TZ_MAGIC "TZif" + +struct tzhead +{ + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */ + char tzh_reserved[15]; /* reserved; must be zero */ + char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* + * . . .followed by. . . + * + * tzh_timecnt (char [4])s coded transition times a la time(2) + * tzh_timecnt (unsigned char)s types of local time starting at above + * tzh_typecnt repetitions of + * one (char [4]) coded UT offset in seconds + * one (unsigned char) used to set tm_isdst + * one (unsigned char) that's an abbreviation list index + * tzh_charcnt (char)s '\0'-terminated zone abbreviations + * tzh_leapcnt repetitions of + * one (char [4]) coded leap second transition times + * one (char [4]) total correction after above + * tzh_ttisstdcnt (char)s indexed by type; if 1, transition + * time is standard time, if 0, + * transition time is local (wall clock) + * time; if absent, transition times are + * assumed to be local time + * tzh_ttisutcnt (char)s indexed by type; if 1, transition + * time is UT, if 0, transition time is + * local time; if absent, transition + * times are assumed to be local time. + * When this is 1, the corresponding + * std/wall indicator must also be 1. + */ + +/* + * If tzh_version is '2' or greater, the above is followed by a second instance + * of tzhead and a second instance of the data in which each coded transition + * time uses 8 rather than 4 chars, + * then a POSIX-TZ-environment-variable-style string for use in handling + * instants after the last transition time stored in the file + * (with nothing between the newlines if there is no POSIX representation for + * such instants). + * + * If tz_version is '3' or greater, the above is extended as follows. + * First, the POSIX TZ string's hour offset may range from -167 + * through 167 as compared to the POSIX-required 0 through 24. + * Second, its DST start time may be January 1 at 00:00 and its stop + * time December 31 at 24:00 plus the difference between DST and + * standard time, indicating DST all year. + */ + +/* + * In the current implementation, "tzset()" refuses to deal with files that + * exceed any of the limits below. + */ + +#define TZ_MAX_TIMES 2000 + +/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ + +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ + +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ + +#endif /* !defined TZFILE_H */ diff --git a/src/timezone/tznames/Africa.txt b/src/timezone/tznames/Africa.txt new file mode 100644 index 0000000..2ea08a6 --- /dev/null +++ b/src/timezone/tznames/Africa.txt @@ -0,0 +1,176 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Africa.txt +# + +CAT 7200 # Central Africa Time + # (Africa/Blantyre) + # (Africa/Bujumbura) + # (Africa/Gaborone) + # (Africa/Harare) + # (Africa/Kigali) + # (Africa/Lubumbashi) + # (Africa/Lusaka) + # (Africa/Maputo) +CEST 7200 D # Central Europe Summer Time + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +CET 3600 # Central Europe Time + # (Africa/Algiers) + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +EAT 10800 # East Africa Time + # (Africa/Addis_Ababa) + # (Africa/Asmera) + # (Africa/Dar_es_Salaam) + # (Africa/Djibouti) + # (Africa/Kampala) + # (Africa/Khartoum) + # (Africa/Mogadishu) + # (Africa/Nairobi) + # (Indian/Antananarivo) + # (Indian/Comoro) + # (Indian/Mayotte) +EEST 10800 D # East-Egypt Summer Time + # Eastern Europe Summer Time + # (Africa/Cairo) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EET 7200 # East-Egypt Time + # Eastern Europe Time + # (Africa/Cairo) + # (Africa/Tripoli) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +GMT 0 # Greenwich Mean Time + # (Africa/Abidjan) + # (Africa/Bamako) + # (Africa/Banjul) + # (Africa/Bissau) + # (Africa/Conakry) + # (Africa/Dakar) + # (Africa/Lome) + # (Africa/Monrovia) + # (Africa/Nouakchott) + # (Africa/Ouagadougou) + # (Africa/Sao_Tome) + # (America/Danmarkshavn) + # (Atlantic/Reykjavik) + # (Atlantic/St_Helena) + # (Etc/GMT) + # (Europe/Dublin) + # (Europe/London) +# CONFLICT! SAST is not unique +# Other timezones: +# - SAST South Australian Standard Time (not in IANA database) +SAST 7200 # South Africa Standard Time + # (Africa/Johannesburg) +WAST 7200 D # West Africa Summer Time (obsolete) +WAT 3600 # West Africa Time + # (Africa/Bangui) + # (Africa/Brazzaville) + # (Africa/Douala) + # (Africa/Kinshasa) + # (Africa/Lagos) + # (Africa/Libreville) + # (Africa/Luanda) + # (Africa/Malabo) + # (Africa/Ndjamena) + # (Africa/Niamey) + # (Africa/Porto-Novo) + # (Africa/Windhoek) +WEST 3600 D # Western Europe Summer Time + # (Africa/Casablanca) + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) +WET 0 # Western Europe Time + # (Africa/Casablanca) + # (Africa/El_Aaiun) + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) diff --git a/src/timezone/tznames/America.txt b/src/timezone/tznames/America.txt new file mode 100644 index 0000000..2594c37 --- /dev/null +++ b/src/timezone/tznames/America.txt @@ -0,0 +1,257 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/America.txt +# + +# Acre time is sometimes called Acre Standard Time (AST) which leads to a +# conflict with AST (see below at AST) +ACT -18000 # Acre Time (obsolete) +# CONFLICT! ACST is not unique +# Other timezones: +# - ACST: Australian Central Standard Time +ACST -14400 D # Acre Summer Time (obsolete, not in IANA database) +ADT -10800 D # Atlantic Daylight Time + # (America/Glace_Bay) + # (America/Goose_Bay) + # (America/Halifax) + # (America/Thule) + # (Atlantic/Bermuda) +AKDT -28800 D # Alaska Daylight Time + # (America/Anchorage) + # (America/Juneau) + # (America/Nome) + # (America/Yakutat) +AKST -32400 # Alaska Standard Time + # (America/Anchorage) + # (America/Juneau) + # (America/Nome) + # (America/Yakutat) +# CONFLICT! AMST is not unique +# Other timezones: +# - AMST: Armenia Summer Time (Asia) +AMST -10800 D # Amazon Summer Time (obsolete) +# CONFLICT! AMT is not unique +# Other timezones: +# - AMT: Armenia Time (Asia) +AMT -14400 # Amazon Time (obsolete) +ART America/Argentina/Buenos_Aires # Argentina Time (obsolete) +ARST America/Argentina/Buenos_Aires # Argentina Summer Time (obsolete) +# CONFLICT! AST is not unique +# Other timezones: +# - AST: Arabic Standard Time (Asia) +# - AST: Al Manamah Standard Time (Asia) same offset as Arabia Standard Time +# - AST/ACT: Acre Standard Time (America) listed as ACT +# - AST: Anguilla Standard Time (America) same offset +# - AST: Antigua Standard Time (America) same offset +# - AST: Antilles Standard Time (America) same offset +AST -14400 # Atlantic Standard Time + # (America/Anguilla) + # (America/Antigua) + # (America/Aruba) + # (America/Curacao) + # (America/Dominica) + # (America/Glace_Bay) + # (America/Goose_Bay) + # (America/Grenada) + # (America/Guadeloupe) + # (America/Halifax) + # (America/Martinique) + # (America/Montserrat) + # (America/Port_of_Spain) + # (America/Puerto_Rico) + # (America/Santo_Domingo) + # (America/St_Kitts) + # (America/St_Lucia) + # (America/St_Thomas) + # (America/St_Vincent) + # (America/Thule) + # (America/Tortola) + # (Atlantic/Bermuda) +BOT -14400 # Bolivia Time (obsolete) +BRA -10800 # Brazil Time (not in IANA database) +BRST -7200 D # Brasil Summer Time (obsolete) +BRT -10800 # Brasil Time (obsolete) +# CONFLICT! CDT is not unique +# Other timezones: +# - CDT: Central Daylight Time (America) +# - CDT: Mexico Central Daylight Time (America) +# - CDT: Canada Central Daylight Time (America) +CDT -14400 D # Cuba Central Daylight Time + # (America/Havana) +# CONFLICT! CDT is not unique +# Other timezones: +# - CDT: Mexico Central Daylight Time (America) +# - CDT: Cuba Central Daylight Time (America) +# - CDT: Canada Central Daylight Time (America) +CDT -18000 D # Central Daylight Time + # (America/Chicago) + # (America/Menominee) + # (America/Merida) + # (America/Mexico_City) + # (America/Monterrey) + # (America/North_Dakota/Center) + # (America/Rainy_River) + # (America/Rankin_Inlet) + # (America/Winnipeg) +CLST -10800 D # Chile Summer Time (obsolete) +CLT America/Santiago # Chile Time (obsolete) +COT -18000 # Columbia Time (obsolete) +# CONFLICT! CST is not unique +# Other timezones: +# - CST: Central Standard Time (Australia) +# - CST: Central Standard Time (America) +# - CST: China Standard Time (Asia) +CST -18000 # Cuba Central Standard Time (America) + # (America/Havana) +# CONFLICT! CST is not unique +# Other timezones: +# - CST: Central Standard Time (Australia) +# - CST: China Standard Time (Asia) +# - CST: Cuba Central Standard Time (America) +CST -21600 # Central Standard Time (America) + # (America/Chicago) + # (America/Menominee) + # (America/Merida) + # (America/Mexico_City) + # (America/Monterrey) + # (America/North_Dakota/Center) + # (America/Rainy_River) + # (America/Rankin_Inlet) + # (America/Regina) + # (America/Swift_Current) + # (America/Winnipeg) +ECT -18000 # Ecuador Time (obsolete) +EDT -14400 D # Eastern Daylight Saving Time + # (America/Detroit) + # (America/Grand_Turk) + # (America/Indiana/Indianapolis) + # (America/Indiana/Knox) + # (America/Indiana/Marengo) + # (America/Indiana/Vevay) + # (America/Iqaluit) + # (America/Kentucky/Louisville) + # (America/Kentucky/Monticello) + # (America/Montreal) + # (America/Nassau) + # (America/New_York) + # (America/Nipigon) + # (America/Pangnirtung) + # (America/Thunder_Bay) + # (America/Toronto) +EGST 0 D # East Greenland Summer Time (obsolete) +EGT -3600 # East Greenland Time (Svalbard & Jan Mayen) (obsolete) +# CONFLICT! EST is not unique +# Other timezones: +# - EST: Eastern Standard Time (Australia) +EST -18000 # Eastern Standard Time (America) + # (America/Cancun) + # (America/Cayman) + # (America/Coral_Harbour) + # (America/Detroit) + # (America/Grand_Turk) + # (America/Indiana/Indianapolis) + # (America/Indiana/Knox) + # (America/Indiana/Marengo) + # (America/Indiana/Vevay) + # (America/Iqaluit) + # (America/Jamaica) + # (America/Kentucky/Louisville) + # (America/Kentucky/Monticello) + # (America/Montreal) + # (America/Nassau) + # (America/New_York) + # (America/Nipigon) + # (America/Panama) + # (America/Pangnirtung) + # (America/Thunder_Bay) + # (America/Toronto) +FNT -7200 # Fernando de Noronha Time (obsolete) +FNST -3600 D # Fernando de Noronha Summer Time (obsolete) +GFT -10800 # French Guiana Time (obsolete) +GMT 0 # Greenwich Mean Time + # (Africa/Abidjan) + # (Africa/Bamako) + # (Africa/Banjul) + # (Africa/Bissau) + # (Africa/Conakry) + # (Africa/Dakar) + # (Africa/Lome) + # (Africa/Monrovia) + # (Africa/Nouakchott) + # (Africa/Ouagadougou) + # (Africa/Sao_Tome) + # (America/Danmarkshavn) + # (Atlantic/Reykjavik) + # (Atlantic/St_Helena) + # (Etc/GMT) + # (Europe/Dublin) + # (Europe/London) +GYT America/Guyana # Guyana Time (obsolete) +HADT -32400 D # Hawaii-Aleutian Daylight Time (obsolete abbreviation) + # (America/Adak) +HAST -36000 # Hawaii-Aleutian Standard Time (obsolete abbreviation) + # (America/Adak) +HDT -32400 D # Hawaiian-Aleutian Daylight Time + # (America/Adak) +MDT -21600 D # Mexico Mountain Daylight Time + # Mountain Daylight Time + # (America/Boise) + # (America/Cambridge_Bay) + # (America/Chihuahua) + # (America/Denver) + # (America/Edmonton) + # (America/Inuvik) + # (America/Mazatlan) + # (America/Yellowknife) +MST -25200 # Mexico Mountain Standard Time + # Mountain Standard Time + # (America/Boise) + # (America/Cambridge_Bay) + # (America/Chihuahua) + # (America/Dawson_Creek) + # (America/Denver) + # (America/Edmonton) + # (America/Hermosillo) + # (America/Inuvik) + # (America/Mazatlan) + # (America/Phoenix) + # (America/Yellowknife) +NDT -9000 D # Newfoundland Daylight Time + # (America/St_Johns) +# CONFLICT! NFT is not unique +# Other timezones: +# - NFT: Norfolk Time (Pacific) +NFT -12600 # Newfoundland Time (not in IANA database) +NST -12600 # Newfoundland Standard Time + # (America/St_Johns) +PDT -25200 D # Pacific Daylight Time + # (America/Dawson) + # (America/Los_Angeles) + # (America/Tijuana) + # (America/Vancouver) + # (America/Whitehorse) +PET -18000 # Peru Time (obsolete) +PMDT -7200 D # Pierre & Miquelon Daylight Time (obsolete) +PMST -10800 # Pierre & Miquelon Standard Time (obsolete) +# CONFLICT! PST is not unique +# Other timezones: +# - PST: Philippine Standard Time +PST -28800 # Pacific Standard Time + # (America/Dawson) + # (America/Los_Angeles) + # (America/Tijuana) + # (America/Vancouver) + # (America/Whitehorse) + # (Pacific/Pitcairn) +PYST -10800 D # Paraguay Summer Time (obsolete) +PYT America/Asuncion # Paraguay Time (obsolete) +SRT America/Paramaribo # Suriname Time (obsolete) +UYST -7200 D # Uruguay Summer Time (obsolete) +UYT -10800 # Uruguay Time (obsolete) +VET America/Caracas # Venezuela Time (obsolete) +WGST -7200 D # Western Greenland Summer Time (obsolete) +WGT -10800 # West Greenland Time (obsolete) diff --git a/src/timezone/tznames/Antarctica.txt b/src/timezone/tznames/Antarctica.txt new file mode 100644 index 0000000..413b928 --- /dev/null +++ b/src/timezone/tznames/Antarctica.txt @@ -0,0 +1,27 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Antarctica.txt +# + +AWST 28800 # Australian Western Standard Time + # (Antarctica/Casey) + # (Australia/Perth) +CLST -10800 D # Chile Summer Time (obsolete) +CLT America/Santiago # Chile Time (obsolete) +DAVT Antarctica/Davis # Davis Time (Antarctica) (obsolete) +DDUT 36000 # Dumont-d'Urville Time (Antarctica) (obsolete) +MAWT Antarctica/Mawson # Mawson Time (Antarctica) (obsolete) +MIST 39600 # Macquarie Island Time (obsolete) +NZDT 46800 D # New Zealand Daylight Time + # (Antarctica/McMurdo) + # (Pacific/Auckland) +NZST 43200 # New Zealand Standard Time + # (Antarctica/McMurdo) + # (Pacific/Auckland) +ROTT -10800 # Rothera Time (obsolete) +SYOT 10800 # Syowa Time (obsolete) +VOST 21600 # Vostok time (obsolete) diff --git a/src/timezone/tznames/Asia.txt b/src/timezone/tznames/Asia.txt new file mode 100644 index 0000000..1133339 --- /dev/null +++ b/src/timezone/tznames/Asia.txt @@ -0,0 +1,190 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Asia.txt +# + +AFT 16200 # Afghanistan Time (obsolete) +ALMST 25200 D # Alma-Ata Summer Time (obsolete) +ALMT 21600 # Alma-Ata Time (obsolete) +# CONFLICT! AMST is not unique +# Other timezones: +# - AMST: Amazon Summer Time (America) +AMST Asia/Yerevan # Armenia Summer Time (obsolete) +# CONFLICT! AMT is not unique +# Other timezones: +# - AMT: Amazon Time (America) +AMT Asia/Yerevan # Armenia Time (obsolete) +ANAST Asia/Anadyr # Anadyr Summer Time (obsolete) +ANAT Asia/Anadyr # Anadyr Time (obsolete) +AQTST Asia/Aqtau # Aqtau Summer Time (obsolete) +AQTT Asia/Aqtau # Aqtau Time (obsolete) +# CONFLICT! AST is not unique +# Other timezones: +# - AST: Atlantic Standard Time (America) +# - AST/ACT: Acre Standard Time (America) listed as ACT +# - AST: Anguilla Standard Time (America) same offset as Atlantic Standard Time +# - AST: Antigua Standard Time (America) same offset as Atlantic Standard Time +# - AST: Antilles Standard Time (America) same offset as Atlantic Standard Time +# - AST: Al Manamah Standard Time (Asia) same offset as Arabia Standard Time +AST 10800 # Arabia Standard Time (obsolete) +AZST Asia/Baku # Azerbaijan Summer Time (obsolete) +AZT Asia/Baku # Azerbaijan Time (obsolete) +BDT 21600 # Bangladesh Time (obsolete) +BNT 28800 # Brunei Darussalam Time (obsolete) +BORT 28800 # Borneo Time (Indonesia) (not in IANA database) +BTT 21600 # Bhutan Time (obsolete) +CCT 28800 # China Coastal Time (not in IANA database) +CHOST Asia/Choibalsan # Choibalsan Summer Time (obsolete) +CHOT Asia/Choibalsan # Choibalsan Time (obsolete) +CIT 28800 # Central Indonesia Time (obsolete, WITA is now preferred) +# CONFLICT! CST is not unique +# Other timezones: +# - CST: Central Standard Time (Australia) +# - CST: Central Standard Time (America) +# - CST: Cuba Central Standard Time (America) +CST 28800 # China Standard Time + # (Asia/Macau) + # (Asia/Shanghai) + # (Asia/Taipei) +EEST 10800 D # East-Egypt Summer Time + # Eastern Europe Summer Time + # (Africa/Cairo) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EET 7200 # East-Egypt Time + # Eastern Europe Time + # (Africa/Cairo) + # (Africa/Tripoli) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EIT 32400 # East Indonesia Time (obsolete, WIT is now preferred) +GEST Asia/Tbilisi # Georgia Summer Time (obsolete) +GET Asia/Tbilisi # Georgia Time (obsolete) +# CONFLICT! GST is not unique +# Other timezones: +# - GST: South Georgia Time (Atlantic) +GST 14400 # Gulf Standard Time (obsolete) +HKT 28800 # Hong Kong Time + # (Asia/Hong_Kong) +HOVST 28800 D # Hovd Summer Time (obsolete) +HOVT Asia/Hovd # Hovd Time (obsolete) +ICT 25200 # Indochina Time (obsolete) +IDT 10800 D # Israel Daylight Time + # (Asia/Jerusalem) +IRDT Asia/Tehran # Iran Daylight Time (obsolete) +IRKST Asia/Irkutsk # Irkutsk Summer Time (obsolete) +IRKT Asia/Irkutsk # Irkutsk Time (obsolete) +IRST Asia/Tehran # Iran Standard Time (obsolete) +IRT 12600 # Iran Time (not in IANA database) +# CONFLICT! IST is not unique +# Other timezones: +# - IST: Irish Standard Time (Europe) +# - IST: Israel Standard Time (Asia) +IST 19800 # Indian Standard Time + # (Asia/Calcutta) +# CONFLICT! IST is not unique +# Other timezones: +# - IST: Irish Standard Time (Europe) +# - IST: Indian Standard Time (Asia) +IST 7200 # Israel Standard Time + # (Asia/Jerusalem) +JAYT 32400 # Jayapura Time (Indonesia) (not in IANA database) +JST 32400 # Japan Standard Time + # (Asia/Tokyo) +KDT 36000 D # Korean Daylight Time (not in IANA database) +KGST 21600 D # Kyrgyzstan Summer Time (obsolete) +KGT Asia/Bishkek # Kyrgyzstan Time (obsolete) +KRAST Asia/Krasnoyarsk # Krasnoyarsk Summer Time (obsolete) +KRAT Asia/Krasnoyarsk # Krasnoyarsk Time (obsolete) +KST Asia/Pyongyang # Korean Standard Time + # (Asia/Pyongyang) +KST 32400 # Korean Standard Time + # (Asia/Seoul) +LKT Asia/Colombo # Lanka Time (obsolete) +MAGST Asia/Magadan # Magadan Summer Time (obsolete) +MAGT Asia/Magadan # Magadan Time (obsolete) +MMT 23400 # Myanmar Time (obsolete) +MYT 28800 # Malaysia Time (obsolete) +NOVST Asia/Novosibirsk # Novosibirsk Summer Time (obsolete) +NOVT Asia/Novosibirsk # Novosibirsk Time (obsolete) +NPT 20700 # Nepal Time (obsolete) +OMSST Asia/Omsk # Omsk Summer Time (obsolete) +OMST Asia/Omsk # Omsk Time (obsolete) +ORAT Asia/Oral # Oral Time (obsolete) +PETST Asia/Kamchatka # Petropavlovsk-Kamchatski Summer Time (obsolete) +PETT Asia/Kamchatka # Petropavlovsk-Kamchatski Time (obsolete) +PHT 28800 # Philippine Time (obsolete) +PKT 18000 # Pakistan Time + # (Asia/Karachi) +PKST 21600 D # Pakistan Summer Time + # (Asia/Karachi) +# CONFLICT! PST is not unique +# Other timezones: +# - PST: Pacific Standard Time (America) +PST 28800 # Philippine Standard Time +QYZT 21600 # Kizilorda Time (obsolete) +SAKST Asia/Sakhalin # Sakhalin Summer Time (obsolete) +SAKT Asia/Sakhalin # Sakhalin Time (obsolete) +SGT Asia/Singapore # Singapore Time (obsolete) +SRET 39600 # Srednekolymsk Time (obsolete) +TJT 18000 # Tajikistan Time (obsolete) +TLT 32400 # East Timor Time (obsolete) +TMT Asia/Ashgabat # Turkmenistan Time (obsolete) +ULAST 32400 D # Ulan Bator Summer Time (obsolete) +ULAT Asia/Ulaanbaatar # Ulan Bator Time (obsolete) +UZST 21600 D # Uzbekistan Summer Time (obsolete) +UZT 18000 # Uzbekistan Time (obsolete) +VLAST Asia/Vladivostok # Vladivostok Summer Time (obsolete) +VLAT Asia/Vladivostok # Vladivostok Time (obsolete) +WIB 25200 # Waktu Indonesia Barat + # (Asia/Jakarta) + # (Asia/Pontianak) +WIT 32400 # Waktu Indonesia Timur (caution: this used to mean 25200) + # (Asia/Jayapura) +WITA 28800 # Waktu Indonesia Tengah + # (Asia/Makassar) +XJT 21600 # Xinjiang Time (obsolete) +YAKST Asia/Yakutsk # Yakutsk Summer Time (obsolete) +YAKT Asia/Yakutsk # Yakutsk Time (obsolete) +YEKST 21600 D # Yekaterinburg Summer Time (obsolete) +YEKT Asia/Yekaterinburg # Yekaterinburg Time (obsolete) diff --git a/src/timezone/tznames/Atlantic.txt b/src/timezone/tznames/Atlantic.txt new file mode 100644 index 0000000..4e036cd --- /dev/null +++ b/src/timezone/tznames/Atlantic.txt @@ -0,0 +1,85 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Atlantic.txt +# + +ADT -10800 D # Atlantic Daylight Time + # (America/Glace_Bay) + # (America/Goose_Bay) + # (America/Halifax) + # (America/Thule) + # (Atlantic/Bermuda) +# CONFLICT! AST is not unique +# Other timezones: +# - AST: Arabic Standard Time (Asia) +# - AST: Al Manamah Standard Time (Asia) same offset as Arabia Standard Time +# - AST/ACT: Acre Standard Time (America) listed as ACT +# - AST: Anguilla Standard Time (America) same offset +# - AST: Antigua Standard Time (America) same offset +# - AST: Antilles Standard Time (America) same offset +AST -14400 # Atlantic Standard Time + # (America/Anguilla) + # (America/Antigua) + # (America/Aruba) + # (America/Curacao) + # (America/Dominica) + # (America/Glace_Bay) + # (America/Goose_Bay) + # (America/Grenada) + # (America/Guadeloupe) + # (America/Halifax) + # (America/Martinique) + # (America/Montserrat) + # (America/Port_of_Spain) + # (America/Puerto_Rico) + # (America/Santo_Domingo) + # (America/St_Kitts) + # (America/St_Lucia) + # (America/St_Thomas) + # (America/St_Vincent) + # (America/Thule) + # (America/Tortola) + # (Atlantic/Bermuda) +AZOST 0 D # Azores Summer Time (obsolete) +AZOT -3600 # Azores Time (obsolete) +CVT Atlantic/Cape_Verde # Cape Verde Time (obsolete) +FKST Atlantic/Stanley # Falkland Islands Summer/Standard Time (obsolete) +FKT Atlantic/Stanley # Falkland Islands Time (obsolete) +GMT 0 # Greenwich Mean Time + # (Africa/Abidjan) + # (Africa/Bamako) + # (Africa/Banjul) + # (Africa/Bissau) + # (Africa/Conakry) + # (Africa/Dakar) + # (Africa/Lome) + # (Africa/Monrovia) + # (Africa/Nouakchott) + # (Africa/Ouagadougou) + # (Africa/Sao_Tome) + # (America/Danmarkshavn) + # (Atlantic/Reykjavik) + # (Atlantic/St_Helena) + # (Etc/GMT) + # (Europe/Dublin) + # (Europe/London) +# CONFLICT! GST is not unique +# Other timezones: +# - GST: Gulf Standard Time (Asia) +GST -7200 # South Georgia Time (Atlantic) (obsolete) +WEST 3600 D # Western Europe Summer Time + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) +WET 0 # Western Europe Time + # (Africa/Casablanca) + # (Africa/El_Aaiun) + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) diff --git a/src/timezone/tznames/Australia b/src/timezone/tznames/Australia new file mode 100644 index 0000000..7216e06 --- /dev/null +++ b/src/timezone/tznames/Australia @@ -0,0 +1,27 @@ +# Time zone configuration file for set "Australia" + +# The abbreviations set up by this file are no longer in widespread use, +# and should be avoided when possible. Use this file if you need backwards +# compatibility with old applications or data. + +# In order to use this file, you need to set the run-time parameter +# timezone_abbreviations to 'Australia'. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Australia + + +# include the default set +@INCLUDE Default + +# most timezones are already defined in the default set. With the OVERRIDE +# option, PostgreSQL will use the new definitions instead of throwing an error +# in case of a conflict. +@OVERRIDE + +CST 34200 # Central Standard Time (not in IANA database) +EAST 36000 # East Australian Standard Time (not in IANA database) +EST 36000 # Eastern Standard Time (not in IANA database) +SAST 34200 # South Australian Standard Time (not in IANA database) +SAT 34200 # South Australian Standard Time (not in IANA database) +WST 28800 # Western Standard Time (not in IANA database) diff --git a/src/timezone/tznames/Australia.txt b/src/timezone/tznames/Australia.txt new file mode 100644 index 0000000..da90866 --- /dev/null +++ b/src/timezone/tznames/Australia.txt @@ -0,0 +1,71 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Australia.txt +# + +ACSST 37800 D # Australian Central Summer Standard Time (not in IANA database) +ACDT 37800 D # Australian Central Daylight Time + # (Australia/Adelaide) + # (Australia/Broken_Hill) + # (Australia/Darwin) +ACST 34200 # Australian Central Standard Time + # (Australia/Adelaide) + # (Australia/Broken_Hill) + # (Australia/Darwin) +ACWST 31500 # Australian Central Western Standard Time (obsolete) +AESST 39600 D # Australian Eastern Summer Standard Time (not in IANA database) +AEDT 39600 D # Australian Eastern Daylight Time + # (Australia/Brisbane) + # (Australia/Currie) + # (Australia/Hobart) + # (Australia/Lindeman) + # (Australia/Melbourne) + # (Australia/Sydney) +AEST 36000 # Australian Eastern Standard Time + # (Australia/Brisbane) + # (Australia/Currie) + # (Australia/Hobart) + # (Australia/Lindeman) + # (Australia/Melbourne) + # (Australia/Sydney) +AWSST 32400 D # Australia Western Summer Standard Time (not in IANA database) +AWST 28800 # Australian Western Standard Time + # (Australia/Perth) +CADT 37800 D # Central Australia Daylight-Saving Time (not in IANA database) +CAST 34200 # Central Australia Standard Time (not in IANA database) +# CONFLICT! CST is not unique +# Other timezones: +# - CST: Central Standard Time (America) +# - CST: China Standard Time (Asia) +# - CST: Cuba Central Standard Time (America) +CST 34200 # Central Standard Time (not in IANA database) +CWST 31500 # Central Western Standard Time (not in IANA database) +# CONFLICT! EAST is not unique +# Other timezones: +# - EAST: Easter Island Time (Chile) (Pacific) +EAST 36000 # East Australian Standard Time (not in IANA database) +# CONFLICT! EST is not unique +# Other timezones: +# - EST: Eastern Standard Time (America) +EST 36000 # Eastern Standard Time (not in IANA database) +LHDT Australia/Lord_Howe # Lord Howe Daylight Time (obsolete) +LHST 37800 # Lord Howe Standard Time (obsolete) +LIGT 36000 # Melbourne, Australia (not in IANA database) +NZT 43200 # New Zealand Time (not in IANA database) +SADT 37800 D # South Australian Daylight-Saving Time (not in IANA database) +# CONFLICT! SAST is not unique +# Other timezones: +# - SAST South Africa Standard Time +SAST 34200 # South Australian Standard Time (not in IANA database) +SAT 34200 # South Australian Standard Time (not in IANA database) +WADT 28800 D # West Australian Daylight-Saving Time (not in IANA database) +WAST 25200 # West Australian Standard Time (not in IANA database) +WDT 32400 D # West Australian Daylight-Saving Time (not in IANA database) +# CONFLICT! WST is not unique +# Other timezones: +# - WST: West Samoa Time +WST 28800 # Western Standard Time (not in IANA database) diff --git a/src/timezone/tznames/Default b/src/timezone/tznames/Default new file mode 100644 index 0000000..8a4dc59 --- /dev/null +++ b/src/timezone/tznames/Default @@ -0,0 +1,632 @@ +# Time zone configuration file for set "Default" + +# In order to use this file, you need to set the run-time parameter +# timezone_abbreviations to 'Default'. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Default + + +#################### AFRICA #################### + +EAT 10800 # East Africa Time + # (Africa/Addis_Ababa) + # (Africa/Asmera) + # (Africa/Dar_es_Salaam) + # (Africa/Djibouti) + # (Africa/Kampala) + # (Africa/Khartoum) + # (Africa/Mogadishu) + # (Africa/Nairobi) + # (Indian/Antananarivo) + # (Indian/Comoro) + # (Indian/Mayotte) +SAST 7200 # South Africa Standard Time + # (Africa/Johannesburg) +WAT 3600 # West Africa Time + # (Africa/Bangui) + # (Africa/Brazzaville) + # (Africa/Douala) + # (Africa/Kinshasa) + # (Africa/Lagos) + # (Africa/Libreville) + # (Africa/Luanda) + # (Africa/Malabo) + # (Africa/Ndjamena) + # (Africa/Niamey) + # (Africa/Porto-Novo) + # (Africa/Windhoek) + +#################### AMERICA #################### + +# Acre time is sometimes called Acre Standard Time (AST) which leads to a +# conflict with AST (see below at AST) +ACT -18000 # Acre Time (obsolete) +AKDT -28800 D # Alaska Daylight Time + # (America/Anchorage) + # (America/Juneau) + # (America/Nome) + # (America/Yakutat) +AKST -32400 # Alaska Standard Time + # (America/Anchorage) + # (America/Juneau) + # (America/Nome) + # (America/Yakutat) +ART America/Argentina/Buenos_Aires # Argentina Time (obsolete) +ARST America/Argentina/Buenos_Aires # Argentina Summer Time (obsolete) +BOT -14400 # Bolivia Time (obsolete) +BRA -10800 # Brazil Time (not in IANA database) +BRST -7200 D # Brasil Summer Time (obsolete) +BRT -10800 # Brasil Time (obsolete) +COT -18000 # Columbia Time (obsolete) +# CONFLICT! CDT is not unique +# Other timezones: +# - CDT: Mexico Central Daylight Time (America) +# - CDT: Cuba Central Daylight Time (America) +# - CDT: Canada Central Daylight Time (America) +CDT -18000 D # Central Daylight Time + # (America/Chicago) + # (America/Menominee) + # (America/Merida) + # (America/Mexico_City) + # (America/Monterrey) + # (America/North_Dakota/Center) + # (America/Rainy_River) + # (America/Rankin_Inlet) + # (America/Winnipeg) +CLST -10800 D # Chile Summer Time (obsolete) +CLT America/Santiago # Chile Time (obsolete) +# CONFLICT! CST is not unique +# Other timezones: +# - CST: Central Standard Time (Australia) +# - CST: China Standard Time (Asia) +# - CST: Cuba Central Standard Time (America) +CST -21600 # Central Standard Time (America) + # (America/Chicago) + # (America/Menominee) + # (America/Merida) + # (America/Mexico_City) + # (America/Monterrey) + # (America/North_Dakota/Center) + # (America/Rainy_River) + # (America/Rankin_Inlet) + # (America/Regina) + # (America/Swift_Current) + # (America/Winnipeg) +EDT -14400 D # Eastern Daylight Saving Time + # (America/Detroit) + # (America/Grand_Turk) + # (America/Indiana/Indianapolis) + # (America/Indiana/Knox) + # (America/Indiana/Marengo) + # (America/Indiana/Vevay) + # (America/Iqaluit) + # (America/Kentucky/Louisville) + # (America/Kentucky/Monticello) + # (America/Montreal) + # (America/Nassau) + # (America/New_York) + # (America/Nipigon) + # (America/Pangnirtung) + # (America/Thunder_Bay) + # (America/Toronto) +EGST 0 D # East Greenland Summer Time (obsolete) +EGT -3600 # East Greenland Time (Svalbard & Jan Mayen) (obsolete) +# CONFLICT! EST is not unique +# Other timezones: +# - EST: Eastern Standard Time (Australia) +EST -18000 # Eastern Standard Time (America) + # (America/Cancun) + # (America/Cayman) + # (America/Coral_Harbour) + # (America/Detroit) + # (America/Grand_Turk) + # (America/Indiana/Indianapolis) + # (America/Indiana/Knox) + # (America/Indiana/Marengo) + # (America/Indiana/Vevay) + # (America/Iqaluit) + # (America/Jamaica) + # (America/Kentucky/Louisville) + # (America/Kentucky/Monticello) + # (America/Montreal) + # (America/Nassau) + # (America/New_York) + # (America/Nipigon) + # (America/Panama) + # (America/Pangnirtung) + # (America/Thunder_Bay) + # (America/Toronto) +FNT -7200 # Fernando de Noronha Time (obsolete) +FNST -3600 D # Fernando de Noronha Summer Time (obsolete) +GFT -10800 # French Guiana Time (obsolete) +GYT America/Guyana # Guyana Time (obsolete) +MDT -21600 D # Mexico Mountain Daylight Time + # Mountain Daylight Time + # (America/Boise) + # (America/Cambridge_Bay) + # (America/Chihuahua) + # (America/Denver) + # (America/Edmonton) + # (America/Inuvik) + # (America/Mazatlan) + # (America/Yellowknife) +MST -25200 # Mexico Mountain Standard Time + # Mountain Standard Time + # (America/Boise) + # (America/Cambridge_Bay) + # (America/Chihuahua) + # (America/Dawson_Creek) + # (America/Denver) + # (America/Edmonton) + # (America/Hermosillo) + # (America/Inuvik) + # (America/Mazatlan) + # (America/Phoenix) + # (America/Yellowknife) +NDT -9000 D # Newfoundland Daylight Time + # (America/St_Johns) +# CONFLICT! NFT is not unique +# Other timezones: +# - NFT: Norfolk Time (Pacific) +NFT -12600 # Newfoundland Time (not in IANA database) +NST -12600 # Newfoundland Standard Time + # (America/St_Johns) +PET -18000 # Peru Time (obsolete) +PDT -25200 D # Pacific Daylight Time + # (America/Dawson) + # (America/Los_Angeles) + # (America/Tijuana) + # (America/Vancouver) + # (America/Whitehorse) +PMDT -7200 D # Pierre & Miquelon Daylight Time (obsolete) +PMST -10800 # Pierre & Miquelon Standard Time (obsolete) +# CONFLICT! PST is not unique +# Other timezones: +# - PST: Philippine Standard Time +PST -28800 # Pacific Standard Time + # (America/Dawson) + # (America/Los_Angeles) + # (America/Tijuana) + # (America/Vancouver) + # (America/Whitehorse) + # (Pacific/Pitcairn) +PYST -10800 D # Paraguay Summer Time (obsolete) +PYT America/Asuncion # Paraguay Time (obsolete) +UYST -7200 D # Uruguay Summer Time (obsolete) +UYT -10800 # Uruguay Time (obsolete) +VET America/Caracas # Venezuela Time (obsolete) +WGST -7200 D # Western Greenland Summer Time (obsolete) +WGT -10800 # West Greenland Time (obsolete) + +#################### ANTARCTICA #################### + +DAVT Antarctica/Davis # Davis Time (Antarctica) (obsolete) +DDUT 36000 # Dumont-d'Urville Time (Antarctica) (obsolete) +MAWT Antarctica/Mawson # Mawson Time (Antarctica) (obsolete) + +#################### ASIA #################### + +AFT 16200 # Afghanistan Time (obsolete) +ALMT 21600 # Alma-Ata Time (obsolete) +ALMST 25200 D # Alma-Ata Summer Time (obsolete) +# CONFLICT! AMST is not unique +# Other timezones: +# - AMST: Amazon Summer Time (America) +AMST Asia/Yerevan # Armenia Summer Time (obsolete) +# CONFLICT! AMT is not unique +# Other timezones: +# - AMT: Armenia Time (Asia) +AMT -14400 # Amazon Time (obsolete) +ANAST Asia/Anadyr # Anadyr Summer Time (obsolete) +ANAT Asia/Anadyr # Anadyr Time (obsolete) +AZST Asia/Baku # Azerbaijan Summer Time (obsolete) +AZT Asia/Baku # Azerbaijan Time (obsolete) +BDT 21600 # Bangladesh Time (obsolete) +BNT 28800 # Brunei Darussalam Time (obsolete) +BORT 28800 # Borneo Time (Indonesia) (not in IANA database) +BTT 21600 # Bhutan Time (obsolete) +CCT 28800 # China Coastal Time (not in IANA database) +GEST Asia/Tbilisi # Georgia Summer Time (obsolete) +GET Asia/Tbilisi # Georgia Time (obsolete) +HKT 28800 # Hong Kong Time + # (Asia/Hong_Kong) +ICT 25200 # Indochina Time (obsolete) +IDT 10800 D # Israel Daylight Time + # (Asia/Jerusalem) +IRKST Asia/Irkutsk # Irkutsk Summer Time (obsolete) +IRKT Asia/Irkutsk # Irkutsk Time (obsolete) +IRT 12600 # Iran Time (not in IANA database) +# CONFLICT! IST is not unique +# Other timezones: +# - IST: Irish Standard Time (Europe) +# - IST: Indian Standard Time (Asia) +IST 7200 # Israel Standard Time + # (Asia/Jerusalem) +JAYT 32400 # Jayapura Time (Indonesia) (not in IANA database) +JST 32400 # Japan Standard Time + # (Asia/Tokyo) +KDT 36000 D # Korean Daylight Time (not in IANA database) +KGST 21600 D # Kyrgyzstan Summer Time (obsolete) +KGT Asia/Bishkek # Kyrgyzstan Time (obsolete) +KRAST Asia/Krasnoyarsk # Krasnoyarsk Summer Time (obsolete) +KRAT Asia/Krasnoyarsk # Krasnoyarsk Time (obsolete) +KST 32400 # Korean Standard Time + # (Asia/Seoul) +LKT Asia/Colombo # Lanka Time (obsolete) +MAGST Asia/Magadan # Magadan Summer Time (obsolete) +MAGT Asia/Magadan # Magadan Time (obsolete) +MMT 23400 # Myanmar Time (obsolete) +MYT 28800 # Malaysia Time (obsolete) +NOVST Asia/Novosibirsk # Novosibirsk Summer Time (obsolete) +NOVT Asia/Novosibirsk # Novosibirsk Time (obsolete) +NPT 20700 # Nepal Time (obsolete) +OMSST Asia/Omsk # Omsk Summer Time (obsolete) +OMST Asia/Omsk # Omsk Time (obsolete) +PETST Asia/Kamchatka # Petropavlovsk-Kamchatski Summer Time (obsolete) +PETT Asia/Kamchatka # Petropavlovsk-Kamchatski Time (obsolete) +PHT 28800 # Philippine Time (obsolete) +PKT 18000 # Pakistan Time + # (Asia/Karachi) +PKST 21600 D # Pakistan Summer Time + # (Asia/Karachi) +SGT Asia/Singapore # Singapore Time (obsolete) +TJT 18000 # Tajikistan Time (obsolete) +TMT Asia/Ashgabat # Turkmenistan Time (obsolete) +ULAST 32400 D # Ulan Bator Summer Time (obsolete) +ULAT Asia/Ulaanbaatar # Ulan Bator Time (obsolete) +UZST 21600 D # Uzbekistan Summer Time (obsolete) +UZT 18000 # Uzbekistan Time (obsolete) +VLAST Asia/Vladivostok # Vladivostok Summer Time (obsolete) +VLAT Asia/Vladivostok # Vladivostok Time (obsolete) +XJT 21600 # Xinjiang Time (obsolete) +YAKST Asia/Yakutsk # Yakutsk Summer Time (obsolete) +YAKT Asia/Yakutsk # Yakutsk Time (obsolete) +YEKST 21600 D # Yekaterinburg Summer Time (obsolete) +YEKT Asia/Yekaterinburg # Yekaterinburg Time (obsolete) + +#################### ATLANTIC #################### + +ADT -10800 D # Atlantic Daylight Time + # (America/Glace_Bay) + # (America/Goose_Bay) + # (America/Halifax) + # (America/Thule) + # (Atlantic/Bermuda) +# CONFLICT! AST is not unique +# Other timezones: +# - AST: Arabic Standard Time (Asia) +# - AST: Al Manamah Standard Time (Asia) same offset as Arabia Standard Time +# - AST/ACT: Acre Standard Time (America) listed as ACT +# - AST: Anguilla Standard Time (America) same offset +# - AST: Antigua Standard Time (America) same offset +# - AST: Antilles Standard Time (America) same offset +AST -14400 # Atlantic Standard Time + # (America/Anguilla) + # (America/Antigua) + # (America/Aruba) + # (America/Curacao) + # (America/Dominica) + # (America/Glace_Bay) + # (America/Goose_Bay) + # (America/Grenada) + # (America/Guadeloupe) + # (America/Halifax) + # (America/Martinique) + # (America/Montserrat) + # (America/Port_of_Spain) + # (America/Puerto_Rico) + # (America/Santo_Domingo) + # (America/St_Kitts) + # (America/St_Lucia) + # (America/St_Thomas) + # (America/St_Vincent) + # (America/Thule) + # (America/Tortola) + # (Atlantic/Bermuda) +AZOST 0 D # Azores Summer Time (obsolete) +AZOT -3600 # Azores Time (obsolete) +FKST Atlantic/Stanley # Falkland Islands Summer/Standard Time (obsolete) +FKT Atlantic/Stanley # Falkland Islands Time (obsolete) + +#################### AUSTRALIA #################### + +ACSST 37800 D # Australian Central Summer Standard Time (not in IANA database) +ACDT 37800 D # Australian Central Daylight Time + # (Australia/Adelaide) + # (Australia/Broken_Hill) + # (Australia/Darwin) +ACST 34200 # Australian Central Standard Time + # (Australia/Adelaide) + # (Australia/Broken_Hill) + # (Australia/Darwin) +ACWST 31500 # Australian Central Western Standard Time (obsolete) +AESST 39600 D # Australian Eastern Summer Standard Time (not in IANA database) +AEDT 39600 D # Australian Eastern Daylight Time + # (Australia/Brisbane) + # (Australia/Currie) + # (Australia/Hobart) + # (Australia/Lindeman) + # (Australia/Melbourne) + # (Australia/Sydney) +AEST 36000 # Australian Eastern Standard Time + # (Australia/Brisbane) + # (Australia/Currie) + # (Australia/Hobart) + # (Australia/Lindeman) + # (Australia/Melbourne) + # (Australia/Sydney) +AWSST 32400 D # Australia Western Summer Standard Time (not in IANA database) +AWST 28800 # Australian Western Standard Time + # (Australia/Perth) +CADT 37800 D # Central Australia Daylight-Saving Time (not in IANA database) +CAST 34200 # Central Australia Standard Time (not in IANA database) +LHDT Australia/Lord_Howe # Lord Howe Daylight Time (obsolete) +LHST 37800 # Lord Howe Standard Time (obsolete) +LIGT 36000 # Melbourne, Australia (not in IANA database) +NZT 43200 # New Zealand Time (not in IANA database) +SADT 37800 D # South Australian Daylight-Saving Time (not in IANA database) +WADT 28800 D # West Australian Daylight-Saving Time (not in IANA database) +WAST 25200 # West Australian Standard Time (not in IANA database) +WDT 32400 D # West Australian Daylight-Saving Time (not in IANA database) + +#################### ETC #################### + +GMT 0 # Greenwich Mean Time + # (Africa/Abidjan) + # (Africa/Bamako) + # (Africa/Banjul) + # (Africa/Bissau) + # (Africa/Conakry) + # (Africa/Dakar) + # (Africa/Lome) + # (Africa/Monrovia) + # (Africa/Nouakchott) + # (Africa/Ouagadougou) + # (Africa/Sao_Tome) + # (America/Danmarkshavn) + # (Atlantic/Reykjavik) + # (Atlantic/St_Helena) + # (Etc/GMT) + # (Europe/Dublin) + # (Europe/London) +UCT 0 # Universal Coordinated Time + # (Etc/UCT) +UT 0 # Universal Time (not in IANA database) +UTC 0 # Coordinated Universal Time +Z 0 # Zulu +ZULU 0 # Zulu + +#################### EUROPE #################### + +# CONFLICT! BST is not unique +# Other timezones: +# - BST: Bougainville Standard Time (Papua New Guinea) +BST 3600 D # British Summer Time + # (Europe/London) +BDST 7200 D # British Double Summer Time +CEST 7200 D # Central Europe Summer Time + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +CET 3600 # Central Europe Time + # (Africa/Algiers) + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +CETDST 7200 D # Central Europe Summer Time + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +EEST 10800 D # East-Egypt Summertime + # Eastern Europe Summer Time + # (Africa/Cairo) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EET 7200 # East-Egypt Time + # Eastern Europe Time + # (Africa/Cairo) + # (Africa/Tripoli) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EETDST 10800 D # East-Egypt Summertime + # Eastern Europe Summer Time + # (Africa/Cairo) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +FET 10800 # Further-eastern European Time (obsolete) + # (Europe/Kaliningrad) + # (Europe/Minsk) +MEST 7200 D # Middle Europe Summer Time + # (MET) +MESZ 7200 D # Mitteleuropaeische Sommerzeit (German) + # (attested in IANA comments though not their code) +MET 3600 # Middle Europe Time + # (MET) +METDST 7200 D # Middle Europe Summer Time (not in IANA database) +MEZ 3600 # Mitteleuropaeische Zeit (German) + # (attested in IANA comments though not their code) +MSD 14400 D # Moscow Daylight Time (obsolete) +MSK Europe/Moscow # Moscow Time + # (Europe/Moscow) + # (Europe/Volgograd) +VOLT Europe/Volgograd # Volgograd Time (obsolete) +WET 0 # Western Europe Time + # (Africa/Casablanca) + # (Africa/El_Aaiun) + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) +WETDST 3600 D # Western Europe Summer Time + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) + +#################### INDIAN #################### + +CXT 25200 # Christmas Island Time (Indian Ocean) (obsolete) +IOT Indian/Chagos # British Indian Ocean Territory (Chagos) (obsolete) +MUT 14400 # Mauritius Island Time (obsolete) +MUST 18000 D # Mauritius Island Summer Time (obsolete) +MVT 18000 # Maldives Island Time (obsolete) +RET 14400 # Reunion Time (obsolete) +SCT 14400 # Seychelles Time (obsolete) +TFT 18000 # Kerguelen Time (obsolete) + +#################### PACIFIC #################### + +CHADT 49500 D # Chatham Daylight Time (New Zealand) (obsolete) +CHAST 45900 # Chatham Standard Time (New Zealand) (obsolete) +CHUT 36000 # Chuuk Time (obsolete) +CKT Pacific/Rarotonga # Cook Islands Time (obsolete) +EASST Pacific/Easter # Easter Island Summer Time (obsolete) +EAST Pacific/Easter # Easter Island Time (Chile) (obsolete) +FJST 46800 D # Fiji Summer Time (caution: this used to mean -46800) (obsolete) +FJT 43200 # Fiji Time (caution: this used to mean -43200) (obsolete) +GALT -21600 # Galapagos Time (obsolete) +GAMT -32400 # Gambier Time (obsolete) +GILT 43200 # Gilbert Islands Time (obsolete) +HST -36000 # Hawaiian Standard Time + # (Pacific/Honolulu) + # (Pacific/Johnston) +KOST Pacific/Kosrae # Kosrae Time (obsolete) +LINT Pacific/Kiritimati # Line Islands Time (Kiribati) (obsolete) +MART -34200 # Marquesas Time (obsolete) +MHT 43200 # Kwajalein Time (obsolete) +MPT 36000 # North Mariana Islands Time (not in IANA database) +NUT Pacific/Niue # Niue Time (obsolete) +NZDT 46800 D # New Zealand Daylight Time + # (Antarctica/McMurdo) + # (Pacific/Auckland) +NZST 43200 # New Zealand Standard Time + # (Antarctica/McMurdo) + # (Pacific/Auckland) +PGT 36000 # Papua New Guinea Time (obsolete) +PHOT Pacific/Enderbury # Phoenix Islands Time (Kiribati) (obsolete) +PONT 39600 # Ponape Time (Micronesia) (obsolete) +PWT 32400 # Palau Time (obsolete) +TAHT -36000 # Tahiti Time (obsolete) +TKT Pacific/Fakaofo # Tokelau Time (obsolete) +TOT 46800 # Tonga Time (obsolete) +TRUT 36000 # Truk Time (obsolete) +TVT 43200 # Tuvalu Time (obsolete) +VUT 39600 # Vanuata Time (obsolete) +WAKT 43200 # Wake Time (obsolete) +WFT 43200 # Wallis and Futuna Time (obsolete) +YAPT 36000 # Yap Time (Micronesia) (not in IANA database) diff --git a/src/timezone/tznames/Etc.txt b/src/timezone/tznames/Etc.txt new file mode 100644 index 0000000..aa48404 --- /dev/null +++ b/src/timezone/tznames/Etc.txt @@ -0,0 +1,34 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Etc.txt +# + +GMT 0 # Greenwich Mean Time + # (Africa/Abidjan) + # (Africa/Bamako) + # (Africa/Banjul) + # (Africa/Bissau) + # (Africa/Conakry) + # (Africa/Dakar) + # (Africa/Lome) + # (Africa/Monrovia) + # (Africa/Nouakchott) + # (Africa/Ouagadougou) + # (Africa/Sao_Tome) + # (America/Danmarkshavn) + # (Atlantic/Reykjavik) + # (Atlantic/St_Helena) + # (Etc/GMT) + # (Europe/Dublin) + # (Europe/London) +UCT 0 # Universal Coordinated Time + # (Etc/UCT) +UT 0 # Universal Time (not in IANA database) +UTC 0 # Coordinated Universal Time + # (Etc/UTC) +Z 0 # Zulu +ZULU 0 # Zulu diff --git a/src/timezone/tznames/Europe.txt b/src/timezone/tznames/Europe.txt new file mode 100644 index 0000000..2e762b9 --- /dev/null +++ b/src/timezone/tznames/Europe.txt @@ -0,0 +1,219 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Europe.txt +# + +# CONFLICT! BST is not unique +# Other timezones: +# - BST: Bougainville Standard Time (Papua New Guinea) +BST 3600 D # British Summer Time + # (Europe/London) +BDST 7200 D # British Double Summer Time +CEST 7200 D # Central Europe Summer Time + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +CET 3600 # Central Europe Time + # (Africa/Algiers) + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +CETDST 7200 D # Central Europe Summer Time + # (Africa/Ceuta) + # (Europe/Amsterdam) + # (Europe/Andorra) + # (Europe/Belgrade) + # (Europe/Berlin) + # (Europe/Brussels) + # (Europe/Budapest) + # (Europe/Copenhagen) + # (Europe/Gibraltar) + # (Europe/Luxembourg) + # (Europe/Madrid) + # (Europe/Malta) + # (Europe/Monaco) + # (Europe/Oslo) + # (Europe/Paris) + # (Europe/Prague) + # (Europe/Rome) + # (Europe/Stockholm) + # (Europe/Tirane) + # (Europe/Vaduz) + # (Europe/Vienna) + # (Europe/Warsaw) + # (Europe/Zurich) +EEST 10800 D # East-Egypt Summertime + # Eastern Europe Summer Time + # (Africa/Cairo) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EET 7200 # East-Egypt Time + # Eastern Europe Time + # (Africa/Cairo) + # (Africa/Tripoli) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +EETDST 10800 D # East-Egypt Summertime + # Eastern Europe Summer Time + # (Africa/Cairo) + # (Asia/Amman) + # (Asia/Beirut) + # (Asia/Damascus) + # (Asia/Gaza) + # (Asia/Nicosia) + # (Europe/Athens) + # (Europe/Bucharest) + # (Europe/Chisinau) + # (Europe/Helsinki) + # (Europe/Istanbul) + # (Europe/Kaliningrad) + # (Europe/Kiev) + # (Europe/Minsk) + # (Europe/Riga) + # (Europe/Simferopol) + # (Europe/Sofia) + # (Europe/Tallinn) + # (Europe/Uzhgorod) + # (Europe/Vilnius) + # (Europe/Zaporozhye) +FET 10800 # Further-eastern European Time (obsolete) + # (Europe/Kaliningrad) + # (Europe/Minsk) +GMT 0 # Greenwich Mean Time + # (Africa/Abidjan) + # (Africa/Bamako) + # (Africa/Banjul) + # (Africa/Bissau) + # (Africa/Conakry) + # (Africa/Dakar) + # (Africa/Lome) + # (Africa/Monrovia) + # (Africa/Nouakchott) + # (Africa/Ouagadougou) + # (Africa/Sao_Tome) + # (America/Danmarkshavn) + # (Atlantic/Reykjavik) + # (Atlantic/St_Helena) + # (Etc/GMT) + # (Europe/Dublin) + # (Europe/London) +# CONFLICT! IST is not unique +# Other timezones: +# - IST: Indian Standard Time (Asia) +# - IST: Israel Standard Time (Asia) +IST 3600 # Irish Standard Time + # (Europe/Dublin) +MEST 7200 D # Middle Europe Summer Time + # (MET) +MESZ 7200 D # Mitteleuropaeische Sommerzeit (German) + # (attested in IANA comments though not their code) +MET 3600 # Middle Europe Time + # (MET) +METDST 7200 D # Middle Europe Summer Time (not in IANA database) +MEZ 3600 # Mitteleuropaeische Zeit (German) + # (attested in IANA comments though not their code) +MSD 14400 D # Moscow Daylight Time (obsolete) +MSK Europe/Moscow # Moscow Time + # (Europe/Moscow) + # (Europe/Volgograd) +SAMST Europe/Samara # Samara Summer Time (obsolete) +SAMT Europe/Samara # Samara Time (obsolete) +VOLT Europe/Volgograd # Volgograd Time (obsolete) +WEST 3600 D # Western Europe Summer Time + # (Africa/Casablanca) + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) +WET 0 # Western Europe Time + # (Africa/Casablanca) + # (Africa/El_Aaiun) + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) +WETDST 3600 D # Western Europe Summer Time + # (Atlantic/Canary) + # (Atlantic/Faeroe) + # (Atlantic/Madeira) + # (Europe/Lisbon) diff --git a/src/timezone/tznames/India b/src/timezone/tznames/India new file mode 100644 index 0000000..85830e9 --- /dev/null +++ b/src/timezone/tznames/India @@ -0,0 +1,19 @@ +# Time zone configuration file for set "India" + +# In order to use this file, you need to set the run-time parameter +# timezone_abbreviations to 'India'. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/India + + +# include the default set +@INCLUDE Default + +# most timezones are already defined in the default set. With the OVERRIDE +# option, PostgreSQL will use the new definitions instead of throwing an error +# in case of a conflict. +@OVERRIDE + +IST 19800 # Indian Standard Time + # (Asia/Calcutta) diff --git a/src/timezone/tznames/Indian.txt b/src/timezone/tznames/Indian.txt new file mode 100644 index 0000000..8e6fe60 --- /dev/null +++ b/src/timezone/tznames/Indian.txt @@ -0,0 +1,30 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Indian.txt +# + +CCT 23400 # Cocos Islands Time (Indian Ocean) (obsolete) +CXT 25200 # Christmas Island Time (Indian Ocean) (obsolete) +EAT 10800 # East Africa Time + # (Africa/Addis_Ababa) + # (Africa/Asmera) + # (Africa/Dar_es_Salaam) + # (Africa/Djibouti) + # (Africa/Kampala) + # (Africa/Khartoum) + # (Africa/Mogadishu) + # (Africa/Nairobi) + # (Indian/Antananarivo) + # (Indian/Comoro) + # (Indian/Mayotte) +IOT Indian/Chagos # British Indian Ocean Territory (Chagos) (obsolete) +MUT 14400 # Mauritius Island Time (obsolete) +MUST 18000 D # Mauritius Island Summer Time (obsolete) +MVT 18000 # Maldives Island Time (obsolete) +RET 14400 # Reunion Time (obsolete) +SCT 14400 # Seychelles Time (obsolete) +TFT 18000 # Kerguelen Time (obsolete) diff --git a/src/timezone/tznames/Makefile b/src/timezone/tznames/Makefile new file mode 100644 index 0000000..e80bf53 --- /dev/null +++ b/src/timezone/tznames/Makefile @@ -0,0 +1,30 @@ +#------------------------------------------------------------------------- +# +# Makefile +# Makefile for the timezone names + +# IDENTIFICATION +# src/timezone/tznames/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/timezone/tznames +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +TZNAMES_TEMPLATES = Africa.txt America.txt Antarctica.txt Asia.txt \ + Atlantic.txt Australia.txt Etc.txt Europe.txt Indian.txt Pacific.txt +TZNAMES_TEMPLATES_FILES = $(TZNAMES_TEMPLATES:%=$(srcdir)/%) + +TZNAMES_SETS = Default Australia India +TZNAMES_SETS_FILES = $(TZNAMES_SETS:%=$(srcdir)/%) + +install: installdirs + $(INSTALL_DATA) $(TZNAMES_TEMPLATES_FILES) '$(DESTDIR)$(datadir)/timezonesets' + $(INSTALL_DATA) $(TZNAMES_SETS_FILES) '$(DESTDIR)$(datadir)/timezonesets' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(datadir)/timezonesets' + +uninstall: + rm -rf '$(DESTDIR)$(datadir)/timezonesets' diff --git a/src/timezone/tznames/Pacific.txt b/src/timezone/tznames/Pacific.txt new file mode 100644 index 0000000..c30008c --- /dev/null +++ b/src/timezone/tznames/Pacific.txt @@ -0,0 +1,84 @@ +# +# NOTE: +# This file is NOT loaded by the PostgreSQL database. It just serves as +# a template for timezones you could need. See the `Date/Time Support' +# appendix in the PostgreSQL documentation for more information. +# +# src/timezone/tznames/Pacific.txt +# + +# CONFLICT! BST is not unique +# Other timezones: +# - BST: British Summer Time +BST 39600 # Bougainville Standard Time (Papua New Guinea) (obsolete) +CHADT 49500 D # Chatham Daylight Time (New Zealand) (obsolete) +CHAST 45900 # Chatham Standard Time (New Zealand) (obsolete) +ChST 36000 # Chamorro Standard Time (lower case "h" is as in IANA database) + # (Pacific/Guam) + # (Pacific/Saipan) +CHUT 36000 # Chuuk Time (obsolete) +CKT Pacific/Rarotonga # Cook Islands Time (obsolete) +EASST Pacific/Easter # Easter Island Summer Time (obsolete) +# CONFLICT! EAST is not unique +# Other timezones: +# - EAST: East Australian Standard Time (Australia) +EAST Pacific/Easter # Easter Island Time (Chile) (obsolete) +FJST 46800 D # Fiji Summer Time (caution: this used to mean -46800) (obsolete) +FJT 43200 # Fiji Time (caution: this used to mean -43200) (obsolete) +GALT -21600 # Galapagos Time (obsolete) +GAMT -32400 # Gambier Time (obsolete) +GILT 43200 # Gilbert Islands Time (obsolete) +HST -36000 # Hawaiian Standard Time + # (Pacific/Honolulu) + # (Pacific/Johnston) +KOST Pacific/Kosrae # Kosrae Time (obsolete) +LINT Pacific/Kiritimati # Line Islands Time (Kiribati) (obsolete) +MART -34200 # Marquesas Time (obsolete) +MHT 43200 # Kwajalein Time (obsolete) +MPT 36000 # North Mariana Islands Time (not in IANA database) +NCT 39600 # New Caledonia Time (obsolete) +# CONFLICT! NFT is not unique +# Other timezones: +# - NFT: Newfoundland Time (America) +NFT Pacific/Norfolk # Norfolk Time (obsolete) +NRT Pacific/Nauru # Nauru Time (obsolete) +NUT Pacific/Niue # Niue Time (obsolete) +NZDT 46800 D # New Zealand Daylight Time + # (Antarctica/McMurdo) + # (Pacific/Auckland) +NZST 43200 # New Zealand Standard Time + # (Antarctica/McMurdo) + # (Pacific/Auckland) +PGT 36000 # Papua New Guinea Time (obsolete) +PHOT Pacific/Enderbury # Phoenix Islands Time (Kiribati) (obsolete) +PONT 39600 # Ponape Time (Micronesia) (obsolete) +# CONFLICT! PST is not unique +# Other timezones: +# - PST: Philippine Standard Time +PST -28800 # Pacific Standard Time + # (America/Dawson) + # (America/Los_Angeles) + # (America/Tijuana) + # (America/Vancouver) + # (America/Whitehorse) + # (Pacific/Pitcairn) +PWT 32400 # Palau Time (obsolete) +SBT 39600 # Solomon Islands Time (obsolete) +SST -39600 # South Sumatran Time + # (Pacific/Midway) + # (Pacific/Pago_Pago) +TAHT -36000 # Tahiti Time (obsolete) +TKT Pacific/Fakaofo # Tokelau Time (obsolete) +TOT 46800 # Tonga Time (obsolete) +TRUT 36000 # Truk Time (obsolete) +TVT 43200 # Tuvalu Time (obsolete) +VUT 39600 # Vanuata Time (obsolete) +WAKT 43200 # Wake Time (obsolete) +WFT 43200 # Wallis and Futuna Time (obsolete) +WSDT 50400 D # West Samoa Daylight Time (obsolete) +WSST 46800 # West Samoa Standard Time (obsolete) +# CONFLICT! WST is not unique +# Other timezones: +# - WST: Western Standard Time (Australia) +WST 46800 # West Samoa Time (caution: this used to mean -39600) (not in IANA database) +YAPT 36000 # Yap Time (Micronesia) (not in IANA database) diff --git a/src/timezone/tznames/README b/src/timezone/tznames/README new file mode 100644 index 0000000..6d355e4 --- /dev/null +++ b/src/timezone/tznames/README @@ -0,0 +1,40 @@ +src/timezone/tznames/README + +tznames +======= + +This directory contains files with timezone sets for PostgreSQL. The problem +is that time zone abbreviations are not unique throughout the world and you +might find out that a time zone abbreviation in the `Default' set collides +with the one you wanted to use. This can be fixed by selecting a timezone +set that defines the abbreviation the way you want it. There might already +be a file here that serves your needs. If not, you can create your own. + +In order to use one of these files, you need to set + + timezone_abbreviations = 'xyz' + +in any of the usual ways for setting a parameter, where xyz is the filename +that contains the desired time zone abbreviations. + +If you do not find an appropriate set of abbreviations for your geographic +location supplied here, please report this to <pgsql-hackers@lists.postgresql.org>. +Your set of time zone abbreviations can then be included in future releases. +For the time being you can always add your own set. + +Typically a custom abbreviation set is made by including the `Default' set +and then adding or overriding abbreviations as necessary. For examples, +see the `Australia' and `India' files. + +The files named Africa.txt, etc, are not intended to be used directly as +time zone abbreviation files. They contain reference definitions of time zone +abbreviations that can be copied into a custom abbreviation file as needed. +These files contain most of the time zone abbreviations that were shown +in the IANA timezone database circa 2010. + +However, it turns out that many of these abbreviations had simply been +invented by the IANA timezone group, and do not have currency in real-world +use. The IANA group have changed their policy about that, and now prefer to +use numeric UTC offsets whenever there's not an abbreviation with known +real-world popularity. A lot of these abbreviations therefore no longer +appear in the IANA data, and so are marked "obsolete" in these data files. diff --git a/src/timezone/zic.c b/src/timezone/zic.c new file mode 100644 index 0000000..0ea6ead --- /dev/null +++ b/src/timezone/zic.c @@ -0,0 +1,4013 @@ +/* Compile .zi time zone data into TZif binary files. */ + +/* + * This file is in the public domain, so clarified as of + * 2006-07-17 by Arthur David Olson. + * + * IDENTIFICATION + * src/timezone/zic.c + */ + +#include "postgres_fe.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <time.h> + +#include "pg_getopt.h" + +#include "private.h" +#include "tzfile.h" + +#define ZIC_VERSION_PRE_2013 '2' +#define ZIC_VERSION '3' + +typedef int64 zic_t; +#define ZIC_MIN PG_INT64_MIN +#define ZIC_MAX PG_INT64_MAX + +#ifndef ZIC_MAX_ABBR_LEN_WO_WARN +#define ZIC_MAX_ABBR_LEN_WO_WARN 6 +#endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */ + +#ifndef WIN32 +#ifdef S_IRUSR +#define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) +#else +#define MKDIR_UMASK 0755 +#endif +#endif +/* Port to native MS-Windows and to ancient UNIX. */ +#if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +/* The maximum ptrdiff_t value, for pre-C99 platforms. */ +#ifndef PTRDIFF_MAX +static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)); +#endif + +/* + * The type for line numbers. In Postgres, use %d to format them; upstream + * uses PRIdMAX but we prefer not to rely on that, not least because it + * results in platform-dependent strings to be translated. + */ +typedef int lineno_t; + +struct rule +{ + const char *r_filename; + lineno_t r_linenum; + const char *r_name; + + zic_t r_loyear; /* for example, 1986 */ + zic_t r_hiyear; /* for example, 1986 */ + bool r_lowasnum; + bool r_hiwasnum; + + int r_month; /* 0..11 */ + + int r_dycode; /* see below */ + int r_dayofmonth; + int r_wday; + + zic_t r_tod; /* time from midnight */ + bool r_todisstd; /* is r_tod standard time? */ + bool r_todisut; /* is r_tod UT? */ + bool r_isdst; /* is this daylight saving time? */ + zic_t r_save; /* offset from standard time */ + const char *r_abbrvar; /* variable part of abbreviation */ + + bool r_todo; /* a rule to do (used in outzone) */ + zic_t r_temp; /* used in outzone */ +}; + +/* + * r_dycode r_dayofmonth r_wday + */ + +#define DC_DOM 0 /* 1..31 */ /* unused */ +#define DC_DOWGEQ 1 /* 1..31 */ /* 0..6 (Sun..Sat) */ +#define DC_DOWLEQ 2 /* 1..31 */ /* 0..6 (Sun..Sat) */ + +struct zone +{ + const char *z_filename; + lineno_t z_linenum; + + const char *z_name; + zic_t z_stdoff; + char *z_rule; + const char *z_format; + char z_format_specifier; + + bool z_isdst; + zic_t z_save; + + struct rule *z_rules; + ptrdiff_t z_nrules; + + struct rule z_untilrule; + zic_t z_untiltime; +}; + +extern int link(const char *target, const char *linkname); +#ifndef AT_SYMLINK_FOLLOW +#define linkat(targetdir, target, linknamedir, linkname, flag) \ + (itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname)) +#endif + +static void memory_exhausted(const char *msg) pg_attribute_noreturn(); +static void verror(const char *string, va_list args) pg_attribute_printf(1, 0); +static void error(const char *string,...) pg_attribute_printf(1, 2); +static void warning(const char *string,...) pg_attribute_printf(1, 2); +static void usage(FILE *stream, int status) pg_attribute_noreturn(); +static void addtt(zic_t starttime, int type); +static int addtype(zic_t, char const *, bool, bool, bool); +static void leapadd(zic_t, int, int); +static void adjleap(void); +static void associate(void); +static void dolink(const char *, const char *, bool); +static char **getfields(char *buf); +static zic_t gethms(const char *string, const char *errstring); +static zic_t getsave(char *, bool *); +static void inexpires(char **, int); +static void infile(const char *filename); +static void inleap(char **fields, int nfields); +static void inlink(char **fields, int nfields); +static void inrule(char **fields, int nfields); +static bool inzcont(char **fields, int nfields); +static bool inzone(char **fields, int nfields); +static bool inzsub(char **, int, bool); +static bool itsdir(char const *); +static bool itssymlink(char const *); +static bool is_alpha(char a); +static char lowerit(char); +static void mkdirs(char const *, bool); +static void newabbr(const char *abbr); +static zic_t oadd(zic_t t1, zic_t t2); +static void outzone(const struct zone *zp, ptrdiff_t ntzones); +static zic_t rpytime(const struct rule *rp, zic_t wantedy); +static void rulesub(struct rule *rp, + const char *loyearp, const char *hiyearp, + const char *typep, const char *monthp, + const char *dayp, const char *timep); +static zic_t tadd(zic_t t1, zic_t t2); + +/* Bound on length of what %z can expand to. */ +enum +{ +PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1}; + +/* If true, work around a bug in Qt 5.6.1 and earlier, which mishandles + TZif files whose POSIX-TZ-style strings contain '<'; see + QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>. This + workaround will no longer be needed when Qt 5.6.1 and earlier are + obsolete, say in the year 2021. */ +#ifndef WORK_AROUND_QTBUG_53071 +enum +{ +WORK_AROUND_QTBUG_53071 = true}; +#endif + +static int charcnt; +static bool errors; +static bool warnings; +static const char *filename; +static int leapcnt; +static bool leapseen; +static zic_t leapminyear; +static zic_t leapmaxyear; +static lineno_t linenum; +static int max_abbrvar_len = PERCENT_Z_LEN_BOUND; +static int max_format_len; +static zic_t max_year; +static zic_t min_year; +static bool noise; +static bool print_abbrevs; +static zic_t print_cutoff; +static const char *rfilename; +static lineno_t rlinenum; +static const char *progname; +static ptrdiff_t timecnt; +static ptrdiff_t timecnt_alloc; +static int typecnt; + +/* + * Line codes. + */ + +#define LC_RULE 0 +#define LC_ZONE 1 +#define LC_LINK 2 +#define LC_LEAP 3 +#define LC_EXPIRES 4 + +/* + * Which fields are which on a Zone line. + */ + +#define ZF_NAME 1 +#define ZF_STDOFF 2 +#define ZF_RULE 3 +#define ZF_FORMAT 4 +#define ZF_TILYEAR 5 +#define ZF_TILMONTH 6 +#define ZF_TILDAY 7 +#define ZF_TILTIME 8 +#define ZONE_MINFIELDS 5 +#define ZONE_MAXFIELDS 9 + +/* + * Which fields are which on a Zone continuation line. + */ + +#define ZFC_STDOFF 0 +#define ZFC_RULE 1 +#define ZFC_FORMAT 2 +#define ZFC_TILYEAR 3 +#define ZFC_TILMONTH 4 +#define ZFC_TILDAY 5 +#define ZFC_TILTIME 6 +#define ZONEC_MINFIELDS 3 +#define ZONEC_MAXFIELDS 7 + +/* + * Which files are which on a Rule line. + */ + +#define RF_NAME 1 +#define RF_LOYEAR 2 +#define RF_HIYEAR 3 +#define RF_COMMAND 4 +#define RF_MONTH 5 +#define RF_DAY 6 +#define RF_TOD 7 +#define RF_SAVE 8 +#define RF_ABBRVAR 9 +#define RULE_FIELDS 10 + +/* + * Which fields are which on a Link line. + */ + +#define LF_TARGET 1 +#define LF_LINKNAME 2 +#define LINK_FIELDS 3 + +/* + * Which fields are which on a Leap line. + */ + +#define LP_YEAR 1 +#define LP_MONTH 2 +#define LP_DAY 3 +#define LP_TIME 4 +#define LP_CORR 5 +#define LP_ROLL 6 +#define LEAP_FIELDS 7 + +/* Expires lines are like Leap lines, except without CORR and ROLL fields. */ +#define EXPIRES_FIELDS 5 + +/* + * Year synonyms. + */ + +#define YR_MINIMUM 0 +#define YR_MAXIMUM 1 +#define YR_ONLY 2 + +static struct rule *rules; +static ptrdiff_t nrules; /* number of rules */ +static ptrdiff_t nrules_alloc; + +static struct zone *zones; +static ptrdiff_t nzones; /* number of zones */ +static ptrdiff_t nzones_alloc; + +struct link +{ + const char *l_filename; + lineno_t l_linenum; + const char *l_target; + const char *l_linkname; +}; + +static struct link *links; +static ptrdiff_t nlinks; +static ptrdiff_t nlinks_alloc; + +struct lookup +{ + const char *l_word; + const int l_value; +}; + +static struct lookup const *byword(const char *string, + const struct lookup *lp); + +static struct lookup const zi_line_codes[] = { + {"Rule", LC_RULE}, + {"Zone", LC_ZONE}, + {"Link", LC_LINK}, + {NULL, 0} +}; +static struct lookup const leap_line_codes[] = { + {"Leap", LC_LEAP}, + {"Expires", LC_EXPIRES}, + {NULL, 0} +}; + +static struct lookup const mon_names[] = { + {"January", TM_JANUARY}, + {"February", TM_FEBRUARY}, + {"March", TM_MARCH}, + {"April", TM_APRIL}, + {"May", TM_MAY}, + {"June", TM_JUNE}, + {"July", TM_JULY}, + {"August", TM_AUGUST}, + {"September", TM_SEPTEMBER}, + {"October", TM_OCTOBER}, + {"November", TM_NOVEMBER}, + {"December", TM_DECEMBER}, + {NULL, 0} +}; + +static struct lookup const wday_names[] = { + {"Sunday", TM_SUNDAY}, + {"Monday", TM_MONDAY}, + {"Tuesday", TM_TUESDAY}, + {"Wednesday", TM_WEDNESDAY}, + {"Thursday", TM_THURSDAY}, + {"Friday", TM_FRIDAY}, + {"Saturday", TM_SATURDAY}, + {NULL, 0} +}; + +static struct lookup const lasts[] = { + {"last-Sunday", TM_SUNDAY}, + {"last-Monday", TM_MONDAY}, + {"last-Tuesday", TM_TUESDAY}, + {"last-Wednesday", TM_WEDNESDAY}, + {"last-Thursday", TM_THURSDAY}, + {"last-Friday", TM_FRIDAY}, + {"last-Saturday", TM_SATURDAY}, + {NULL, 0} +}; + +static struct lookup const begin_years[] = { + {"minimum", YR_MINIMUM}, + {"maximum", YR_MAXIMUM}, + {NULL, 0} +}; + +static struct lookup const end_years[] = { + {"minimum", YR_MINIMUM}, + {"maximum", YR_MAXIMUM}, + {"only", YR_ONLY}, + {NULL, 0} +}; + +static struct lookup const leap_types[] = { + {"Rolling", true}, + {"Stationary", false}, + {NULL, 0} +}; + +static const int len_months[2][MONSPERYEAR] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +static const int len_years[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +static struct attype +{ + zic_t at; + bool dontmerge; + unsigned char type; +} *attypes; +static zic_t utoffs[TZ_MAX_TYPES]; +static char isdsts[TZ_MAX_TYPES]; +static unsigned char desigidx[TZ_MAX_TYPES]; +static bool ttisstds[TZ_MAX_TYPES]; +static bool ttisuts[TZ_MAX_TYPES]; +static char chars[TZ_MAX_CHARS]; +static zic_t trans[TZ_MAX_LEAPS]; +static zic_t corr[TZ_MAX_LEAPS]; +static char roll[TZ_MAX_LEAPS]; + +/* + * Memory allocation. + */ + +static void +memory_exhausted(const char *msg) +{ + fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg); + exit(EXIT_FAILURE); +} + +static size_t +size_product(size_t nitems, size_t itemsize) +{ + if (SIZE_MAX / itemsize < nitems) + memory_exhausted(_("size overflow")); + return nitems * itemsize; +} + +static void * +memcheck(void *ptr) +{ + if (ptr == NULL) + memory_exhausted(strerror(errno)); + return ptr; +} + +static void * +emalloc(size_t size) +{ + return memcheck(malloc(size)); +} + +static void * +erealloc(void *ptr, size_t size) +{ + return memcheck(realloc(ptr, size)); +} + +static char * +ecpyalloc(char const *str) +{ + return memcheck(strdup(str)); +} + +static void * +growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc) +{ + if (nitems < *nitems_alloc) + return ptr; + else + { + ptrdiff_t nitems_max = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071; + ptrdiff_t amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX; + + if ((amax - 1) / 3 * 2 < *nitems_alloc) + memory_exhausted(_("integer overflow")); + *nitems_alloc += (*nitems_alloc >> 1) + 1; + return erealloc(ptr, size_product(*nitems_alloc, itemsize)); + } +} + +/* + * Error handling. + */ + +static void +eats(char const *name, lineno_t num, char const *rname, lineno_t rnum) +{ + filename = name; + linenum = num; + rfilename = rname; + rlinenum = rnum; +} + +static void +eat(char const *name, lineno_t num) +{ + eats(name, num, NULL, -1); +} + +static void +verror(const char *string, va_list args) +{ + /* + * Match the format of "cc" to allow sh users to zic ... 2>&1 | error -t + * "*" -v on BSD systems. + */ + if (filename) + fprintf(stderr, _("\"%s\", line %d: "), filename, linenum); + vfprintf(stderr, string, args); + if (rfilename != NULL) + fprintf(stderr, _(" (rule from \"%s\", line %d)"), + rfilename, rlinenum); + fprintf(stderr, "\n"); +} + +static void +error(const char *string,...) +{ + va_list args; + + va_start(args, string); + verror(string, args); + va_end(args); + errors = true; +} + +static void +warning(const char *string,...) +{ + va_list args; + + fprintf(stderr, _("warning: ")); + va_start(args, string); + verror(string, args); + va_end(args); + warnings = true; +} + +static void +close_file(FILE *stream, char const *dir, char const *name) +{ + char const *e = (ferror(stream) ? _("I/O error") + : fclose(stream) != 0 ? strerror(errno) : NULL); + + if (e) + { + fprintf(stderr, "%s: %s%s%s%s%s\n", progname, + dir ? dir : "", dir ? "/" : "", + name ? name : "", name ? ": " : "", + e); + exit(EXIT_FAILURE); + } +} + +static void +usage(FILE *stream, int status) +{ + fprintf(stream, + _("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n" + "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]" + " [ -L leapseconds ] \\\n" + "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -t localtime-link ] \\\n" + "\t[ filename ... ]\n\n" + "Report bugs to %s.\n"), + progname, progname, PACKAGE_BUGREPORT); + if (status == EXIT_SUCCESS) + close_file(stream, NULL, NULL); + exit(status); +} + +/* Change the working directory to DIR, possibly creating DIR and its + ancestors. After this is done, all files are accessed with names + relative to DIR. */ +static void +change_directory(char const *dir) +{ + if (chdir(dir) != 0) + { + int chdir_errno = errno; + + if (chdir_errno == ENOENT) + { + mkdirs(dir, false); + chdir_errno = chdir(dir) == 0 ? 0 : errno; + } + if (chdir_errno != 0) + { + fprintf(stderr, _("%s: Can't chdir to %s: %s\n"), + progname, dir, strerror(chdir_errno)); + exit(EXIT_FAILURE); + } + } +} + +#define TIME_T_BITS_IN_FILE 64 + +/* The minimum and maximum values representable in a TZif file. */ +static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); +static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); + +/* The minimum, and one less than the maximum, values specified by + the -r option. These default to MIN_TIME and MAX_TIME. */ +static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); +static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); + +/* The time specified by an Expires line, or negative if no such line. */ +static zic_t leapexpires = -1; + +/* The time specified by an #expires comment, or negative if no such line. */ +static zic_t comment_leapexpires = -1; + +/* Set the time range of the output to TIMERANGE. + Return true if successful. */ +static bool +timerange_option(char *timerange) +{ + int64 lo = min_time, + hi = max_time; + char *lo_end = timerange, + *hi_end; + + if (*timerange == '@') + { + errno = 0; + lo = strtoimax(timerange + 1, &lo_end, 10); + if (lo_end == timerange + 1 || (lo == PG_INT64_MAX && errno == ERANGE)) + return false; + } + hi_end = lo_end; + if (lo_end[0] == '/' && lo_end[1] == '@') + { + errno = 0; + hi = strtoimax(lo_end + 2, &hi_end, 10); + if (hi_end == lo_end + 2 || hi == PG_INT64_MIN) + return false; + hi -= !(hi == PG_INT64_MAX && errno == ERANGE); + } + if (*hi_end || hi < lo || max_time < lo || hi < min_time) + return false; + lo_time = lo < min_time ? min_time : lo; + hi_time = max_time < hi ? max_time : hi; + return true; +} + +static const char *psxrules; +static const char *lcltime; +static const char *directory; +static const char *leapsec; +static const char *tzdefault; + +/* -1 if the TZif output file should be slim, 0 if default, 1 if the + output should be fat for backward compatibility. ZIC_BLOAT_DEFAULT + determines the default. */ +static int bloat; + +static bool +want_bloat(void) +{ + return 0 <= bloat; +} + +#ifndef ZIC_BLOAT_DEFAULT +#define ZIC_BLOAT_DEFAULT "slim" +#endif + +int +main(int argc, char **argv) +{ + int c, + k; + ptrdiff_t i, + j; + bool timerange_given = false; + +#ifndef WIN32 + umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); +#endif + progname = argv[0]; + if (TYPE_BIT(zic_t) < 64) + { + fprintf(stderr, "%s: %s\n", progname, + _("wild compilation-time specification of zic_t")); + return EXIT_FAILURE; + } + for (k = 1; k < argc; k++) + if (strcmp(argv[k], "--version") == 0) + { + printf("zic %s\n", PG_VERSION); + close_file(stdout, NULL, NULL); + return EXIT_SUCCESS; + } + else if (strcmp(argv[k], "--help") == 0) + { + usage(stdout, EXIT_SUCCESS); + } + while ((c = getopt(argc, argv, "b:d:l:L:p:Pr:st:vy:")) != EOF && c != -1) + switch (c) + { + default: + usage(stderr, EXIT_FAILURE); + case 'b': + if (strcmp(optarg, "slim") == 0) + { + if (0 < bloat) + error(_("incompatible -b options")); + bloat = -1; + } + else if (strcmp(optarg, "fat") == 0) + { + if (bloat < 0) + error(_("incompatible -b options")); + bloat = 1; + } + else + error(_("invalid option: -b '%s'"), optarg); + break; + case 'd': + if (directory == NULL) + directory = strdup(optarg); + else + { + fprintf(stderr, + _("%s: More than one -d option specified\n"), + progname); + return EXIT_FAILURE; + } + break; + case 'l': + if (lcltime == NULL) + lcltime = strdup(optarg); + else + { + fprintf(stderr, + _("%s: More than one -l option specified\n"), + progname); + return EXIT_FAILURE; + } + break; + case 'p': + if (psxrules == NULL) + psxrules = strdup(optarg); + else + { + fprintf(stderr, + _("%s: More than one -p option specified\n"), + progname); + return EXIT_FAILURE; + } + break; + case 't': + if (tzdefault != NULL) + { + fprintf(stderr, + _("%s: More than one -t option" + " specified\n"), + progname); + return EXIT_FAILURE; + } + tzdefault = optarg; + break; + case 'y': + warning(_("-y ignored")); + break; + case 'L': + if (leapsec == NULL) + leapsec = strdup(optarg); + else + { + fprintf(stderr, + _("%s: More than one -L option specified\n"), + progname); + return EXIT_FAILURE; + } + break; + case 'v': + noise = true; + break; + case 'P': + print_abbrevs = true; + print_cutoff = time(NULL); + break; + case 'r': + if (timerange_given) + { + fprintf(stderr, + _("%s: More than one -r option specified\n"), + progname); + return EXIT_FAILURE; + } + if (!timerange_option(optarg)) + { + fprintf(stderr, + _("%s: invalid time range: %s\n"), + progname, optarg); + return EXIT_FAILURE; + } + timerange_given = true; + break; + case 's': + warning(_("-s ignored")); + break; + } + if (optind == argc - 1 && strcmp(argv[optind], "=") == 0) + usage(stderr, EXIT_FAILURE); /* usage message by request */ + if (bloat == 0) + { + static char const bloat_default[] = ZIC_BLOAT_DEFAULT; + + if (strcmp(bloat_default, "slim") == 0) + bloat = -1; + else if (strcmp(bloat_default, "fat") == 0) + bloat = 1; + else + abort(); /* Configuration error. */ + } + if (directory == NULL) + directory = "data"; + if (tzdefault == NULL) + tzdefault = TZDEFAULT; + + if (optind < argc && leapsec != NULL) + { + infile(leapsec); + adjleap(); + } + + for (k = optind; k < argc; k++) + infile(argv[k]); + if (errors) + return EXIT_FAILURE; + associate(); + change_directory(directory); + for (i = 0; i < nzones; i = j) + { + /* + * Find the next non-continuation zone entry. + */ + for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j) + continue; + outzone(&zones[i], j - i); + } + + /* + * Make links. + */ + for (i = 0; i < nlinks; ++i) + { + eat(links[i].l_filename, links[i].l_linenum); + dolink(links[i].l_target, links[i].l_linkname, false); + if (noise) + for (j = 0; j < nlinks; ++j) + if (strcmp(links[i].l_linkname, + links[j].l_target) == 0) + warning(_("link to link")); + } + if (lcltime != NULL) + { + eat(_("command line"), 1); + dolink(lcltime, tzdefault, true); + } + if (psxrules != NULL) + { + eat(_("command line"), 1); + dolink(psxrules, TZDEFRULES, true); + } + if (warnings && (ferror(stderr) || fclose(stderr) != 0)) + return EXIT_FAILURE; + return errors ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static bool +componentcheck(char const *name, char const *component, + char const *component_end) +{ + enum + { + component_len_max = 14}; + ptrdiff_t component_len = component_end - component; + + if (component_len == 0) + { + if (!*name) + error(_("empty file name")); + else + error(_(component == name + ? "file name '%s' begins with '/'" + : *component_end + ? "file name '%s' contains '//'" + : "file name '%s' ends with '/'"), + name); + return false; + } + if (0 < component_len && component_len <= 2 + && component[0] == '.' && component_end[-1] == '.') + { + int len = component_len; + + error(_("file name '%s' contains '%.*s' component"), + name, len, component); + return false; + } + if (noise) + { + if (0 < component_len && component[0] == '-') + warning(_("file name '%s' component contains leading '-'"), + name); + if (component_len_max < component_len) + warning(_("file name '%s' contains overlength component" + " '%.*s...'"), + name, component_len_max, component); + } + return true; +} + +static bool +namecheck(const char *name) +{ + char const *cp; + + /* Benign characters in a portable file name. */ + static char const benign[] = + "-/_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /* + * Non-control chars in the POSIX portable character set, excluding the + * benign characters. + */ + static char const printable_and_not_benign[] = + " !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~"; + + char const *component = name; + + for (cp = name; *cp; cp++) + { + unsigned char c = *cp; + + if (noise && !strchr(benign, c)) + { + warning((strchr(printable_and_not_benign, c) + ? _("file name '%s' contains byte '%c'") + : _("file name '%s' contains byte '\\%o'")), + name, c); + } + if (c == '/') + { + if (!componentcheck(name, component, cp)) + return false; + component = cp + 1; + } + } + return componentcheck(name, component, cp); +} + +/* + * Create symlink contents suitable for symlinking FROM to TO, as a + * freshly allocated string. FROM should be a relative file name, and + * is relative to the global variable DIRECTORY. TO can be either + * relative or absolute. + */ +#ifdef HAVE_SYMLINK +static char * +relname(char const *target, char const *linkname) +{ + size_t i, + taillen, + dotdotetcsize; + size_t dir_len = 0, + dotdots = 0, + linksize = SIZE_MAX; + char const *f = target; + char *result = NULL; + + if (*linkname == '/') + { + /* Make F absolute too. */ + size_t len = strlen(directory); + bool needslash = len && directory[len - 1] != '/'; + + linksize = len + needslash + strlen(target) + 1; + f = result = emalloc(linksize); + strcpy(result, directory); + result[len] = '/'; + strcpy(result + len + needslash, target); + } + for (i = 0; f[i] && f[i] == linkname[i]; i++) + if (f[i] == '/') + dir_len = i + 1; + for (; linkname[i]; i++) + dotdots += linkname[i] == '/' && linkname[i - 1] != '/'; + taillen = strlen(f + dir_len); + dotdotetcsize = 3 * dotdots + taillen + 1; + if (dotdotetcsize <= linksize) + { + if (!result) + result = emalloc(dotdotetcsize); + for (i = 0; i < dotdots; i++) + memcpy(result + 3 * i, "../", 3); + memmove(result + 3 * dotdots, f + dir_len, taillen + 1); + } + return result; +} +#endif /* HAVE_SYMLINK */ + +/* Hard link FROM to TO, following any symbolic links. + Return 0 if successful, an error number otherwise. */ +static int +hardlinkerr(char const *target, char const *linkname) +{ + int r = linkat(AT_FDCWD, target, AT_FDCWD, linkname, AT_SYMLINK_FOLLOW); + + return r == 0 ? 0 : errno; +} + +static void +dolink(char const *target, char const *linkname, bool staysymlink) +{ + bool remove_only = strcmp(target, "-") == 0; + bool linkdirs_made = false; + int link_errno; + + /* + * We get to be careful here since there's a fair chance of root running + * us. + */ + if (!remove_only && itsdir(target)) + { + fprintf(stderr, _("%s: linking target %s/%s failed: %s\n"), + progname, directory, target, strerror(EPERM)); + exit(EXIT_FAILURE); + } + if (staysymlink) + staysymlink = itssymlink(linkname); + if (remove(linkname) == 0) + linkdirs_made = true; + else if (errno != ENOENT) + { + char const *e = strerror(errno); + + fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"), + progname, directory, linkname, e); + exit(EXIT_FAILURE); + } + if (remove_only) + return; + link_errno = staysymlink ? ENOTSUP : hardlinkerr(target, linkname); + if (link_errno == ENOENT && !linkdirs_made) + { + mkdirs(linkname, true); + linkdirs_made = true; + link_errno = hardlinkerr(target, linkname); + } + if (link_errno != 0) + { +#ifdef HAVE_SYMLINK + bool absolute = *target == '/'; + char *linkalloc = absolute ? NULL : relname(target, linkname); + char const *contents = absolute ? target : linkalloc; + int symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno; + + if (!linkdirs_made + && (symlink_errno == ENOENT || symlink_errno == ENOTSUP)) + { + mkdirs(linkname, true); + if (symlink_errno == ENOENT) + symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno; + } + free(linkalloc); + if (symlink_errno == 0) + { + if (link_errno != ENOTSUP) + warning(_("symbolic link used because hard link failed: %s"), + strerror(link_errno)); + } + else +#endif /* HAVE_SYMLINK */ + { + FILE *fp, + *tp; + int c; + + fp = fopen(target, "rb"); + if (!fp) + { + char const *e = strerror(errno); + + fprintf(stderr, _("%s: Can't read %s/%s: %s\n"), + progname, directory, target, e); + exit(EXIT_FAILURE); + } + tp = fopen(linkname, "wb"); + if (!tp) + { + char const *e = strerror(errno); + + fprintf(stderr, _("%s: Can't create %s/%s: %s\n"), + progname, directory, linkname, e); + exit(EXIT_FAILURE); + } + while ((c = getc(fp)) != EOF) + putc(c, tp); + close_file(fp, directory, target); + close_file(tp, directory, linkname); + if (link_errno != ENOTSUP) + warning(_("copy used because hard link failed: %s"), + strerror(link_errno)); +#ifdef HAVE_SYMLINK + else if (symlink_errno != ENOTSUP) + warning(_("copy used because symbolic link failed: %s"), + strerror(symlink_errno)); +#endif + } + } +} + +/* Return true if NAME is a directory. */ +static bool +itsdir(char const *name) +{ + struct stat st; + int res = stat(name, &st); +#ifdef S_ISDIR + if (res == 0) + return S_ISDIR(st.st_mode) != 0; +#endif + if (res == 0 || errno == EOVERFLOW) + { + size_t n = strlen(name); + char *nameslashdot = emalloc(n + 3); + bool dir; + + memcpy(nameslashdot, name, n); + strcpy(&nameslashdot[n], &"/."[!(n && name[n - 1] != '/')]); + dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW; + free(nameslashdot); + return dir; + } + return false; +} + +/* Return true if NAME is a symbolic link. */ +static bool +itssymlink(char const *name) +{ +#ifdef HAVE_SYMLINK + char c; + + return 0 <= readlink(name, &c, 1); +#else + return false; +#endif +} + +/* + * Associate sets of rules with zones. + */ + +/* + * Sort by rule name. + */ + +static int +rcomp(const void *cp1, const void *cp2) +{ + return strcmp(((const struct rule *) cp1)->r_name, + ((const struct rule *) cp2)->r_name); +} + +static void +associate(void) +{ + struct zone *zp; + struct rule *rp; + ptrdiff_t i, + j, + base, + out; + + if (nrules != 0) + { + qsort(rules, nrules, sizeof *rules, rcomp); + for (i = 0; i < nrules - 1; ++i) + { + if (strcmp(rules[i].r_name, + rules[i + 1].r_name) != 0) + continue; + if (strcmp(rules[i].r_filename, + rules[i + 1].r_filename) == 0) + continue; + eat(rules[i].r_filename, rules[i].r_linenum); + warning(_("same rule name in multiple files")); + eat(rules[i + 1].r_filename, rules[i + 1].r_linenum); + warning(_("same rule name in multiple files")); + for (j = i + 2; j < nrules; ++j) + { + if (strcmp(rules[i].r_name, + rules[j].r_name) != 0) + break; + if (strcmp(rules[i].r_filename, + rules[j].r_filename) == 0) + continue; + if (strcmp(rules[i + 1].r_filename, + rules[j].r_filename) == 0) + continue; + break; + } + i = j - 1; + } + } + for (i = 0; i < nzones; ++i) + { + zp = &zones[i]; + zp->z_rules = NULL; + zp->z_nrules = 0; + } + for (base = 0; base < nrules; base = out) + { + rp = &rules[base]; + for (out = base + 1; out < nrules; ++out) + if (strcmp(rp->r_name, rules[out].r_name) != 0) + break; + for (i = 0; i < nzones; ++i) + { + zp = &zones[i]; + if (strcmp(zp->z_rule, rp->r_name) != 0) + continue; + zp->z_rules = rp; + zp->z_nrules = out - base; + } + } + for (i = 0; i < nzones; ++i) + { + zp = &zones[i]; + if (zp->z_nrules == 0) + { + /* + * Maybe we have a local standard time offset. + */ + eat(zp->z_filename, zp->z_linenum); + zp->z_save = getsave(zp->z_rule, &zp->z_isdst); + + /* + * Note, though, that if there's no rule, a '%s' in the format is + * a bad thing. + */ + if (zp->z_format_specifier == 's') + error("%s", _("%s in ruleless zone")); + } + } + if (errors) + exit(EXIT_FAILURE); +} + +static void +infile(const char *name) +{ + FILE *fp; + char **fields; + char *cp; + const struct lookup *lp; + int nfields; + bool wantcont; + lineno_t num; + char buf[BUFSIZ]; + + if (strcmp(name, "-") == 0) + { + name = _("standard input"); + fp = stdin; + } + else if ((fp = fopen(name, "r")) == NULL) + { + const char *e = strerror(errno); + + fprintf(stderr, _("%s: Cannot open %s: %s\n"), + progname, name, e); + exit(EXIT_FAILURE); + } + wantcont = false; + for (num = 1;; ++num) + { + eat(name, num); + if (fgets(buf, sizeof buf, fp) != buf) + break; + cp = strchr(buf, '\n'); + if (cp == NULL) + { + error(_("line too long")); + exit(EXIT_FAILURE); + } + *cp = '\0'; + fields = getfields(buf); + nfields = 0; + while (fields[nfields] != NULL) + { + static char nada; + + if (strcmp(fields[nfields], "-") == 0) + fields[nfields] = &nada; + ++nfields; + } + if (nfields == 0) + { + if (name == leapsec && *buf == '#') + { + /* + * PG: INT64_FORMAT isn't portable for sscanf, so be content + * with scanning a "long". Once we are requiring C99 in all + * live branches, it'd be sensible to adopt upstream's + * practice of using the <inttypes.h> macros. But for now, we + * don't actually use this code, and it won't overflow before + * 2038 anyway. + */ + long cl_tmp; + + sscanf(buf, "#expires %ld", &cl_tmp); + comment_leapexpires = cl_tmp; + } + } + else if (wantcont) + { + wantcont = inzcont(fields, nfields); + } + else + { + struct lookup const *line_codes + = name == leapsec ? leap_line_codes : zi_line_codes; + + lp = byword(fields[0], line_codes); + if (lp == NULL) + error(_("input line of unknown type")); + else + switch (lp->l_value) + { + case LC_RULE: + inrule(fields, nfields); + wantcont = false; + break; + case LC_ZONE: + wantcont = inzone(fields, nfields); + break; + case LC_LINK: + inlink(fields, nfields); + wantcont = false; + break; + case LC_LEAP: + inleap(fields, nfields); + wantcont = false; + break; + case LC_EXPIRES: + inexpires(fields, nfields); + wantcont = false; + break; + default: /* "cannot happen" */ + fprintf(stderr, + _("%s: panic: Invalid l_value %d\n"), + progname, lp->l_value); + exit(EXIT_FAILURE); + } + } + free(fields); + } + close_file(fp, NULL, filename); + if (wantcont) + error(_("expected continuation line not found")); +} + +/* + * Convert a string of one of the forms + * h -h hh:mm -hh:mm hh:mm:ss -hh:mm:ss + * into a number of seconds. + * A null string maps to zero. + * Call error with errstring and return zero on errors. + */ + +static zic_t +gethms(char const *string, char const *errstring) +{ + /* PG: make hh be int not zic_t to avoid sscanf portability issues */ + int hh; + int sign, + mm = 0, + ss = 0; + char hhx, + mmx, + ssx, + xr = '0', + xs; + int tenths = 0; + bool ok = true; + + if (string == NULL || *string == '\0') + return 0; + if (*string == '-') + { + sign = -1; + ++string; + } + else + sign = 1; + switch (sscanf(string, + "%d%c%d%c%d%c%1d%*[0]%c%*[0123456789]%c", + &hh, &hhx, &mm, &mmx, &ss, &ssx, &tenths, &xr, &xs)) + { + default: + ok = false; + break; + case 8: + ok = '0' <= xr && xr <= '9'; + /* fallthrough */ + case 7: + ok &= ssx == '.'; + if (ok && noise) + warning(_("fractional seconds rejected by" + " pre-2018 versions of zic")); + /* fallthrough */ + case 5: + ok &= mmx == ':'; + /* fallthrough */ + case 3: + ok &= hhx == ':'; + /* fallthrough */ + case 1: + break; + } + if (!ok) + { + error("%s", errstring); + return 0; + } + if (hh < 0 || + mm < 0 || mm >= MINSPERHOUR || + ss < 0 || ss > SECSPERMIN) + { + error("%s", errstring); + return 0; + } + /* Some compilers warn that this test is unsatisfiable for 32-bit ints */ +#if INT_MAX > PG_INT32_MAX + if (ZIC_MAX / SECSPERHOUR < hh) + { + error(_("time overflow")); + return 0; + } +#endif + ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths; /* Round to even. */ + if (noise && (hh > HOURSPERDAY || + (hh == HOURSPERDAY && (mm != 0 || ss != 0)))) + warning(_("values over 24 hours not handled by pre-2007 versions of zic")); + return oadd(sign * (zic_t) hh * SECSPERHOUR, + sign * (mm * SECSPERMIN + ss)); +} + +static zic_t +getsave(char *field, bool *isdst) +{ + int dst = -1; + zic_t save; + size_t fieldlen = strlen(field); + + if (fieldlen != 0) + { + char *ep = field + fieldlen - 1; + + switch (*ep) + { + case 'd': + dst = 1; + *ep = '\0'; + break; + case 's': + dst = 0; + *ep = '\0'; + break; + } + } + save = gethms(field, _("invalid saved time")); + *isdst = dst < 0 ? save != 0 : dst; + return save; +} + +static void +inrule(char **fields, int nfields) +{ + static struct rule r; + + if (nfields != RULE_FIELDS) + { + error(_("wrong number of fields on Rule line")); + return; + } + switch (*fields[RF_NAME]) + { + case '\0': + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + case '+': + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + error(_("Invalid rule name \"%s\""), fields[RF_NAME]); + return; + } + r.r_filename = filename; + r.r_linenum = linenum; + r.r_save = getsave(fields[RF_SAVE], &r.r_isdst); + rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND], + fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]); + r.r_name = ecpyalloc(fields[RF_NAME]); + r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]); + if (max_abbrvar_len < strlen(r.r_abbrvar)) + max_abbrvar_len = strlen(r.r_abbrvar); + rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc); + rules[nrules++] = r; +} + +static bool +inzone(char **fields, int nfields) +{ + ptrdiff_t i; + + if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) + { + error(_("wrong number of fields on Zone line")); + return false; + } + if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0) + { + error( + _("\"Zone %s\" line and -l option are mutually exclusive"), + tzdefault); + return false; + } + if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) + { + error( + _("\"Zone %s\" line and -p option are mutually exclusive"), + TZDEFRULES); + return false; + } + for (i = 0; i < nzones; ++i) + if (zones[i].z_name != NULL && + strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) + { + error(_("duplicate zone name %s" + " (file \"%s\", line %d)"), + fields[ZF_NAME], + zones[i].z_filename, + zones[i].z_linenum); + return false; + } + return inzsub(fields, nfields, false); +} + +static bool +inzcont(char **fields, int nfields) +{ + if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS) + { + error(_("wrong number of fields on Zone continuation line")); + return false; + } + return inzsub(fields, nfields, true); +} + +static bool +inzsub(char **fields, int nfields, bool iscont) +{ + char *cp; + char *cp1; + static struct zone z; + int i_stdoff, + i_rule, + i_format; + int i_untilyear, + i_untilmonth; + int i_untilday, + i_untiltime; + bool hasuntil; + + if (iscont) + { + i_stdoff = ZFC_STDOFF; + i_rule = ZFC_RULE; + i_format = ZFC_FORMAT; + i_untilyear = ZFC_TILYEAR; + i_untilmonth = ZFC_TILMONTH; + i_untilday = ZFC_TILDAY; + i_untiltime = ZFC_TILTIME; + z.z_name = NULL; + } + else if (!namecheck(fields[ZF_NAME])) + return false; + else + { + i_stdoff = ZF_STDOFF; + i_rule = ZF_RULE; + i_format = ZF_FORMAT; + i_untilyear = ZF_TILYEAR; + i_untilmonth = ZF_TILMONTH; + i_untilday = ZF_TILDAY; + i_untiltime = ZF_TILTIME; + z.z_name = ecpyalloc(fields[ZF_NAME]); + } + z.z_filename = filename; + z.z_linenum = linenum; + z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset")); + if ((cp = strchr(fields[i_format], '%')) != NULL) + { + if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%') + || strchr(fields[i_format], '/')) + { + error(_("invalid abbreviation format")); + return false; + } + } + z.z_rule = ecpyalloc(fields[i_rule]); + z.z_format = cp1 = ecpyalloc(fields[i_format]); + z.z_format_specifier = cp ? *cp : '\0'; + if (z.z_format_specifier == 'z') + { + if (noise) + warning(_("format '%s' not handled by pre-2015 versions of zic"), + z.z_format); + cp1[cp - fields[i_format]] = 's'; + } + if (max_format_len < strlen(z.z_format)) + max_format_len = strlen(z.z_format); + hasuntil = nfields > i_untilyear; + if (hasuntil) + { + z.z_untilrule.r_filename = filename; + z.z_untilrule.r_linenum = linenum; + rulesub(&z.z_untilrule, + fields[i_untilyear], + "only", + "", + (nfields > i_untilmonth) ? + fields[i_untilmonth] : "Jan", + (nfields > i_untilday) ? fields[i_untilday] : "1", + (nfields > i_untiltime) ? fields[i_untiltime] : "0"); + z.z_untiltime = rpytime(&z.z_untilrule, + z.z_untilrule.r_loyear); + if (iscont && nzones > 0 && + z.z_untiltime > min_time && + z.z_untiltime < max_time && + zones[nzones - 1].z_untiltime > min_time && + zones[nzones - 1].z_untiltime < max_time && + zones[nzones - 1].z_untiltime >= z.z_untiltime) + { + error(_("Zone continuation line end time is not after end time of previous line")); + return false; + } + } + zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc); + zones[nzones++] = z; + + /* + * If there was an UNTIL field on this line, there's more information + * about the zone on the next line. + */ + return hasuntil; +} + +static zic_t +getleapdatetime(char **fields, int nfields, bool expire_line) +{ + const char *cp; + const struct lookup *lp; + zic_t i, + j; + + /* PG: make year be int not zic_t to avoid sscanf portability issues */ + int year; + int month, + day; + zic_t dayoff, + tod; + zic_t t; + char xs; + + dayoff = 0; + cp = fields[LP_YEAR]; + if (sscanf(cp, "%d%c", &year, &xs) != 1) + { + /* + * Leapin' Lizards! + */ + error(_("invalid leaping year")); + return -1; + } + if (!expire_line) + { + if (!leapseen || leapmaxyear < year) + leapmaxyear = year; + if (!leapseen || leapminyear > year) + leapminyear = year; + leapseen = true; + } + j = EPOCH_YEAR; + while (j != year) + { + if (year > j) + { + i = len_years[isleap(j)]; + ++j; + } + else + { + --j; + i = -len_years[isleap(j)]; + } + dayoff = oadd(dayoff, i); + } + if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) + { + error(_("invalid month name")); + return -1; + } + month = lp->l_value; + j = TM_JANUARY; + while (j != month) + { + i = len_months[isleap(year)][j]; + dayoff = oadd(dayoff, i); + ++j; + } + cp = fields[LP_DAY]; + if (sscanf(cp, "%d%c", &day, &xs) != 1 || + day <= 0 || day > len_months[isleap(year)][month]) + { + error(_("invalid day of month")); + return -1; + } + dayoff = oadd(dayoff, day - 1); + if (dayoff < min_time / SECSPERDAY) + { + error(_("time too small")); + return -1; + } + if (dayoff > max_time / SECSPERDAY) + { + error(_("time too large")); + return -1; + } + t = dayoff * SECSPERDAY; + tod = gethms(fields[LP_TIME], _("invalid time of day")); + t = tadd(t, tod); + if (t < 0) + error(_("leap second precedes Epoch")); + return t; +} + +static void +inleap(char **fields, int nfields) +{ + if (nfields != LEAP_FIELDS) + error(_("wrong number of fields on Leap line")); + else + { + zic_t t = getleapdatetime(fields, nfields, false); + + if (0 <= t) + { + struct lookup const *lp = byword(fields[LP_ROLL], leap_types); + + if (!lp) + error(_("invalid Rolling/Stationary field on Leap line")); + else + { + int correction = 0; + + if (!fields[LP_CORR][0]) /* infile() turns "-" into "". */ + correction = -1; + else if (strcmp(fields[LP_CORR], "+") == 0) + correction = 1; + else + error(_("invalid CORRECTION field on Leap line")); + if (correction) + leapadd(t, correction, lp->l_value); + } + } + } +} + +static void +inexpires(char **fields, int nfields) +{ + if (nfields != EXPIRES_FIELDS) + error(_("wrong number of fields on Expires line")); + else if (0 <= leapexpires) + error(_("multiple Expires lines")); + else + leapexpires = getleapdatetime(fields, nfields, true); +} + +static void +inlink(char **fields, int nfields) +{ + struct link l; + + if (nfields != LINK_FIELDS) + { + error(_("wrong number of fields on Link line")); + return; + } + if (*fields[LF_TARGET] == '\0') + { + error(_("blank TARGET field on Link line")); + return; + } + if (!namecheck(fields[LF_LINKNAME])) + return; + l.l_filename = filename; + l.l_linenum = linenum; + l.l_target = ecpyalloc(fields[LF_TARGET]); + l.l_linkname = ecpyalloc(fields[LF_LINKNAME]); + links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc); + links[nlinks++] = l; +} + +static void +rulesub(struct rule *rp, const char *loyearp, const char *hiyearp, + const char *typep, const char *monthp, const char *dayp, + const char *timep) +{ + const struct lookup *lp; + const char *cp; + char *dp; + char *ep; + char xs; + + /* PG: year_tmp is to avoid sscanf portability issues */ + int year_tmp; + + if ((lp = byword(monthp, mon_names)) == NULL) + { + error(_("invalid month name")); + return; + } + rp->r_month = lp->l_value; + rp->r_todisstd = false; + rp->r_todisut = false; + dp = ecpyalloc(timep); + if (*dp != '\0') + { + ep = dp + strlen(dp) - 1; + switch (lowerit(*ep)) + { + case 's': /* Standard */ + rp->r_todisstd = true; + rp->r_todisut = false; + *ep = '\0'; + break; + case 'w': /* Wall */ + rp->r_todisstd = false; + rp->r_todisut = false; + *ep = '\0'; + break; + case 'g': /* Greenwich */ + case 'u': /* Universal */ + case 'z': /* Zulu */ + rp->r_todisstd = true; + rp->r_todisut = true; + *ep = '\0'; + break; + } + } + rp->r_tod = gethms(dp, _("invalid time of day")); + free(dp); + + /* + * Year work. + */ + cp = loyearp; + lp = byword(cp, begin_years); + rp->r_lowasnum = lp == NULL; + if (!rp->r_lowasnum) + switch (lp->l_value) + { + case YR_MINIMUM: + rp->r_loyear = ZIC_MIN; + break; + case YR_MAXIMUM: + rp->r_loyear = ZIC_MAX; + break; + default: /* "cannot happen" */ + fprintf(stderr, + _("%s: panic: Invalid l_value %d\n"), + progname, lp->l_value); + exit(EXIT_FAILURE); + } + else if (sscanf(cp, "%d%c", &year_tmp, &xs) == 1) + rp->r_loyear = year_tmp; + else + { + error(_("invalid starting year")); + return; + } + cp = hiyearp; + lp = byword(cp, end_years); + rp->r_hiwasnum = lp == NULL; + if (!rp->r_hiwasnum) + switch (lp->l_value) + { + case YR_MINIMUM: + rp->r_hiyear = ZIC_MIN; + break; + case YR_MAXIMUM: + rp->r_hiyear = ZIC_MAX; + break; + case YR_ONLY: + rp->r_hiyear = rp->r_loyear; + break; + default: /* "cannot happen" */ + fprintf(stderr, + _("%s: panic: Invalid l_value %d\n"), + progname, lp->l_value); + exit(EXIT_FAILURE); + } + else if (sscanf(cp, "%d%c", &year_tmp, &xs) == 1) + rp->r_hiyear = year_tmp; + else + { + error(_("invalid ending year")); + return; + } + if (rp->r_loyear > rp->r_hiyear) + { + error(_("starting year greater than ending year")); + return; + } + if (*typep != '\0') + { + error(_("year type \"%s\" is unsupported; use \"-\" instead"), + typep); + return; + } + + /* + * Day work. Accept things such as: 1 lastSunday last-Sunday + * (undocumented; warn about this) Sun<=20 Sun>=7 + */ + dp = ecpyalloc(dayp); + if ((lp = byword(dp, lasts)) != NULL) + { + rp->r_dycode = DC_DOWLEQ; + rp->r_wday = lp->l_value; + rp->r_dayofmonth = len_months[1][rp->r_month]; + } + else + { + if ((ep = strchr(dp, '<')) != NULL) + rp->r_dycode = DC_DOWLEQ; + else if ((ep = strchr(dp, '>')) != NULL) + rp->r_dycode = DC_DOWGEQ; + else + { + ep = dp; + rp->r_dycode = DC_DOM; + } + if (rp->r_dycode != DC_DOM) + { + *ep++ = 0; + if (*ep++ != '=') + { + error(_("invalid day of month")); + free(dp); + return; + } + if ((lp = byword(dp, wday_names)) == NULL) + { + error(_("invalid weekday name")); + free(dp); + return; + } + rp->r_wday = lp->l_value; + } + if (sscanf(ep, "%d%c", &rp->r_dayofmonth, &xs) != 1 || + rp->r_dayofmonth <= 0 || + (rp->r_dayofmonth > len_months[1][rp->r_month])) + { + error(_("invalid day of month")); + free(dp); + return; + } + } + free(dp); +} + +static void +convert(const int32 val, char *const buf) +{ + int i; + int shift; + unsigned char *const b = (unsigned char *) buf; + + for (i = 0, shift = 24; i < 4; ++i, shift -= 8) + b[i] = val >> shift; +} + +static void +convert64(const zic_t val, char *const buf) +{ + int i; + int shift; + unsigned char *const b = (unsigned char *) buf; + + for (i = 0, shift = 56; i < 8; ++i, shift -= 8) + b[i] = val >> shift; +} + +static void +puttzcode(const int32 val, FILE *const fp) +{ + char buf[4]; + + convert(val, buf); + fwrite(buf, sizeof buf, 1, fp); +} + +static void +puttzcodepass(zic_t val, FILE *fp, int pass) +{ + if (pass == 1) + puttzcode(val, fp); + else + { + char buf[8]; + + convert64(val, buf); + fwrite(buf, sizeof buf, 1, fp); + } +} + +static int +atcomp(const void *avp, const void *bvp) +{ + const zic_t a = ((const struct attype *) avp)->at; + const zic_t b = ((const struct attype *) bvp)->at; + + return (a < b) ? -1 : (a > b); +} + +struct timerange +{ + int defaulttype; + ptrdiff_t base, + count; + int leapbase, + leapcount; +}; + +static struct timerange +limitrange(struct timerange r, zic_t lo, zic_t hi, + zic_t const *ats, unsigned char const *types) +{ + while (0 < r.count && ats[r.base] < lo) + { + r.defaulttype = types[r.base]; + r.count--; + r.base++; + } + while (0 < r.leapcount && trans[r.leapbase] < lo) + { + r.leapcount--; + r.leapbase++; + } + + if (hi < ZIC_MAX) + { + while (0 < r.count && hi + 1 < ats[r.base + r.count - 1]) + r.count--; + while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1]) + r.leapcount--; + } + + return r; +} + +static void +writezone(const char *const name, const char *const string, char version, + int defaulttype) +{ + FILE *fp; + ptrdiff_t i, + j; + int pass; + static const struct tzhead tzh0; + static struct tzhead tzh; + bool dir_checked = false; + zic_t one = 1; + zic_t y2038_boundary = one << 31; + ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071; + + /* + * Allocate the ATS and TYPES arrays via a single malloc, as this is a bit + * faster. + */ + zic_t *ats = emalloc(MAXALIGN(size_product(nats, sizeof *ats + 1))); + void *typesptr = ats + nats; + unsigned char *types = typesptr; + struct timerange rangeall, + range32, + range64; + + /* + * Sort. + */ + if (timecnt > 1) + qsort(attypes, timecnt, sizeof *attypes, atcomp); + + /* + * Optimize. + */ + { + ptrdiff_t fromi, + toi; + + toi = 0; + fromi = 0; + for (; fromi < timecnt; ++fromi) + { + if (toi != 0 + && ((attypes[fromi].at + + utoffs[attypes[toi - 1].type]) + <= (attypes[toi - 1].at + + utoffs[toi == 1 ? 0 + : attypes[toi - 2].type]))) + { + attypes[toi - 1].type = + attypes[fromi].type; + continue; + } + if (toi == 0 + || attypes[fromi].dontmerge + || (utoffs[attypes[toi - 1].type] + != utoffs[attypes[fromi].type]) + || (isdsts[attypes[toi - 1].type] + != isdsts[attypes[fromi].type]) + || (desigidx[attypes[toi - 1].type] + != desigidx[attypes[fromi].type])) + attypes[toi++] = attypes[fromi]; + } + timecnt = toi; + } + + if (noise && timecnt > 1200) + { + if (timecnt > TZ_MAX_TIMES) + warning(_("reference clients mishandle" + " more than %d transition times"), + TZ_MAX_TIMES); + else + warning(_("pre-2014 clients may mishandle" + " more than 1200 transition times")); + } + + /* + * Transfer. + */ + for (i = 0; i < timecnt; ++i) + { + ats[i] = attypes[i].at; + types[i] = attypes[i].type; + } + + /* + * Correct for leap seconds. + */ + for (i = 0; i < timecnt; ++i) + { + j = leapcnt; + while (--j >= 0) + if (ats[i] > trans[j] - corr[j]) + { + ats[i] = tadd(ats[i], corr[j]); + break; + } + } + + /* + * Work around QTBUG-53071 for timestamps less than y2038_boundary - 1, by + * inserting a no-op transition at time y2038_boundary - 1. This works + * only for timestamps before the boundary, which should be good enough in + * practice as QTBUG-53071 should be long-dead by 2038. Do this after + * correcting for leap seconds, as the idea is to insert a transition just + * before 32-bit pg_time_t rolls around, and this occurs at a slightly + * different moment if transitions are leap-second corrected. + */ + if (WORK_AROUND_QTBUG_53071 && timecnt != 0 && want_bloat() + && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<')) + { + ats[timecnt] = y2038_boundary - 1; + types[timecnt] = types[timecnt - 1]; + timecnt++; + } + + rangeall.defaulttype = defaulttype; + rangeall.base = rangeall.leapbase = 0; + rangeall.count = timecnt; + rangeall.leapcount = leapcnt; + range64 = limitrange(rangeall, lo_time, hi_time, ats, types); + range32 = limitrange(range64, PG_INT32_MIN, PG_INT32_MAX, ats, types); + + /* + * Remove old file, if any, to snap links. + */ + if (remove(name) == 0) + dir_checked = true; + else if (errno != ENOENT) + { + const char *e = strerror(errno); + + fprintf(stderr, _("%s: Cannot remove %s/%s: %s\n"), + progname, directory, name, e); + exit(EXIT_FAILURE); + } + fp = fopen(name, "wb"); + if (!fp) + { + int fopen_errno = errno; + + if (fopen_errno == ENOENT && !dir_checked) + { + mkdirs(name, true); + fp = fopen(name, "wb"); + fopen_errno = errno; + } + if (!fp) + { + fprintf(stderr, _("%s: Cannot create %s/%s: %s\n"), + progname, directory, name, strerror(fopen_errno)); + exit(EXIT_FAILURE); + } + } + for (pass = 1; pass <= 2; ++pass) + { + ptrdiff_t thistimei, + thistimecnt, + thistimelim; + int thisleapi, + thisleapcnt, + thisleaplim; + int currenttype, + thisdefaulttype; + bool locut, + hicut; + zic_t lo; + int old0; + char omittype[TZ_MAX_TYPES]; + int typemap[TZ_MAX_TYPES]; + int thistypecnt, + stdcnt, + utcnt; + char thischars[TZ_MAX_CHARS]; + int thischarcnt; + bool toomanytimes; + int indmap[TZ_MAX_CHARS]; + + if (pass == 1) + { + /* + * Arguably the default time type in the 32-bit data should be + * range32.defaulttype, which is suited for timestamps just before + * PG_INT32_MIN. However, zic traditionally used the time type of + * the indefinite past instead. Internet RFC 8532 says readers + * should ignore 32-bit data, so this discrepancy matters only to + * obsolete readers where the traditional type might be more + * appropriate even if it's "wrong". So, use the historical zic + * value, unless -r specifies a low cutoff that excludes some + * 32-bit timestamps. + */ + thisdefaulttype = (lo_time <= PG_INT32_MIN + ? range64.defaulttype + : range32.defaulttype); + + thistimei = range32.base; + thistimecnt = range32.count; + toomanytimes = thistimecnt >> 31 >> 1 != 0; + thisleapi = range32.leapbase; + thisleapcnt = range32.leapcount; + locut = PG_INT32_MIN < lo_time; + hicut = hi_time < PG_INT32_MAX; + } + else + { + thisdefaulttype = range64.defaulttype; + thistimei = range64.base; + thistimecnt = range64.count; + toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0; + thisleapi = range64.leapbase; + thisleapcnt = range64.leapcount; + locut = min_time < lo_time; + hicut = hi_time < max_time; + } + if (toomanytimes) + error(_("too many transition times")); + + /* + * Keep the last too-low transition if no transition is exactly at LO. + * The kept transition will be output as a LO "transition"; see + * "Output a LO_TIME transition" below. This is needed when the + * output is truncated at the start, and is also useful when catering + * to buggy 32-bit clients that do not use time type 0 for timestamps + * before the first transition. + */ + if (0 < thistimei && ats[thistimei] != lo_time) + { + thistimei--; + thistimecnt++; + locut = false; + } + + thistimelim = thistimei + thistimecnt; + thisleaplim = thisleapi + thisleapcnt; + if (thistimecnt != 0) + { + if (ats[thistimei] == lo_time) + locut = false; + if (hi_time < ZIC_MAX && ats[thistimelim - 1] == hi_time + 1) + hicut = false; + } + memset(omittype, true, typecnt); + omittype[thisdefaulttype] = false; + for (i = thistimei; i < thistimelim; i++) + omittype[types[i]] = false; + + /* + * Reorder types to make THISDEFAULTTYPE type 0. Use TYPEMAP to swap + * OLD0 and THISDEFAULTTYPE so that THISDEFAULTTYPE appears as type 0 + * in the output instead of OLD0. TYPEMAP also omits unused types. + */ + old0 = strlen(omittype); + +#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH + + /* + * For some pre-2011 systems: if the last-to-be-written standard (or + * daylight) type has an offset different from the most recently used + * offset, append an (unused) copy of the most recently used type (to + * help get global "altzone" and "timezone" variables set correctly). + */ + if (want_bloat()) + { + int mrudst, + mrustd, + hidst, + histd, + type; + + hidst = histd = mrudst = mrustd = -1; + for (i = thistimei; i < thistimelim; ++i) + if (isdsts[types[i]]) + mrudst = types[i]; + else + mrustd = types[i]; + for (i = old0; i < typecnt; i++) + { + int h = (i == old0 ? thisdefaulttype + : i == thisdefaulttype ? old0 : i); + + if (!omittype[h]) + { + if (isdsts[h]) + hidst = i; + else + histd = i; + } + } + if (hidst >= 0 && mrudst >= 0 && hidst != mrudst && + utoffs[hidst] != utoffs[mrudst]) + { + isdsts[mrudst] = -1; + type = addtype(utoffs[mrudst], + &chars[desigidx[mrudst]], + true, + ttisstds[mrudst], + ttisuts[mrudst]); + isdsts[mrudst] = 1; + omittype[type] = false; + } + if (histd >= 0 && mrustd >= 0 && histd != mrustd && + utoffs[histd] != utoffs[mrustd]) + { + isdsts[mrustd] = -1; + type = addtype(utoffs[mrustd], + &chars[desigidx[mrustd]], + false, + ttisstds[mrustd], + ttisuts[mrustd]); + isdsts[mrustd] = 0; + omittype[type] = false; + } + } +#endif /* !defined + * LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */ + thistypecnt = 0; + for (i = old0; i < typecnt; i++) + if (!omittype[i]) + typemap[i == old0 ? thisdefaulttype + : i == thisdefaulttype ? old0 : i] + = thistypecnt++; + + for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i) + indmap[i] = -1; + thischarcnt = stdcnt = utcnt = 0; + for (i = old0; i < typecnt; i++) + { + char *thisabbr; + + if (omittype[i]) + continue; + if (ttisstds[i]) + stdcnt = thistypecnt; + if (ttisuts[i]) + utcnt = thistypecnt; + if (indmap[desigidx[i]] >= 0) + continue; + thisabbr = &chars[desigidx[i]]; + for (j = 0; j < thischarcnt; ++j) + if (strcmp(&thischars[j], thisabbr) == 0) + break; + if (j == thischarcnt) + { + strcpy(&thischars[thischarcnt], thisabbr); + thischarcnt += strlen(thisabbr) + 1; + } + indmap[desigidx[i]] = j; + } + if (pass == 1 && !want_bloat()) + { + utcnt = stdcnt = thisleapcnt = 0; + thistimecnt = -(locut + hicut); + thistypecnt = thischarcnt = 1; + thistimelim = thistimei; + } +#define DO(field) fwrite(tzh.field, sizeof tzh.field, 1, fp) + tzh = tzh0; + memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); + tzh.tzh_version[0] = version; + convert(utcnt, tzh.tzh_ttisutcnt); + convert(stdcnt, tzh.tzh_ttisstdcnt); + convert(thisleapcnt, tzh.tzh_leapcnt); + convert(locut + thistimecnt + hicut, tzh.tzh_timecnt); + convert(thistypecnt, tzh.tzh_typecnt); + convert(thischarcnt, tzh.tzh_charcnt); + DO(tzh_magic); + DO(tzh_version); + DO(tzh_reserved); + DO(tzh_ttisutcnt); + DO(tzh_ttisstdcnt); + DO(tzh_leapcnt); + DO(tzh_timecnt); + DO(tzh_typecnt); + DO(tzh_charcnt); +#undef DO + if (pass == 1 && !want_bloat()) + { + /* Output a minimal data block with just one time type. */ + puttzcode(0, fp); /* utoff */ + putc(0, fp); /* dst */ + putc(0, fp); /* index of abbreviation */ + putc(0, fp); /* empty-string abbreviation */ + continue; + } + + /* PG: print current timezone abbreviations if requested */ + if (print_abbrevs && pass == 2) + { + /* Print "type" data for periods ending after print_cutoff */ + for (i = thistimei; i < thistimelim; ++i) + { + if (i == thistimelim - 1 || ats[i + 1] > print_cutoff) + { + unsigned char tm = types[i]; + char *thisabbrev = &thischars[indmap[desigidx[tm]]]; + + fprintf(stdout, "%s\t" INT64_FORMAT "%s\n", + thisabbrev, + utoffs[tm], + isdsts[tm] ? "\tD" : ""); + } + } + /* Print the default type if we have no transitions at all */ + if (thistimei >= thistimelim) + { + unsigned char tm = defaulttype; + char *thisabbrev = &thischars[indmap[desigidx[tm]]]; + + fprintf(stdout, "%s\t" INT64_FORMAT "%s\n", + thisabbrev, + utoffs[tm], + isdsts[tm] ? "\tD" : ""); + } + } + + /* + * Output a LO_TIME transition if needed; see limitrange. But do not + * go below the minimum representable value for this pass. + */ + lo = pass == 1 && lo_time < PG_INT32_MIN ? PG_INT32_MIN : lo_time; + + if (locut) + puttzcodepass(lo, fp, pass); + for (i = thistimei; i < thistimelim; ++i) + { + zic_t at = ats[i] < lo ? lo : ats[i]; + + puttzcodepass(at, fp, pass); + } + if (hicut) + puttzcodepass(hi_time + 1, fp, pass); + currenttype = 0; + if (locut) + putc(currenttype, fp); + for (i = thistimei; i < thistimelim; ++i) + { + currenttype = typemap[types[i]]; + putc(currenttype, fp); + } + if (hicut) + putc(currenttype, fp); + + for (i = old0; i < typecnt; i++) + { + int h = (i == old0 ? thisdefaulttype + : i == thisdefaulttype ? old0 : i); + + if (!omittype[h]) + { + puttzcode(utoffs[h], fp); + putc(isdsts[h], fp); + putc(indmap[desigidx[h]], fp); + } + } + if (thischarcnt != 0) + fwrite(thischars, sizeof thischars[0], + thischarcnt, fp); + for (i = thisleapi; i < thisleaplim; ++i) + { + zic_t todo; + + if (roll[i]) + { + if (timecnt == 0 || trans[i] < ats[0]) + { + j = 0; + while (isdsts[j]) + if (++j >= typecnt) + { + j = 0; + break; + } + } + else + { + j = 1; + while (j < timecnt && + trans[i] >= ats[j]) + ++j; + j = types[j - 1]; + } + todo = tadd(trans[i], -utoffs[j]); + } + else + todo = trans[i]; + puttzcodepass(todo, fp, pass); + puttzcode(corr[i], fp); + } + if (stdcnt != 0) + for (i = old0; i < typecnt; i++) + if (!omittype[i]) + putc(ttisstds[i], fp); + if (utcnt != 0) + for (i = old0; i < typecnt; i++) + if (!omittype[i]) + putc(ttisuts[i], fp); + } + fprintf(fp, "\n%s\n", string); + close_file(fp, directory, name); + free(ats); +} + +static char const * +abbroffset(char *buf, zic_t offset) +{ + char sign = '+'; + int seconds, + minutes; + + if (offset < 0) + { + offset = -offset; + sign = '-'; + } + + seconds = offset % SECSPERMIN; + offset /= SECSPERMIN; + minutes = offset % MINSPERHOUR; + offset /= MINSPERHOUR; + if (100 <= offset) + { + error(_("%%z UT offset magnitude exceeds 99:59:59")); + return "%z"; + } + else + { + char *p = buf; + + *p++ = sign; + *p++ = '0' + offset / 10; + *p++ = '0' + offset % 10; + if (minutes | seconds) + { + *p++ = '0' + minutes / 10; + *p++ = '0' + minutes % 10; + if (seconds) + { + *p++ = '0' + seconds / 10; + *p++ = '0' + seconds % 10; + } + } + *p = '\0'; + return buf; + } +} + +static size_t +doabbr(char *abbr, struct zone const *zp, char const *letters, + bool isdst, zic_t save, bool doquotes) +{ + char *cp; + char *slashp; + size_t len; + char const *format = zp->z_format; + + slashp = strchr(format, '/'); + if (slashp == NULL) + { + char letterbuf[PERCENT_Z_LEN_BOUND + 1]; + + if (zp->z_format_specifier == 'z') + letters = abbroffset(letterbuf, zp->z_stdoff + save); + else if (!letters) + letters = "%s"; + sprintf(abbr, format, letters); + } + else if (isdst) + { + strcpy(abbr, slashp + 1); + } + else + { + memcpy(abbr, format, slashp - format); + abbr[slashp - format] = '\0'; + } + len = strlen(abbr); + if (!doquotes) + return len; + for (cp = abbr; is_alpha(*cp); cp++) + continue; + if (len > 0 && *cp == '\0') + return len; + abbr[len + 2] = '\0'; + abbr[len + 1] = '>'; + memmove(abbr + 1, abbr, len); + abbr[0] = '<'; + return len + 2; +} + +static void +updateminmax(const zic_t x) +{ + if (min_year > x) + min_year = x; + if (max_year < x) + max_year = x; +} + +static int +stringoffset(char *result, zic_t offset) +{ + int hours; + int minutes; + int seconds; + bool negative = offset < 0; + int len = negative; + + if (negative) + { + offset = -offset; + result[0] = '-'; + } + seconds = offset % SECSPERMIN; + offset /= SECSPERMIN; + minutes = offset % MINSPERHOUR; + offset /= MINSPERHOUR; + hours = offset; + if (hours >= HOURSPERDAY * DAYSPERWEEK) + { + result[0] = '\0'; + return 0; + } + len += sprintf(result + len, "%d", hours); + if (minutes != 0 || seconds != 0) + { + len += sprintf(result + len, ":%02d", minutes); + if (seconds != 0) + len += sprintf(result + len, ":%02d", seconds); + } + return len; +} + +static int +stringrule(char *result, struct rule *const rp, zic_t save, zic_t stdoff) +{ + zic_t tod = rp->r_tod; + int compat = 0; + + if (rp->r_dycode == DC_DOM) + { + int month, + total; + + if (rp->r_dayofmonth == 29 && rp->r_month == TM_FEBRUARY) + return -1; + total = 0; + for (month = 0; month < rp->r_month; ++month) + total += len_months[0][month]; + /* Omit the "J" in Jan and Feb, as that's shorter. */ + if (rp->r_month <= 1) + result += sprintf(result, "%d", total + rp->r_dayofmonth - 1); + else + result += sprintf(result, "J%d", total + rp->r_dayofmonth); + } + else + { + int week; + int wday = rp->r_wday; + int wdayoff; + + if (rp->r_dycode == DC_DOWGEQ) + { + wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK; + if (wdayoff) + compat = 2013; + wday -= wdayoff; + tod += wdayoff * SECSPERDAY; + week = 1 + (rp->r_dayofmonth - 1) / DAYSPERWEEK; + } + else if (rp->r_dycode == DC_DOWLEQ) + { + if (rp->r_dayofmonth == len_months[1][rp->r_month]) + week = 5; + else + { + wdayoff = rp->r_dayofmonth % DAYSPERWEEK; + if (wdayoff) + compat = 2013; + wday -= wdayoff; + tod += wdayoff * SECSPERDAY; + week = rp->r_dayofmonth / DAYSPERWEEK; + } + } + else + return -1; /* "cannot happen" */ + if (wday < 0) + wday += DAYSPERWEEK; + result += sprintf(result, "M%d.%d.%d", + rp->r_month + 1, week, wday); + } + if (rp->r_todisut) + tod += stdoff; + if (rp->r_todisstd && !rp->r_isdst) + tod += save; + if (tod != 2 * SECSPERMIN * MINSPERHOUR) + { + *result++ = '/'; + if (!stringoffset(result, tod)) + return -1; + if (tod < 0) + { + if (compat < 2013) + compat = 2013; + } + else if (SECSPERDAY <= tod) + { + if (compat < 1994) + compat = 1994; + } + } + return compat; +} + +static int +rule_cmp(struct rule const *a, struct rule const *b) +{ + if (!a) + return -!!b; + if (!b) + return 1; + if (a->r_hiyear != b->r_hiyear) + return a->r_hiyear < b->r_hiyear ? -1 : 1; + if (a->r_month - b->r_month != 0) + return a->r_month - b->r_month; + return a->r_dayofmonth - b->r_dayofmonth; +} + +static int +stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) +{ + const struct zone *zp; + struct rule *rp; + struct rule *stdrp; + struct rule *dstrp; + ptrdiff_t i; + const char *abbrvar; + int compat = 0; + int c; + size_t len; + int offsetlen; + struct rule stdr, + dstr; + + result[0] = '\0'; + + /* + * Internet RFC 8536 section 5.1 says to use an empty TZ string if future + * timestamps are truncated. + */ + if (hi_time < max_time) + return -1; + + zp = zpfirst + zonecount - 1; + stdrp = dstrp = NULL; + for (i = 0; i < zp->z_nrules; ++i) + { + rp = &zp->z_rules[i]; + if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX) + continue; + if (!rp->r_isdst) + { + if (stdrp == NULL) + stdrp = rp; + else + return -1; + } + else + { + if (dstrp == NULL) + dstrp = rp; + else + return -1; + } + } + if (stdrp == NULL && dstrp == NULL) + { + /* + * There are no rules running through "max". Find the latest std rule + * in stdabbrrp and latest rule of any type in stdrp. + */ + struct rule *stdabbrrp = NULL; + + for (i = 0; i < zp->z_nrules; ++i) + { + rp = &zp->z_rules[i]; + if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0) + stdabbrrp = rp; + if (rule_cmp(stdrp, rp) < 0) + stdrp = rp; + } + if (stdrp != NULL && stdrp->r_isdst) + { + /* Perpetual DST. */ + dstr.r_month = TM_JANUARY; + dstr.r_dycode = DC_DOM; + dstr.r_dayofmonth = 1; + dstr.r_tod = 0; + dstr.r_todisstd = dstr.r_todisut = false; + dstr.r_isdst = stdrp->r_isdst; + dstr.r_save = stdrp->r_save; + dstr.r_abbrvar = stdrp->r_abbrvar; + stdr.r_month = TM_DECEMBER; + stdr.r_dycode = DC_DOM; + stdr.r_dayofmonth = 31; + stdr.r_tod = SECSPERDAY + stdrp->r_save; + stdr.r_todisstd = stdr.r_todisut = false; + stdr.r_isdst = false; + stdr.r_save = 0; + stdr.r_abbrvar + = (stdabbrrp ? stdabbrrp->r_abbrvar : ""); + dstrp = &dstr; + stdrp = &stdr; + } + } + if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst)) + return -1; + abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar; + len = doabbr(result, zp, abbrvar, false, 0, true); + offsetlen = stringoffset(result + len, -zp->z_stdoff); + if (!offsetlen) + { + result[0] = '\0'; + return -1; + } + len += offsetlen; + if (dstrp == NULL) + return compat; + len += doabbr(result + len, zp, dstrp->r_abbrvar, + dstrp->r_isdst, dstrp->r_save, true); + if (dstrp->r_save != SECSPERMIN * MINSPERHOUR) + { + offsetlen = stringoffset(result + len, + -(zp->z_stdoff + dstrp->r_save)); + if (!offsetlen) + { + result[0] = '\0'; + return -1; + } + len += offsetlen; + } + result[len++] = ','; + c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff); + if (c < 0) + { + result[0] = '\0'; + return -1; + } + if (compat < c) + compat = c; + len += strlen(result + len); + result[len++] = ','; + c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff); + if (c < 0) + { + result[0] = '\0'; + return -1; + } + if (compat < c) + compat = c; + return compat; +} + +static void +outzone(const struct zone *zpfirst, ptrdiff_t zonecount) +{ + const struct zone *zp; + struct rule *rp; + ptrdiff_t i, + j; + bool usestart, + useuntil; + zic_t starttime, + untiltime; + zic_t stdoff; + zic_t save; + zic_t year; + zic_t startoff; + bool startttisstd; + bool startttisut; + int type; + char *startbuf; + char *ab; + char *envvar; + int max_abbr_len; + int max_envvar_len; + bool prodstic; /* all rules are min to max */ + int compat; + bool do_extend; + char version; + ptrdiff_t lastatmax = -1; + zic_t one = 1; + zic_t y2038_boundary = one << 31; + zic_t max_year0; + int defaulttype = -1; + + max_abbr_len = 2 + max_format_len + max_abbrvar_len; + max_envvar_len = 2 * max_abbr_len + 5 * 9; + startbuf = emalloc(max_abbr_len + 1); + ab = emalloc(max_abbr_len + 1); + envvar = emalloc(max_envvar_len + 1); + INITIALIZE(untiltime); + INITIALIZE(starttime); + + /* + * Now. . .finally. . .generate some useful data! + */ + timecnt = 0; + typecnt = 0; + charcnt = 0; + prodstic = zonecount == 1; + + /* + * Thanks to Earl Chew for noting the need to unconditionally initialize + * startttisstd. + */ + startttisstd = false; + startttisut = false; + min_year = max_year = EPOCH_YEAR; + if (leapseen) + { + updateminmax(leapminyear); + updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX)); + } + for (i = 0; i < zonecount; ++i) + { + zp = &zpfirst[i]; + if (i < zonecount - 1) + updateminmax(zp->z_untilrule.r_loyear); + for (j = 0; j < zp->z_nrules; ++j) + { + rp = &zp->z_rules[j]; + if (rp->r_lowasnum) + updateminmax(rp->r_loyear); + if (rp->r_hiwasnum) + updateminmax(rp->r_hiyear); + if (rp->r_lowasnum || rp->r_hiwasnum) + prodstic = false; + } + } + + /* + * Generate lots of data if a rule can't cover all future times. + */ + compat = stringzone(envvar, zpfirst, zonecount); + version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION; + do_extend = compat < 0; + if (noise) + { + if (!*envvar) + warning("%s %s", + _("no POSIX environment variable for zone"), + zpfirst->z_name); + else if (compat != 0) + { + /* + * Circa-COMPAT clients, and earlier clients, might not work for + * this zone when given dates before 1970 or after 2038. + */ + warning(_("%s: pre-%d clients may mishandle" + " distant timestamps"), + zpfirst->z_name, compat); + } + } + if (do_extend) + { + /* + * Search through a couple of extra years past the obvious 400, to + * avoid edge cases. For example, suppose a non-POSIX rule applies + * from 2012 onwards and has transitions in March and September, plus + * some one-off transitions in November 2013. If zic looked only at + * the last 400 years, it would set max_year=2413, with the intent + * that the 400 years 2014 through 2413 will be repeated. The last + * transition listed in the tzfile would be in 2413-09, less than 400 + * years after the last one-off transition in 2013-11. Two years + * might be overkill, but with the kind of edge cases available we're + * not sure that one year would suffice. + */ + enum + { + years_of_observations = YEARSPERREPEAT + 2}; + + if (min_year >= ZIC_MIN + years_of_observations) + min_year -= years_of_observations; + else + min_year = ZIC_MIN; + if (max_year <= ZIC_MAX - years_of_observations) + max_year += years_of_observations; + else + max_year = ZIC_MAX; + + /* + * Regardless of any of the above, for a "proDSTic" zone which + * specifies that its rules always have and always will be in effect, + * we only need one cycle to define the zone. + */ + if (prodstic) + { + min_year = 1900; + max_year = min_year + years_of_observations; + } + } + max_year0 = max_year; + if (want_bloat()) + { + /* + * For the benefit of older systems, generate data from 1900 through + * 2038. + */ + if (min_year > 1900) + min_year = 1900; + if (max_year < 2038) + max_year = 2038; + } + + for (i = 0; i < zonecount; ++i) + { + struct rule *prevrp = NULL; + + /* + * A guess that may well be corrected later. + */ + save = 0; + zp = &zpfirst[i]; + usestart = i > 0 && (zp - 1)->z_untiltime > min_time; + useuntil = i < (zonecount - 1); + if (useuntil && zp->z_untiltime <= min_time) + continue; + stdoff = zp->z_stdoff; + eat(zp->z_filename, zp->z_linenum); + *startbuf = '\0'; + startoff = zp->z_stdoff; + if (zp->z_nrules == 0) + { + save = zp->z_save; + doabbr(startbuf, zp, NULL, zp->z_isdst, save, false); + type = addtype(oadd(zp->z_stdoff, save), + startbuf, zp->z_isdst, startttisstd, + startttisut); + if (usestart) + { + addtt(starttime, type); + usestart = false; + } + else + defaulttype = type; + } + else + for (year = min_year; year <= max_year; ++year) + { + if (useuntil && year > zp->z_untilrule.r_hiyear) + break; + + /* + * Mark which rules to do in the current year. For those to + * do, calculate rpytime(rp, year); The former TYPE field was + * also considered here. + */ + for (j = 0; j < zp->z_nrules; ++j) + { + rp = &zp->z_rules[j]; + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + rp->r_todo = year >= rp->r_loyear && + year <= rp->r_hiyear; + if (rp->r_todo) + { + rp->r_temp = rpytime(rp, year); + rp->r_todo + = (rp->r_temp < y2038_boundary + || year <= max_year0); + } + } + for (;;) + { + ptrdiff_t k; + zic_t jtime, + ktime; + zic_t offset; + + INITIALIZE(ktime); + if (useuntil) + { + /* + * Turn untiltime into UT assuming the current stdoff + * and save values. + */ + untiltime = zp->z_untiltime; + if (!zp->z_untilrule.r_todisut) + untiltime = tadd(untiltime, + -stdoff); + if (!zp->z_untilrule.r_todisstd) + untiltime = tadd(untiltime, + -save); + } + + /* + * Find the rule (of those to do, if any) that takes + * effect earliest in the year. + */ + k = -1; + for (j = 0; j < zp->z_nrules; ++j) + { + rp = &zp->z_rules[j]; + if (!rp->r_todo) + continue; + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + offset = rp->r_todisut ? 0 : stdoff; + if (!rp->r_todisstd) + offset = oadd(offset, save); + jtime = rp->r_temp; + if (jtime == min_time || + jtime == max_time) + continue; + jtime = tadd(jtime, -offset); + if (k < 0 || jtime < ktime) + { + k = j; + ktime = jtime; + } + else if (jtime == ktime) + { + char const *dup_rules_msg = + _("two rules for same instant"); + + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + warning("%s", dup_rules_msg); + rp = &zp->z_rules[k]; + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + error("%s", dup_rules_msg); + } + } + if (k < 0) + break; /* go on to next year */ + rp = &zp->z_rules[k]; + rp->r_todo = false; + if (useuntil && ktime >= untiltime) + break; + save = rp->r_save; + if (usestart && ktime == starttime) + usestart = false; + if (usestart) + { + if (ktime < starttime) + { + startoff = oadd(zp->z_stdoff, + save); + doabbr(startbuf, zp, + rp->r_abbrvar, + rp->r_isdst, + rp->r_save, + false); + continue; + } + if (*startbuf == '\0' + && startoff == oadd(zp->z_stdoff, + save)) + { + doabbr(startbuf, + zp, + rp->r_abbrvar, + rp->r_isdst, + rp->r_save, + false); + } + } + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + doabbr(ab, zp, rp->r_abbrvar, + rp->r_isdst, rp->r_save, false); + offset = oadd(zp->z_stdoff, rp->r_save); + if (!want_bloat() && !useuntil && !do_extend + && prevrp + && rp->r_hiyear == ZIC_MAX + && prevrp->r_hiyear == ZIC_MAX) + break; + type = addtype(offset, ab, rp->r_isdst, + rp->r_todisstd, rp->r_todisut); + if (defaulttype < 0 && !rp->r_isdst) + defaulttype = type; + if (rp->r_hiyear == ZIC_MAX + && !(0 <= lastatmax + && ktime < attypes[lastatmax].at)) + lastatmax = timecnt; + addtt(ktime, type); + prevrp = rp; + } + } + if (usestart) + { + if (*startbuf == '\0' && + zp->z_format != NULL && + strchr(zp->z_format, '%') == NULL && + strchr(zp->z_format, '/') == NULL) + strcpy(startbuf, zp->z_format); + eat(zp->z_filename, zp->z_linenum); + if (*startbuf == '\0') + error(_("cannot determine time zone abbreviation to use just after until time")); + else + { + bool isdst = startoff != zp->z_stdoff; + + type = addtype(startoff, startbuf, isdst, + startttisstd, startttisut); + if (defaulttype < 0 && !isdst) + defaulttype = type; + addtt(starttime, type); + } + } + + /* + * Now we may get to set starttime for the next zone line. + */ + if (useuntil) + { + startttisstd = zp->z_untilrule.r_todisstd; + startttisut = zp->z_untilrule.r_todisut; + starttime = zp->z_untiltime; + if (!startttisstd) + starttime = tadd(starttime, -save); + if (!startttisut) + starttime = tadd(starttime, -stdoff); + } + } + if (defaulttype < 0) + defaulttype = 0; + if (0 <= lastatmax) + attypes[lastatmax].dontmerge = true; + if (do_extend) + { + /* + * If we're extending the explicitly listed observations for 400 years + * because we can't fill the POSIX-TZ field, check whether we actually + * ended up explicitly listing observations through that period. If + * there aren't any near the end of the 400-year period, add a + * redundant one at the end of the final year, to make it clear that + * we are claiming to have definite knowledge of the lack of + * transitions up to that point. + */ + struct rule xr; + struct attype *lastat; + + xr.r_month = TM_JANUARY; + xr.r_dycode = DC_DOM; + xr.r_dayofmonth = 1; + xr.r_tod = 0; + for (lastat = attypes, i = 1; i < timecnt; i++) + if (attypes[i].at > lastat->at) + lastat = &attypes[i]; + if (!lastat || lastat->at < rpytime(&xr, max_year - 1)) + { + addtt(rpytime(&xr, max_year + 1), + lastat ? lastat->type : defaulttype); + attypes[timecnt - 1].dontmerge = true; + } + } + writezone(zpfirst->z_name, envvar, version, defaulttype); + free(startbuf); + free(ab); + free(envvar); +} + +static void +addtt(zic_t starttime, int type) +{ + attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc); + attypes[timecnt].at = starttime; + attypes[timecnt].dontmerge = false; + attypes[timecnt].type = type; + ++timecnt; +} + +static int +addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut) +{ + int i, + j; + + if (!(-1L - 2147483647L <= utoff && utoff <= 2147483647L)) + { + error(_("UT offset out of range")); + exit(EXIT_FAILURE); + } + if (!want_bloat()) + ttisstd = ttisut = false; + + for (j = 0; j < charcnt; ++j) + if (strcmp(&chars[j], abbr) == 0) + break; + if (j == charcnt) + newabbr(abbr); + else + { + /* If there's already an entry, return its index. */ + for (i = 0; i < typecnt; i++) + if (utoff == utoffs[i] && isdst == isdsts[i] && j == desigidx[i] + && ttisstd == ttisstds[i] && ttisut == ttisuts[i]) + return i; + } + + /* + * There isn't one; add a new one, unless there are already too many. + */ + if (typecnt >= TZ_MAX_TYPES) + { + error(_("too many local time types")); + exit(EXIT_FAILURE); + } + i = typecnt++; + utoffs[i] = utoff; + isdsts[i] = isdst; + ttisstds[i] = ttisstd; + ttisuts[i] = ttisut; + desigidx[i] = j; + return i; +} + +static void +leapadd(zic_t t, int correction, int rolling) +{ + int i; + + if (TZ_MAX_LEAPS <= leapcnt) + { + error(_("too many leap seconds")); + exit(EXIT_FAILURE); + } + for (i = 0; i < leapcnt; ++i) + if (t <= trans[i]) + break; + memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans); + memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr); + memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll); + trans[i] = t; + corr[i] = correction; + roll[i] = rolling; + ++leapcnt; +} + +static void +adjleap(void) +{ + int i; + zic_t last = 0; + zic_t prevtrans = 0; + + /* + * propagate leap seconds forward + */ + for (i = 0; i < leapcnt; ++i) + { + if (trans[i] - prevtrans < 28 * SECSPERDAY) + { + error(_("Leap seconds too close together")); + exit(EXIT_FAILURE); + } + prevtrans = trans[i]; + trans[i] = tadd(trans[i], last); + last = corr[i] += last; + } + + if (leapexpires < 0) + { + leapexpires = comment_leapexpires; + if (0 <= leapexpires) + warning(_("\"#expires\" is obsolescent; use \"Expires\"")); + } + + if (0 <= leapexpires) + { + leapexpires = oadd(leapexpires, last); + if (!(leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) + { + error(_("last Leap time does not precede Expires time")); + exit(EXIT_FAILURE); + } + if (leapexpires <= hi_time) + hi_time = leapexpires - 1; + } +} + +/* Is A a space character in the C locale? */ +static bool +is_space(char a) +{ + switch (a) + { + default: + return false; + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + return true; + } +} + +/* Is A an alphabetic character in the C locale? */ +static bool +is_alpha(char a) +{ + switch (a) + { + default: + return false; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + return true; + } +} + +/* If A is an uppercase character in the C locale, return its lowercase + counterpart. Otherwise, return A. */ +static char +lowerit(char a) +{ + switch (a) + { + default: + return a; + case 'A': + return 'a'; + case 'B': + return 'b'; + case 'C': + return 'c'; + case 'D': + return 'd'; + case 'E': + return 'e'; + case 'F': + return 'f'; + case 'G': + return 'g'; + case 'H': + return 'h'; + case 'I': + return 'i'; + case 'J': + return 'j'; + case 'K': + return 'k'; + case 'L': + return 'l'; + case 'M': + return 'm'; + case 'N': + return 'n'; + case 'O': + return 'o'; + case 'P': + return 'p'; + case 'Q': + return 'q'; + case 'R': + return 'r'; + case 'S': + return 's'; + case 'T': + return 't'; + case 'U': + return 'u'; + case 'V': + return 'v'; + case 'W': + return 'w'; + case 'X': + return 'x'; + case 'Y': + return 'y'; + case 'Z': + return 'z'; + } +} + +/* case-insensitive equality */ +static bool +ciequal(const char *ap, const char *bp) +{ + while (lowerit(*ap) == lowerit(*bp++)) + if (*ap++ == '\0') + return true; + return false; +} + +static bool +itsabbr(const char *abbr, const char *word) +{ + if (lowerit(*abbr) != lowerit(*word)) + return false; + ++word; + while (*++abbr != '\0') + do + { + if (*word == '\0') + return false; + } while (lowerit(*word++) != lowerit(*abbr)); + return true; +} + +/* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case. */ + +static bool +ciprefix(char const *abbr, char const *word) +{ + do + if (!*abbr) + return true; + while (lowerit(*abbr++) == lowerit(*word++)); + + return false; +} + +static const struct lookup * +byword(const char *word, const struct lookup *table) +{ + const struct lookup *foundlp; + const struct lookup *lp; + + if (word == NULL || table == NULL) + return NULL; + + /* + * If TABLE is LASTS and the word starts with "last" followed by a + * non-'-', skip the "last" and look in WDAY_NAMES instead. Warn about any + * usage of the undocumented prefix "last-". + */ + if (table == lasts && ciprefix("last", word) && word[4]) + { + if (word[4] == '-') + warning(_("\"%s\" is undocumented; use \"last%s\" instead"), + word, word + 5); + else + { + word += 4; + table = wday_names; + } + } + + /* + * Look for exact match. + */ + for (lp = table; lp->l_word != NULL; ++lp) + if (ciequal(word, lp->l_word)) + return lp; + + /* + * Look for inexact match. + */ + foundlp = NULL; + for (lp = table; lp->l_word != NULL; ++lp) + if (ciprefix(word, lp->l_word)) + { + if (foundlp == NULL) + foundlp = lp; + else + return NULL; /* multiple inexact matches */ + } + + if (foundlp && noise) + { + /* Warn about any backward-compatibility issue with pre-2017c zic. */ + bool pre_2017c_match = false; + + for (lp = table; lp->l_word; lp++) + if (itsabbr(word, lp->l_word)) + { + if (pre_2017c_match) + { + warning(_("\"%s\" is ambiguous in pre-2017c zic"), word); + break; + } + pre_2017c_match = true; + } + } + + return foundlp; +} + +static char ** +getfields(char *cp) +{ + char *dp; + char **array; + int nsubs; + + if (cp == NULL) + return NULL; + array = emalloc(size_product(strlen(cp) + 1, sizeof *array)); + nsubs = 0; + for (;;) + { + while (is_space(*cp)) + ++cp; + if (*cp == '\0' || *cp == '#') + break; + array[nsubs++] = dp = cp; + do + { + if ((*dp = *cp++) != '"') + ++dp; + else + while ((*dp = *cp++) != '"') + if (*dp != '\0') + ++dp; + else + { + error(_("Odd number of quotation marks")); + exit(EXIT_FAILURE); + } + } while (*cp && *cp != '#' && !is_space(*cp)); + if (is_space(*cp)) + ++cp; + *dp = '\0'; + } + array[nsubs] = NULL; + return array; +} + +static void +time_overflow(void) +{ + error(_("time overflow")); + exit(EXIT_FAILURE); +} + +static zic_t +oadd(zic_t t1, zic_t t2) +{ + if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2) + time_overflow(); + return t1 + t2; +} + +static zic_t +tadd(zic_t t1, zic_t t2) +{ + if (t1 < 0) + { + if (t2 < min_time - t1) + { + if (t1 != min_time) + time_overflow(); + return min_time; + } + } + else + { + if (max_time - t1 < t2) + { + if (t1 != max_time) + time_overflow(); + return max_time; + } + } + return t1 + t2; +} + +/* + * Given a rule, and a year, compute the date (in seconds since January 1, + * 1970, 00:00 LOCAL time) in that year that the rule refers to. + */ + +static zic_t +rpytime(const struct rule *rp, zic_t wantedy) +{ + int m, + i; + zic_t dayoff; /* with a nod to Margaret O. */ + zic_t t, + y; + + if (wantedy == ZIC_MIN) + return min_time; + if (wantedy == ZIC_MAX) + return max_time; + dayoff = 0; + m = TM_JANUARY; + y = EPOCH_YEAR; + if (y < wantedy) + { + wantedy -= y; + dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY); + wantedy %= YEARSPERREPEAT; + wantedy += y; + } + else if (wantedy < 0) + { + dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY); + wantedy %= YEARSPERREPEAT; + } + while (wantedy != y) + { + if (wantedy > y) + { + i = len_years[isleap(y)]; + ++y; + } + else + { + --y; + i = -len_years[isleap(y)]; + } + dayoff = oadd(dayoff, i); + } + while (m != rp->r_month) + { + i = len_months[isleap(y)][m]; + dayoff = oadd(dayoff, i); + ++m; + } + i = rp->r_dayofmonth; + if (m == TM_FEBRUARY && i == 29 && !isleap(y)) + { + if (rp->r_dycode == DC_DOWLEQ) + --i; + else + { + error(_("use of 2/29 in non leap-year")); + exit(EXIT_FAILURE); + } + } + --i; + dayoff = oadd(dayoff, i); + if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) + { + zic_t wday; + +#define LDAYSPERWEEK ((zic_t) DAYSPERWEEK) + wday = EPOCH_WDAY; + + /* + * Don't trust mod of negative numbers. + */ + if (dayoff >= 0) + wday = (wday + dayoff) % LDAYSPERWEEK; + else + { + wday -= ((-dayoff) % LDAYSPERWEEK); + if (wday < 0) + wday += LDAYSPERWEEK; + } + while (wday != rp->r_wday) + if (rp->r_dycode == DC_DOWGEQ) + { + dayoff = oadd(dayoff, 1); + if (++wday >= LDAYSPERWEEK) + wday = 0; + ++i; + } + else + { + dayoff = oadd(dayoff, -1); + if (--wday < 0) + wday = LDAYSPERWEEK - 1; + --i; + } + if (i < 0 || i >= len_months[isleap(y)][m]) + { + if (noise) + warning(_("rule goes past start/end of month; \ +will not work with pre-2004 versions of zic")); + } + } + if (dayoff < min_time / SECSPERDAY) + return min_time; + if (dayoff > max_time / SECSPERDAY) + return max_time; + t = (zic_t) dayoff * SECSPERDAY; + return tadd(t, rp->r_tod); +} + +static void +newabbr(const char *string) +{ + int i; + + if (strcmp(string, GRANDPARENTED) != 0) + { + const char *cp; + const char *mp; + + cp = string; + mp = NULL; + while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9') + || *cp == '-' || *cp == '+') + ++cp; + if (noise && cp - string < 3) + mp = _("time zone abbreviation has fewer than 3 characters"); + if (cp - string > ZIC_MAX_ABBR_LEN_WO_WARN) + mp = _("time zone abbreviation has too many characters"); + if (*cp != '\0') + mp = _("time zone abbreviation differs from POSIX standard"); + if (mp != NULL) + warning("%s (%s)", mp, string); + } + i = strlen(string) + 1; + if (charcnt + i > TZ_MAX_CHARS) + { + error(_("too many, or too long, time zone abbreviations")); + exit(EXIT_FAILURE); + } + strcpy(&chars[charcnt], string); + charcnt += i; +} + +/* Ensure that the directories of ARGNAME exist, by making any missing + ones. If ANCESTORS, do this only for ARGNAME's ancestors; otherwise, + do it for ARGNAME too. Exit with failure if there is trouble. + Do not consider an existing non-directory to be trouble. */ +static void +mkdirs(char const *argname, bool ancestors) +{ + char *name; + char *cp; + + cp = name = ecpyalloc(argname); + + /* + * On MS-Windows systems, do not worry about drive letters or backslashes, + * as this should suffice in practice. Time zone names do not use drive + * letters and backslashes. If the -d option of zic does not name an + * already-existing directory, it can use slashes to separate the + * already-existing ancestor prefix from the to-be-created subdirectories. + */ + + /* Do not mkdir a root directory, as it must exist. */ + while (*cp == '/') + cp++; + + while (cp && ((cp = strchr(cp, '/')) || !ancestors)) + { + if (cp) + *cp = '\0'; + + /* + * Try to create it. It's OK if creation fails because the directory + * already exists, perhaps because some other process just created it. + * For simplicity do not check first whether it already exists, as + * that is checked anyway if the mkdir fails. + */ + if (mkdir(name, MKDIR_UMASK) != 0) + { + /* + * For speed, skip itsdir if errno == EEXIST. Since mkdirs is + * called only after open fails with ENOENT on a subfile, EEXIST + * implies itsdir here. + */ + int err = errno; + + if (err != EEXIST && !itsdir(name)) + { + error(_("%s: Cannot create directory %s: %s"), + progname, name, strerror(err)); + exit(EXIT_FAILURE); + } + } + if (cp) + *cp++ = '/'; + } + free(name); +} + + +#ifdef WIN32 +/* + * To run on win32 + */ +int +link(const char *oldpath, const char *newpath) +{ + if (!CopyFile(oldpath, newpath, false)) + { + _dosmaperr(GetLastError()); + return -1; + } + return 0; +} +#endif |