summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/tools/tzcode
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /intl/icu/source/tools/tzcode
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'intl/icu/source/tools/tzcode')
-rw-r--r--intl/icu/source/tools/tzcode/Makefile.in182
-rw-r--r--intl/icu/source/tools/tzcode/asctime.c132
-rw-r--r--intl/icu/source/tools/tzcode/ialloc.c32
-rw-r--r--intl/icu/source/tools/tzcode/icuregions25
-rw-r--r--intl/icu/source/tools/tzcode/icuzdump.cpp426
-rw-r--r--intl/icu/source/tools/tzcode/icuzdump.vcxproj110
-rw-r--r--intl/icu/source/tools/tzcode/icuzdump.vcxproj.filters22
-rw-r--r--intl/icu/source/tools/tzcode/icuzones96
-rw-r--r--intl/icu/source/tools/tzcode/localtime.c2058
-rw-r--r--intl/icu/source/tools/tzcode/private.h415
-rw-r--r--intl/icu/source/tools/tzcode/readme.txt95
-rw-r--r--intl/icu/source/tools/tzcode/scheck.c64
-rw-r--r--intl/icu/source/tools/tzcode/tz2icu.cpp1881
-rw-r--r--intl/icu/source/tools/tzcode/tz2icu.h46
-rw-r--r--intl/icu/source/tools/tzcode/tzfile.h169
-rw-r--r--intl/icu/source/tools/tzcode/tzselect.ksh308
-rw-r--r--intl/icu/source/tools/tzcode/zdump.c1089
-rw-r--r--intl/icu/source/tools/tzcode/zic.c3156
18 files changed, 10306 insertions, 0 deletions
diff --git a/intl/icu/source/tools/tzcode/Makefile.in b/intl/icu/source/tools/tzcode/Makefile.in
new file mode 100644
index 0000000000..4ba969f42e
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/Makefile.in
@@ -0,0 +1,182 @@
+# Copyright (C) 2016 and later: Unicode, Inc. and others.
+# License & terms of use: http://www.unicode.org/copyright.html
+# Some Portions Copyright (c) 2006-2012 IBM and others. All Rights Reserved.
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+
+top_builddir = ../..
+
+subdir = tools/tzcode
+
+include $(top_builddir)/icudefs.mk
+
+ifeq ($(TZDATA),)
+TZDATA = $(firstword $(wildcard ./tzdata*.tar.gz) $(wildcard $(srcdir)/tzdata*.tar.gz))
+endif
+ifeq ($(TZCODE),)
+TZCODE = $(firstword $(wildcard ./tzcode*.tar.gz) $(wildcard $(srcdir)/tzcode*.tar.gz))
+endif
+
+
+PRIMARY_DATA = africa antarctica asia australasia europe northamerica southamerica
+SUPPLEMENTAL_DATA = etcetera factory backward
+#DEPRECATED_DATA = pacificnew systemv solar87 solar88 solar89
+#TDATA = $(PRIMARY_DATA) $(SUPPLEMENTAL_DATA) $(DEPRECATED_DATA)
+TDATA = $(PRIMARY_DATA) $(SUPPLEMENTAL_DATA)
+
+TZDIR=zoneinfo
+
+CFLAGS+=-D_POSIX_C_SOURCE
+CPPFLAGS+= -DTZDIR=\"$(TZDIR)\"
+
+# more data
+XDATA=zone.tab leapseconds iso3166.tab
+ICUDATA=ZoneMetaData.java icu_zone.txt tz2icu zoneinfo64.txt zoneinfo.txt
+
+VANGUARD_DIR= ./vanguard
+
+# For TZ DB/ICU comparison
+TZORIG=./tzorig
+TZORIG_TZDIR=./tzorig/tzdir
+TZORIG_ABS := $(shell pwd)/tzorig
+TZORIG_TZDIR_ABS := $(TZORIG_ABS)/tzdir
+TZORIG_OPTS := CFLAGS="-D_POSIX_C_SOURCE $(TZORIG_EXTRA_CFLAGS)" TZDIR=$(TZORIG_TZDIR_ABS)
+
+
+## Options for building zdumps
+ZDUMPOUT=$(shell pwd)/zdumpout
+ICUZDUMPOUT=$(shell pwd)/icuzdumpout
+
+ZDUMP_OPTS= -v -a -d $(ZDUMPOUT) -c 1902,2038 -i
+ICUZDUMP_OPTS= -a -d $(ICUZDUMPOUT)
+
+# Executables & objects
+OBJECTS= zic.o localtime.o asctime.o scheck.o ialloc.o
+ZICTARG=$(BINDIR)/zic$(EXEEXT)
+ZICEXEC=$(TOOLBINDIR)/zic$(TOOLEXEEXT)
+TZ2ICUTARG=$(BINDIR)/tz2icu$(EXEEXT)
+TZ2ICUEXEC=$(TOOLBINDIR)/tz2icu$(TOOLEXEEXT)
+ICUZDUMPTARG=$(BINDIR)/icuzdump$(EXEEXT)
+ICUZDUMPEXEC=$(TOOLBINDIR)/icuzdump$(TOOLEXEEXT)
+
+ifeq ($(TZDATA),)
+all:
+ @echo ERROR "tzdata*.tar.gz" can\'t be found.
+ @false
+else
+all: icu_data
+endif
+
+TZCODE_TARGETS= tzorig check-dump
+
+ifeq ($(TZCODE),)
+# we're broken.
+$(TZCODE_TARGETS):
+ @echo ERROR "tzcode*.tar.gz" can\'t be found.
+ @false
+
+else
+ifeq ($(TZDATA),)
+# we're broken.
+$(TZCODE_TARGETS):
+ @echo ERROR "tzdata*.tar.gz" can\'t be found.
+ @false
+else
+tzorig: $(TZCODE) $(TZDATA)
+ -$(RMV) ./tzorig/
+ mkdir $@
+ mkdir $(TZORIG_TZDIR)
+ gunzip -d < $(TZDATA) | ( cd $@ ; tar xf - )
+ gunzip -d < $(TZCODE) | ( cd $@ ; tar xf - )
+ for tzfile in $(TDATA) ; do \
+ mv $(TZORIG)/$$tzfile $(TZORIG)/$$tzfile.bak && \
+ awk -v DATAFORM=rearguard -f $(TZORIG)/ziguard.awk $(TZORIG)/$$tzfile.bak > $(TZORIG)/$$tzfile; \
+ done
+ -mv $(TZORIG)/zdump.c $(TZORIG)/zdump.c.orig
+ cp $(srcdir)/zdump.c $(TZORIG)/zdump.c
+ -mv $(TZORIG)/factory $(TZORIG)/factory.orig
+ cat $(TZORIG)/factory.orig $(srcdir)/icuzones > $(TZORIG)/factory
+ -mv $(TZORIG)/zishrink.awk $(TZORIG)/zishrink.awk.orig
+ sed -e '/if (line ~ \/^R SystemV \/) return/s/^/#/' $(TZORIG)/zishrink.awk.orig > $(TZORIG)/zishrink.awk
+# -mv $(TZORIG)/Makefile $(TZORIG)/Makefile.orig
+# sed -e "s/^BACKWARD=.*/BACKWARD= backward pacificnew/" $(TZORIG)/Makefile.orig > $(TZORIG)/Makefile
+ $(MAKE) -C $@ $(TZORIG_OPTS) zdump zones
+
+$(ZDUMPOUT): tzorig
+ ( cd $(TZORIG) ; ./zdump$(EXEEXT) $(ZDUMP_OPTS) )
+ find $(ZDUMPOUT) -name '*--ICU' -exec sh -c 'mv "$${0}" $${0%--ICU}' {} \;
+
+dump-out: $(ZDUMPOUT) $(ICUZDUMPOUT)
+
+check-dump: dump-out
+ diff -r zdumpout icuzdumpout
+
+endif
+endif
+
+$(ICUZDUMPOUT): $(ICUZDUMPEXEC)
+ -$(RMV) $(ICUZDUMPOUT)
+ -mkdir $(ICUZDUMPOUT)
+ $(INVOKE) $(ICUZDUMPEXEC) $(ICUZDUMP_OPTS)
+
+
+#
+# old 'tz' rules start here
+#
+
+
+$(ZICTARG): $(OBJECTS) $(TDATA) $(srcdir)/tz2icu.h
+ $(CC) $(CFLAGS) $(TZORIG_EXTRA_CFLAGS) $(LFLAGS) -I$(srcdir) $(OBJECTS) $(LDLIBS) -o $@
+
+$(TZ2ICUTARG): $(srcdir)/tz2icu.cpp $(srcdir)/tz2icu.h
+ $(CXX) $(CXXFLAGS) -I$(srcdir) -I$(top_srcdir)/common $(srcdir)/tz2icu.cpp -o $@
+
+$(ICUZDUMPTARG): $(srcdir)/icuzdump.cpp
+ $(LINK.cc) -I$(srcdir) -I$(top_srcdir)/common -I$(top_srcdir)/i18n -I$(top_srcdir)/tools/toolutil -I$(top_srcdir)/io -pedantic $(srcdir)/icuzdump.cpp $(LIBICUUC) $(LIBICUDT) $(LIBICUI18N) $(LIBICUIO) $(LIBICUTOOLUTIL) -o $@
+
+
+$(TDATA): tdatamarker
+
+tdatamarker: $(TZDATA)
+ mkdir $(VANGUARD_DIR)
+ gunzip -d < $(TZDATA) | tar xf - --exclude=Makefile
+ for tzfile in $(TDATA) ; do \
+ mv $$tzfile $(VANGUARD_DIR)/$$tzfile && \
+ awk -v DATAFORM=rearguard -f ziguard.awk $(VANGUARD_DIR)/$$tzfile > $$tzfile; \
+ done
+ touch $@
+
+posix_only: $(ZICEXEC) $(TDATA) $(srcdir)/icuzones
+ $(ZICEXEC) -d $(TZDIR) -L /dev/null $(TDATA) $(srcdir)/icuzones
+
+
+icu_data: $(TZ2ICUEXEC) posix_only
+ $(TZ2ICUEXEC) $(TZDIR) zone.tab `echo $(TZDATA) | sed -e "s/.*\/tzdata//;s/\.tar\.gz$$//"`
+ $(TZ2ICUEXEC) $(TZDIR) zone.tab `echo $(TZDATA) | sed -e "s/.*\/tzdata//;s/\.tar\.gz$$//"` --old
+
+clean:
+ -rm -f core *.o *.out zdump${EXEEXT} $(ZICTARG) date $(TZ2ICUTARG)
+ @echo ICU specific cleanup:
+ -rm -f $(ICUDATA)
+ -rm -rf $(TZDIR)
+ -rm -rf $(VANGUARD_DIR)
+ -$(RMV) $(ICUZDUMPTARG) tzorig ./zdumpout/ ./icuzdumpout/
+ifneq ($(TZDATA),)
+ -rm -rf `gunzip -d < $(TZDATA) | tar tf - --exclude=Makefile | grep -o '[^ ]*$$' | tr '\n' ' '`
+ -rm -f tdatamarker
+endif
+
+checkclean:
+
+dataclean: clean
+ -rm -f $(TDATA) $(XDATA)
+
+distclean: dataclean clean
+ -rm -f Makefile
+
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ cd $(top_builddir) \
+ && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+
diff --git a/intl/icu/source/tools/tzcode/asctime.c b/intl/icu/source/tools/tzcode/asctime.c
new file mode 100644
index 0000000000..152b0db4e5
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/asctime.c
@@ -0,0 +1,132 @@
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** Avoid the temptation to punt entirely to strftime;
+** the output of strftime is supposed to be locale specific
+** whereas the output of asctime is supposed to be constant.
+*/
+
+/*LINTLIBRARY*/
+
+#include "private.h"
+#include "tzfile.h"
+
+/*
+** Some systems only handle "%.2d"; others only handle "%02d";
+** "%02.2d" makes (most) everybody happy.
+** At least some versions of gcc warn about the %02.2d;
+** we conditionalize below to avoid the warning.
+*/
+/*
+** All years associated with 32-bit time_t values are exactly four digits long;
+** some years associated with 64-bit time_t values are not.
+** Vintage programs are coded for years that are always four digits long
+** and may assume that the newline always lands in the same place.
+** For years that are less than four digits, we pad the output with
+** leading zeroes to get the newline in the traditional place.
+** The -4 ensures that we get four characters of output even if
+** we call a strftime variant that produces fewer characters for some years.
+** The ISO C 1999 and POSIX 1003.1-2004 standards prohibit padding the year,
+** but many implementations pad anyway; most likely the standards are buggy.
+*/
+#ifdef __GNUC__
+#define ASCTIME_FMT "%.3s %.3s%3d %2.2d:%2.2d:%2.2d %-4s\n"
+#else /* !defined __GNUC__ */
+#define ASCTIME_FMT "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %-4s\n"
+#endif /* !defined __GNUC__ */
+/*
+** For years that are more than four digits we put extra spaces before the year
+** so that code trying to overwrite the newline won't end up overwriting
+** a digit within a year and truncating the year (operating on the assumption
+** that no output is better than wrong output).
+*/
+#ifdef __GNUC__
+#define ASCTIME_FMT_B "%.3s %.3s%3d %2.2d:%2.2d:%2.2d %s\n"
+#else /* !defined __GNUC__ */
+#define ASCTIME_FMT_B "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %s\n"
+#endif /* !defined __GNUC__ */
+
+#define STD_ASCTIME_BUF_SIZE 26
+/*
+** Big enough for something such as
+** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n
+** (two three-character abbreviations, five strings denoting integers,
+** seven explicit spaces, two explicit colons, a newline,
+** and a trailing ASCII nul).
+** The values above are for systems where an int is 32 bits and are provided
+** as an example; the define below calculates the maximum for the system at
+** hand.
+*/
+#define MAX_ASCTIME_BUF_SIZE (2*3+5*INT_STRLEN_MAXIMUM(int)+7+2+1+1)
+
+static char buf_asctime[MAX_ASCTIME_BUF_SIZE];
+
+/*
+** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition.
+*/
+
+char *
+asctime_r(register const struct tm *timeptr, char *buf)
+{
+ static const char wday_name[][3] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ static const char mon_name[][3] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ register const char * wn;
+ register const char * mn;
+ char year[INT_STRLEN_MAXIMUM(int) + 2];
+ char result[MAX_ASCTIME_BUF_SIZE];
+
+ if (timeptr == NULL) {
+ errno = EINVAL;
+ return strcpy(buf, "??? ??? ?? ??:??:?? ????\n");
+ }
+ if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
+ wn = "???";
+ else wn = wday_name[timeptr->tm_wday];
+ if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
+ mn = "???";
+ else mn = mon_name[timeptr->tm_mon];
+ /*
+ ** Use strftime's %Y to generate the year, to avoid overflow problems
+ ** when computing timeptr->tm_year + TM_YEAR_BASE.
+ ** Assume that strftime is unaffected by other out-of-range members
+ ** (e.g., timeptr->tm_mday) when processing "%Y".
+ */
+ (void) strftime(year, sizeof year, "%Y", timeptr);
+ /*
+ ** We avoid using snprintf since it's not available on all systems.
+ */
+ (void) sprintf(result,
+ ((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B),
+ wn, mn,
+ timeptr->tm_mday, timeptr->tm_hour,
+ timeptr->tm_min, timeptr->tm_sec,
+ year);
+ if (strlen(result) < STD_ASCTIME_BUF_SIZE || buf == buf_asctime)
+ return strcpy(buf, result);
+ else {
+#ifdef EOVERFLOW
+ errno = EOVERFLOW;
+#else /* !defined EOVERFLOW */
+ errno = EINVAL;
+#endif /* !defined EOVERFLOW */
+ return NULL;
+ }
+}
+
+/*
+** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition.
+*/
+
+char *
+asctime(register const struct tm *timeptr)
+{
+ return asctime_r(timeptr, buf_asctime);
+}
diff --git a/intl/icu/source/tools/tzcode/ialloc.c b/intl/icu/source/tools/tzcode/ialloc.c
new file mode 100644
index 0000000000..b6f018897b
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/ialloc.c
@@ -0,0 +1,32 @@
+/*
+** This file is in the public domain, so clarified as of
+** 2006-07-17 by Arthur David Olson.
+*/
+
+/*LINTLIBRARY*/
+
+#include "private.h"
+
+char *
+icatalloc(char *const old, const char *const new)
+{
+ register char * result;
+ register int oldsize, newsize;
+
+ newsize = (new == NULL) ? 0 : strlen(new);
+ if (old == NULL)
+ oldsize = 0;
+ else if (newsize == 0)
+ return old;
+ else oldsize = strlen(old);
+ if ((result = realloc(old, oldsize + newsize + 1)) != NULL)
+ if (new != NULL)
+ (void) strcpy(result + oldsize, new);
+ return result;
+}
+
+char *
+icpyalloc(const char *const string)
+{
+ return icatalloc(NULL, string);
+}
diff --git a/intl/icu/source/tools/tzcode/icuregions b/intl/icu/source/tools/tzcode/icuregions
new file mode 100644
index 0000000000..2cf5d2f701
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/icuregions
@@ -0,0 +1,25 @@
+# Copyright (C) 2016 and later: Unicode, Inc. and others.
+# License & terms of use: http://www.unicode.org/copyright.html
+######################################################################
+# Copyright (C) 2013-2014, International Business Machines
+# Corporation and others. All Rights Reserved.
+######################################################################
+# This is an ICU-specific file including zone/region mapping.
+#
+# Each line below indicates zone and its region in the syntax below -
+# <zone_id> <region_code>
+#
+Africa/Asmera ER
+Africa/Timbuktu ML
+America/Coral_Harbour CA
+America/Montreal CA
+America/Pangnirtung CA
+America/Virgin VI
+Antarctica/South_Pole AQ
+Atlantic/Jan_Mayen SJ
+Europe/Simferopol UA
+Pacific/Johnston UM
+Pacific/Ponape FM
+Pacific/Truk FM
+Pacific/Yap FM
+
diff --git a/intl/icu/source/tools/tzcode/icuzdump.cpp b/intl/icu/source/tools/tzcode/icuzdump.cpp
new file mode 100644
index 0000000000..c82fc43373
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/icuzdump.cpp
@@ -0,0 +1,426 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+*******************************************************************************
+*
+* Copyright (C) 2007-2016, International Business Machines
+* Corporation and others. All Rights Reserved.
+*
+*******************************************************************************
+* file name: icuzdump.cpp
+* encoding: UTF-8
+* tab size: 8 (not used)
+* indentation:4
+*
+* created on: 2007-04-02
+* created by: Yoshito Umaoka
+*
+* This tool write out timezone transitions for ICU timezone. This tool
+* is used as a part of tzdata update process to check if ICU timezone
+* code works as well as the corresponding Olson stock localtime/zdump.
+*/
+
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <iostream>
+
+#include "unicode/utypes.h"
+#include "unicode/ustring.h"
+#include "unicode/timezone.h"
+#include "unicode/simpletz.h"
+#include "unicode/smpdtfmt.h"
+#include "unicode/decimfmt.h"
+#include "unicode/gregocal.h"
+#include "unicode/ustream.h"
+#include "unicode/putil.h"
+
+#include "cmemory.h"
+#include "uoptions.h"
+
+using namespace std;
+using namespace icu;
+
+class DumpFormatter {
+public:
+ DumpFormatter() {
+ UErrorCode status = U_ZERO_ERROR;
+ stz = new SimpleTimeZone(0, "");
+ sdf = new SimpleDateFormat((UnicodeString)"yyyy-MM-dd EEE HH:mm:ss", Locale::getEnglish(), status);
+ DecimalFormatSymbols *symbols = new DecimalFormatSymbols(Locale::getEnglish(), status);
+ decf = new DecimalFormat("00", symbols, status);
+ }
+ ~DumpFormatter() {
+ }
+
+ UnicodeString& format(UDate time, int32_t offset, UBool isDst, UnicodeString& appendTo) {
+ stz->setRawOffset(offset);
+ sdf->setTimeZone(*stz);
+ UnicodeString str = sdf->format(time, appendTo);
+ if (offset < 0) {
+ appendTo += "-";
+ offset = -offset;
+ } else {
+ appendTo += "+";
+ }
+
+ int32_t hour, min, sec;
+
+ offset /= 1000;
+ sec = offset % 60;
+ offset = (offset - sec) / 60;
+ min = offset % 60;
+ hour = offset / 60;
+
+ decf->format(hour, appendTo);
+ decf->format(min, appendTo);
+ decf->format(sec, appendTo);
+ appendTo += "[DST=";
+ if (isDst) {
+ appendTo += "1";
+ } else {
+ appendTo += "0";
+ }
+ appendTo += "]";
+ return appendTo;
+ }
+private:
+ SimpleTimeZone* stz;
+ SimpleDateFormat* sdf;
+ DecimalFormat* decf;
+};
+
+class ICUZDump {
+public:
+ ICUZDump() {
+ formatter = new DumpFormatter();
+ loyear = 1902;
+ hiyear = 2050;
+ tick = 1000;
+ linesep = nullptr;
+ }
+
+ ~ICUZDump() {
+ }
+
+ void setLowYear(int32_t lo) {
+ loyear = lo;
+ }
+
+ void setHighYear(int32_t hi) {
+ hiyear = hi;
+ }
+
+ void setTick(int32_t t) {
+ tick = t;
+ }
+
+ void setTimeZone(TimeZone* tz) {
+ timezone = tz;
+ }
+
+ void setDumpFormatter(DumpFormatter* fmt) {
+ formatter = fmt;
+ }
+
+ void setLineSeparator(const char* sep) {
+ linesep = sep;
+ }
+
+ void dump(ostream& out) {
+ UErrorCode status = U_ZERO_ERROR;
+ UDate SEARCH_INCREMENT = 12 * 60 * 60 * 1000; // half day
+ UDate t, cutlo, cuthi;
+ int32_t rawOffset, dstOffset;
+ UnicodeString str;
+
+ getCutOverTimes(cutlo, cuthi);
+ t = cutlo;
+ timezone->getOffset(t, false, rawOffset, dstOffset, status);
+ while (t < cuthi) {
+ int32_t newRawOffset, newDstOffset;
+ UDate newt = t + SEARCH_INCREMENT;
+
+ timezone->getOffset(newt, false, newRawOffset, newDstOffset, status);
+
+ UBool bSameOffset = (rawOffset + dstOffset) == (newRawOffset + newDstOffset);
+ UBool bSameDst = ((dstOffset != 0) && (newDstOffset != 0)) || ((dstOffset == 0) && (newDstOffset == 0));
+
+ if (!bSameOffset || !bSameDst) {
+ // find the boundary
+ UDate lot = t;
+ UDate hit = newt;
+ while (true) {
+ int32_t diff = (int32_t)(hit - lot);
+ if (diff <= tick) {
+ break;
+ }
+ UDate medt = lot + ((diff / 2) / tick) * tick;
+ int32_t medRawOffset, medDstOffset;
+ timezone->getOffset(medt, false, medRawOffset, medDstOffset, status);
+
+ bSameOffset = (rawOffset + dstOffset) == (medRawOffset + medDstOffset);
+ bSameDst = ((dstOffset != 0) && (medDstOffset != 0)) || ((dstOffset == 0) && (medDstOffset == 0));
+
+ if (!bSameOffset || !bSameDst) {
+ hit = medt;
+ } else {
+ lot = medt;
+ }
+ }
+ // write out the boundary
+ str.remove();
+ formatter->format(lot, rawOffset + dstOffset, (dstOffset == 0 ? false : true), str);
+ out << str << " > ";
+ str.remove();
+ formatter->format(hit, newRawOffset + newDstOffset, (newDstOffset == 0 ? false : true), str);
+ out << str;
+ if (linesep != nullptr) {
+ out << linesep;
+ } else {
+ out << endl;
+ }
+
+ rawOffset = newRawOffset;
+ dstOffset = newDstOffset;
+ }
+ t = newt;
+ }
+ }
+
+private:
+ void getCutOverTimes(UDate& lo, UDate& hi) {
+ UErrorCode status = U_ZERO_ERROR;
+ GregorianCalendar* gcal = new GregorianCalendar(timezone, Locale::getEnglish(), status);
+ gcal->clear();
+ gcal->set(loyear, 0, 1, 0, 0, 0);
+ lo = gcal->getTime(status);
+ gcal->set(hiyear, 0, 1, 0, 0, 0);
+ hi = gcal->getTime(status);
+ }
+
+ TimeZone* timezone;
+ int32_t loyear;
+ int32_t hiyear;
+ int32_t tick;
+
+ DumpFormatter* formatter;
+ const char* linesep;
+};
+
+class ZoneIterator {
+public:
+ ZoneIterator(UBool bAll = false) {
+ if (bAll) {
+ UErrorCode status = U_ZERO_ERROR;
+ zenum = TimeZone::createEnumeration(status);
+ // TODO: Add error case handling later.
+ }
+ else {
+ zenum = nullptr;
+ zids = nullptr;
+ idx = 0;
+ numids = 1;
+ }
+ }
+
+ ZoneIterator(const char** ids, int32_t num) {
+ zenum = nullptr;
+ zids = ids;
+ idx = 0;
+ numids = num;
+ }
+
+ ~ZoneIterator() {
+ if (zenum != nullptr) {
+ delete zenum;
+ }
+ }
+
+ TimeZone* next() {
+ TimeZone* tz = nullptr;
+ if (zenum != nullptr) {
+ UErrorCode status = U_ZERO_ERROR;
+ const UnicodeString* zid = zenum->snext(status);
+ if (zid != nullptr) {
+ tz = TimeZone::createTimeZone(*zid);
+ }
+ }
+ else {
+ if (idx < numids) {
+ if (zids != nullptr) {
+ tz = TimeZone::createTimeZone((const UnicodeString&)zids[idx]);
+ }
+ else {
+ tz = TimeZone::createDefault();
+ }
+ idx++;
+ }
+ }
+ return tz;
+ }
+
+private:
+ const char** zids;
+ StringEnumeration* zenum;
+ int32_t idx;
+ int32_t numids;
+};
+
+enum {
+ kOptHelpH = 0,
+ kOptHelpQuestionMark,
+ kOptAllZones,
+ kOptCutover,
+ kOptDestDir,
+ kOptLineSep
+};
+
+static UOption options[]={
+ UOPTION_HELP_H,
+ UOPTION_HELP_QUESTION_MARK,
+ UOPTION_DEF("allzones", 'a', UOPT_NO_ARG),
+ UOPTION_DEF("cutover", 'c', UOPT_REQUIRES_ARG),
+ UOPTION_DEF("destdir", 'd', UOPT_REQUIRES_ARG),
+ UOPTION_DEF("linesep", 'l', UOPT_REQUIRES_ARG)
+};
+
+extern int
+main(int argc, char *argv[]) {
+ int32_t low = 1902;
+ int32_t high = 2038;
+ UBool bAll = false;
+ const char *dir = nullptr;
+ const char *linesep = nullptr;
+
+ U_MAIN_INIT_ARGS(argc, argv);
+ argc = u_parseArgs(argc, argv, UPRV_LENGTHOF(options), options);
+
+ if (argc < 0) {
+ cerr << "Illegal command line argument(s)" << endl << endl;
+ }
+
+ if (argc < 0 || options[kOptHelpH].doesOccur || options[kOptHelpQuestionMark].doesOccur) {
+ cerr
+ << "Usage: icuzdump [-options] [zoneid1 zoneid2 ...]" << endl
+ << endl
+ << "\tDump all offset transitions for the specified zones." << endl
+ << endl
+ << "Options:" << endl
+ << "\t-a : Dump all available zones." << endl
+ << "\t-d <dir> : When specified, write transitions in a file under" << endl
+ << "\t the directory for each zone." << endl
+ << "\t-l <sep> : New line code type used in file outputs. CR or LF (default)"
+ << "\t or CRLF." << endl
+ << "\t-c [<low_year>,]<high_year>" << endl
+ << "\t : When specified, dump transitions starting <low_year>" << endl
+ << "\t (inclusive) up to <high_year> (exclusive). The default" << endl
+ << "\t values are 1902(low) and 2038(high)." << endl;
+ return argc < 0 ? U_ILLEGAL_ARGUMENT_ERROR : U_ZERO_ERROR;
+ }
+
+ bAll = options[kOptAllZones].doesOccur;
+
+ if (options[kOptDestDir].doesOccur) {
+ dir = options[kOptDestDir].value;
+ }
+
+ if (options[kOptLineSep].doesOccur) {
+ if (strcmp(options[kOptLineSep].value, "CR") == 0) {
+ linesep = "\r";
+ } else if (strcmp(options[kOptLineSep].value, "CRLF") == 0) {
+ linesep = "\r\n";
+ } else if (strcmp(options[kOptLineSep].value, "LF") == 0) {
+ linesep = "\n";
+ }
+ }
+
+ if (options[kOptCutover].doesOccur) {
+ char* comma = (char*)strchr(options[kOptCutover].value, ',');
+ if (comma == nullptr) {
+ high = atoi(options[kOptCutover].value);
+ } else {
+ *comma = 0;
+ low = atoi(options[kOptCutover].value);
+ high = atoi(comma + 1);
+ }
+ }
+
+ ICUZDump dumper;
+ dumper.setLowYear(low);
+ dumper.setHighYear(high);
+ if (dir != nullptr && linesep != nullptr) {
+ // use the specified line separator only for file output
+ dumper.setLineSeparator((const char*)linesep);
+ }
+
+ ZoneIterator* zit;
+ if (bAll) {
+ zit = new ZoneIterator(true);
+ } else {
+ if (argc <= 1) {
+ zit = new ZoneIterator();
+ } else {
+ zit = new ZoneIterator((const char**)&argv[1], argc - 1);
+ }
+ }
+
+ UnicodeString id;
+ if (dir != nullptr) {
+ // file output
+ ostringstream path;
+ ios::openmode mode = ios::out;
+ if (linesep != nullptr) {
+ mode |= ios::binary;
+ }
+ for (;;) {
+ TimeZone* tz = zit->next();
+ if (tz == nullptr) {
+ break;
+ }
+ dumper.setTimeZone(tz);
+ tz->getID(id);
+
+ // target file path
+ path.str("");
+ path << dir << U_FILE_SEP_CHAR;
+ id = id.findAndReplace("/", "-");
+ path << id;
+
+ ofstream* fout = new ofstream(path.str().c_str(), mode);
+ if (fout->fail()) {
+ cerr << "Cannot open file " << path.str() << endl;
+ delete fout;
+ delete tz;
+ break;
+ }
+
+ dumper.dump(*fout);
+ fout->close();
+ delete fout;
+ delete tz;
+ }
+
+ } else {
+ // stdout
+ UBool bFirst = true;
+ for (;;) {
+ TimeZone* tz = zit->next();
+ if (tz == nullptr) {
+ break;
+ }
+ dumper.setTimeZone(tz);
+ tz->getID(id);
+ if (bFirst) {
+ bFirst = false;
+ } else {
+ cout << endl;
+ }
+ cout << "ZONE: " << id << endl;
+ dumper.dump(cout);
+ delete tz;
+ }
+ }
+ delete zit;
+}
diff --git a/intl/icu/source/tools/tzcode/icuzdump.vcxproj b/intl/icu/source/tools/tzcode/icuzdump.vcxproj
new file mode 100644
index 0000000000..4f7b96fc29
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/icuzdump.vcxproj
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{655F4481-B461-4DF0-AF10-0D01114A26C1}</ProjectGuid>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ <RootNamespace>icuzdump</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.40219.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\x86\Debug\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\x86\Debug\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <CustomBuildStep>
+ <Command>copy "$(TargetPath)" ..\..\..\bin
+</Command>
+ <Outputs>..\..\..\bin\$(TargetFileName);%(Outputs)</Outputs>
+ </CustomBuildStep>
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\include;..\toolutil;..\..\common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <DisableLanguageExtensions>true</DisableLanguageExtensions>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <PrecompiledHeaderOutputFile>.\x86\Debug/icuzdump.pch</PrecompiledHeaderOutputFile>
+ <AssemblerListingLocation>.\x86\Debug/</AssemblerListingLocation>
+ <ObjectFileName>.\x86\Debug/</ObjectFileName>
+ <ProgramDataBaseFileName>.\x86\Debug/</ProgramDataBaseFileName>
+ <BrowseInformation>true</BrowseInformation>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <CompileAs>Default</CompileAs>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>icuucd.lib;icuind.lib;icutud.lib;icuiod.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>.\x86\Debug/icuzdump.exe</OutputFile>
+ <AdditionalLibraryDirectories>..\..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>.\x86\Debug/icuzdump.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <DataExecutionPrevention>
+ </DataExecutionPrevention>
+ <TargetMachine>NotSet</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <DataExecutionPrevention>
+ </DataExecutionPrevention>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="icuzdump.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/intl/icu/source/tools/tzcode/icuzdump.vcxproj.filters b/intl/icu/source/tools/tzcode/icuzdump.vcxproj.filters
new file mode 100644
index 0000000000..8004a63a1c
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/icuzdump.vcxproj.filters
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="icuzdump.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/intl/icu/source/tools/tzcode/icuzones b/intl/icu/source/tools/tzcode/icuzones
new file mode 100644
index 0000000000..52f5698cd7
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/icuzones
@@ -0,0 +1,96 @@
+# Copyright (C) 2016 and later: Unicode, Inc. and others.
+# License & terms of use: http://www.unicode.org/copyright.html
+######################################################################
+# Copyright (C) 2007-2014, International Business Machines
+# Corporation and others. All Rights Reserved.
+######################################################################
+# This is an ICU-specific file with the same format as regular
+# tzdata time zone files, for consistent parsing by the tools that
+# turn "Olson" tzdata into ICU's zoneinfo.txt.
+# The purpose of this file is to give ICU a superset of the time zones
+# that are in CLDR and also include legacy ICU time zones originally
+# in tz.alias for rataining backward compatibility.
+
+# Add Etc/Unknown, defined by CLDR. Give it Etc/GMT behavior.
+
+# Zone NAME GMTOFF RULES FORMAT
+Zone Etc/Unknown 0 - Unknown
+
+# SystemV time zones.
+# IANA tzdb file 'systemv' file has these SystemV/* zones commented out up to 2020a.
+# 'systemv' file was removed in 2020b. We keep them in this supplemental zone data
+# file for compatibility purpose.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule SystemV min 1973 - Apr lastSun 2:00 1:00 D
+Rule SystemV min 1973 - Oct lastSun 2:00 0 S
+Rule SystemV 1974 only - Jan 6 2:00 1:00 D
+Rule SystemV 1974 only - Nov lastSun 2:00 0 S
+Rule SystemV 1975 only - Feb 23 2:00 1:00 D
+Rule SystemV 1975 only - Oct lastSun 2:00 0 S
+Rule SystemV 1976 max - Apr lastSun 2:00 1:00 D
+Rule SystemV 1976 max - Oct lastSun 2:00 0 S
+
+# Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
+Zone SystemV/AST4ADT -4:00 SystemV A%sT
+Zone SystemV/EST5EDT -5:00 SystemV E%sT
+Zone SystemV/CST6CDT -6:00 SystemV C%sT
+Zone SystemV/MST7MDT -7:00 SystemV M%sT
+Zone SystemV/PST8PDT -8:00 SystemV P%sT
+Zone SystemV/YST9YDT -9:00 SystemV Y%sT
+Zone SystemV/AST4 -4:00 - AST
+Zone SystemV/EST5 -5:00 - EST
+Zone SystemV/CST6 -6:00 - CST
+Zone SystemV/MST7 -7:00 - MST
+Zone SystemV/PST8 -8:00 - PST
+Zone SystemV/YST9 -9:00 - YST
+Zone SystemV/HST10 -10:00 - HST
+
+# pacificnew
+# IANA tzdb file 'pacificnew' used to contain a Link for US/Pacific-New.
+# 'pacificnew' file was removed in 2020b. We keep the Link here for compatibility.
+Link America/Los_Angeles US/Pacific-New
+
+
+# The list below is for supporting legacy ICU zone aliases.
+# These definitions were originally defined in tz.alias.
+
+#### Aliases that conflict with Olson compatibility Zone definition
+
+Link Australia/Darwin ACT
+Link Australia/Sydney AET
+Link America/Argentina/Buenos_Aires AGT
+Link Africa/Cairo ART
+Link America/Anchorage AST
+Link America/Sao_Paulo BET
+Link Asia/Dhaka BST
+Link Africa/Maputo CAT
+Link America/St_Johns CNT
+Link America/Chicago CST
+Link Asia/Shanghai CTT
+Link Africa/Addis_Ababa EAT
+Link Europe/Paris ECT
+#Link Europe/Istanbul EET # EET is a standard UNIX zone
+####Link EST America/New_York EST # Defined as -05:00
+####Link Pacific/Honolulu HST # Defined as -10:00
+Link America/Indiana/Indianapolis IET
+Link Asia/Kolkata IST
+Link Asia/Tokyo JST
+#Link Asia/Tehran MET # MET is a standard UNIX zone
+Link Pacific/Apia MIT
+####Link America/Denver MST # Defined as -07:00
+Link Asia/Yerevan NET
+Link Pacific/Auckland NST
+Link Asia/Karachi PLT
+Link America/Phoenix PNT
+Link America/Puerto_Rico PRT
+Link America/Los_Angeles PST
+Link Pacific/Guadalcanal SST
+#Link Etc/UTC UTC # Olson LINK
+Link Asia/Ho_Chi_Minh VST
+
+#
+# Aliases already dropped from the TZ database.
+# ICU may also remove these aliases.
+#
+Link America/Regina Canada/East-Saskatchewan # removed from backward in 2017c
diff --git a/intl/icu/source/tools/tzcode/localtime.c b/intl/icu/source/tools/tzcode/localtime.c
new file mode 100644
index 0000000000..8d84a92ddd
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/localtime.c
@@ -0,0 +1,2058 @@
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** Leap second handling from Bradley White.
+** POSIX-style TZ environment variable handling from Guy Harris.
+*/
+
+/*LINTLIBRARY*/
+
+#include <stdbool.h>
+
+#include "private.h"
+#include "tzfile.h"
+#include "fcntl.h"
+
+#ifndef TZ_ABBR_MAX_LEN
+#define TZ_ABBR_MAX_LEN 16
+#endif /* !defined TZ_ABBR_MAX_LEN */
+
+#ifndef TZ_ABBR_CHAR_SET
+#define TZ_ABBR_CHAR_SET \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._"
+#endif /* !defined TZ_ABBR_CHAR_SET */
+
+#ifndef TZ_ABBR_ERR_CHAR
+#define TZ_ABBR_ERR_CHAR '_'
+#endif /* !defined TZ_ABBR_ERR_CHAR */
+
+/*
+** SunOS 4.1.1 headers lack O_BINARY.
+*/
+
+#ifdef O_BINARY
+#define OPEN_MODE (O_RDONLY | O_BINARY)
+#endif /* defined O_BINARY */
+#ifndef O_BINARY
+#define OPEN_MODE O_RDONLY
+#endif /* !defined O_BINARY */
+
+#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 TZ has no rules and we can't load TZDEFRULES.
+** We default to US rules as of 1999-08-17.
+** POSIX 1003.1 section 8.1.1 says that the default DST rules are
+** implementation dependent; for historical reasons, US rules are a
+** common default.
+*/
+#ifndef TZDEFRULESTRING
+#define TZDEFRULESTRING ",M4.1.0,M10.5.0"
+#endif /* !defined TZDEFDST */
+
+struct ttinfo { /* time type information */
+ int_fast32_t tt_gmtoff; /* UT offset in seconds */
+ int tt_isdst; /* used to set tm_isdst */
+ int tt_abbrind; /* abbreviation list index */
+ int tt_ttisstd; /* true if transition is std time */
+ int tt_ttisgmt; /* true if transition is UT */
+};
+
+struct lsinfo { /* leap second information */
+ time_t ls_trans; /* transition time */
+ int_fast64_t ls_corr; /* correction to apply */
+};
+
+#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
+
+#ifdef TZNAME_MAX
+#define MY_TZNAME_MAX TZNAME_MAX
+#endif /* defined TZNAME_MAX */
+#ifndef TZNAME_MAX
+#define MY_TZNAME_MAX 255
+#endif /* !defined TZNAME_MAX */
+
+struct state {
+ int leapcnt;
+ int timecnt;
+ int typecnt;
+ int charcnt;
+ int goback;
+ int goahead;
+ 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, sizeof gmt),
+ (2 * (MY_TZNAME_MAX + 1)))];
+ struct lsinfo lsis[TZ_MAX_LEAPS];
+ int defaulttype; /* for early times or if no transitions */
+};
+
+struct rule {
+ int r_type; /* type of rule--see below */
+ int r_day; /* day number of rule */
+ int r_week; /* week number of rule */
+ int r_mon; /* month number of rule */
+ int_fast32_t r_time; /* transition time of rule */
+};
+
+#define JULIAN_DAY 0 /* Jn - Julian day */
+#define DAY_OF_YEAR 1 /* n - day of year */
+#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */
+
+/*
+** Prototypes for static functions.
+*/
+
+static int_fast32_t detzcode(const char * codep);
+static int_fast64_t detzcode64(const char * codep);
+static int differ_by_repeat(time_t t1, time_t t0);
+static const char * getzname(const char * strp) ATTRIBUTE_PURE;
+static const char * getqzname(const char * strp, const int delim)
+ ATTRIBUTE_PURE;
+static const char * getnum(const char * strp, int * nump, int min,
+ int max);
+static const char * getsecs(const char * strp, int_fast32_t * secsp);
+static const char * getoffset(const char * strp, int_fast32_t * offsetp);
+static const char * getrule(const char * strp, struct rule * rulep);
+static void gmtload(struct state * sp);
+static struct tm * gmtsub(const time_t * timep, int_fast32_t offset,
+ struct tm * tmp);
+static struct tm * localsub(const time_t * timep, int_fast32_t offset,
+ struct tm * tmp);
+static int increment_overflow(int * number, int delta);
+static int leaps_thru_end_of(int y) ATTRIBUTE_PURE;
+static int increment_overflow32(int_fast32_t * number, int delta);
+static int increment_overflow_time(time_t *t, int_fast32_t delta);
+static int normalize_overflow32(int_fast32_t * tensptr,
+ int * unitsptr, int base);
+static int normalize_overflow(int * tensptr, int * unitsptr,
+ int base);
+static void settzname(void);
+static time_t time1(struct tm * tmp,
+ struct tm * (*funcp)(const time_t *,
+ int_fast32_t, struct tm *),
+ int_fast32_t offset);
+static time_t time2(struct tm *tmp,
+ struct tm * (*funcp)(const time_t *,
+ int_fast32_t, struct tm*),
+ int_fast32_t offset, int * okayp);
+static time_t time2sub(struct tm *tmp,
+ struct tm * (*funcp)(const time_t *,
+ int_fast32_t, struct tm*),
+ int_fast32_t offset, int * okayp, int do_norm_secs);
+static struct tm * timesub(const time_t * timep, int_fast32_t offset,
+ const struct state * sp, struct tm * tmp);
+static int tmcomp(const struct tm * atmp,
+ const struct tm * btmp);
+static int_fast32_t transtime(int year, const struct rule * rulep,
+ int_fast32_t offset)
+ ATTRIBUTE_PURE;
+static int typesequiv(const struct state * sp, int a, int b);
+static int tzload(const char * name, struct state * sp,
+ int doextend);
+static int tzparse(const char * name, struct state * sp,
+ int lastditch);
+
+#ifdef ALL_STATE
+static struct state * lclptr;
+static struct state * gmtptr;
+#endif /* defined ALL_STATE */
+
+#ifndef ALL_STATE
+static struct state lclmem;
+static struct state gmtmem;
+#define lclptr (&lclmem)
+#define gmtptr (&gmtmem)
+#endif /* State Farm */
+
+#ifndef TZ_STRLEN_MAX
+#define TZ_STRLEN_MAX 255
+#endif /* !defined TZ_STRLEN_MAX */
+
+static char lcl_TZname[TZ_STRLEN_MAX + 1];
+static int lcl_is_set;
+static int gmt_is_set;
+
+char * tzname[2] = {
+ (char *) wildabbr,
+ (char *) wildabbr
+};
+
+/*
+** 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 tm tm;
+
+#ifdef USG_COMPAT
+long timezone = 0;
+int daylight = 0;
+#endif /* defined USG_COMPAT */
+
+#ifdef ALTZONE
+long altzone = 0;
+#endif /* defined ALTZONE */
+
+static int_fast32_t
+detzcode(const char *const codep)
+{
+ register int_fast32_t result;
+ register int i;
+
+ result = (codep[0] & 0x80) ? -1 : 0;
+ for (i = 0; i < 4; ++i)
+ result = (result << 8) | (codep[i] & 0xff);
+ return result;
+}
+
+static int_fast64_t
+detzcode64(const char *const codep)
+{
+ register int_fast64_t result;
+ register int i;
+
+ result = (codep[0] & 0x80) ? -1 : 0;
+ for (i = 0; i < 8; ++i)
+ result = (result << 8) | (codep[i] & 0xff);
+ return result;
+}
+
+static void
+settzname(void)
+{
+ register struct state * const sp = lclptr;
+ register int i;
+
+ tzname[0] = tzname[1] = (char *) wildabbr;
+#ifdef USG_COMPAT
+ daylight = 0;
+ timezone = 0;
+#endif /* defined USG_COMPAT */
+#ifdef ALTZONE
+ altzone = 0;
+#endif /* defined ALTZONE */
+ if (sp == NULL) {
+ tzname[0] = tzname[1] = (char *) gmt;
+ return;
+ }
+ /*
+ ** And to get the latest zone names into tzname. . .
+ */
+ for (i = 0; i < sp->typecnt; ++i) {
+ register const struct ttinfo * const ttisp = &sp->ttis[i];
+
+ tzname[ttisp->tt_isdst] = &sp->chars[ttisp->tt_abbrind];
+ }
+ for (i = 0; i < sp->timecnt; ++i) {
+ register const struct ttinfo * const ttisp =
+ &sp->ttis[
+ sp->types[i]];
+
+ tzname[ttisp->tt_isdst] =
+ &sp->chars[ttisp->tt_abbrind];
+#ifdef USG_COMPAT
+ if (ttisp->tt_isdst)
+ daylight = 1;
+ if (!ttisp->tt_isdst)
+ timezone = -(ttisp->tt_gmtoff);
+#endif /* defined USG_COMPAT */
+#ifdef ALTZONE
+ if (ttisp->tt_isdst)
+ altzone = -(ttisp->tt_gmtoff);
+#endif /* defined ALTZONE */
+ }
+ /*
+ ** Finally, scrub the abbreviations.
+ ** First, replace bogus characters.
+ */
+ for (i = 0; i < sp->charcnt; ++i)
+ if (strchr(TZ_ABBR_CHAR_SET, sp->chars[i]) == NULL)
+ sp->chars[i] = TZ_ABBR_ERR_CHAR;
+ /*
+ ** Second, truncate long abbreviations.
+ */
+ for (i = 0; i < sp->typecnt; ++i) {
+ register const struct ttinfo * const ttisp = &sp->ttis[i];
+ register char * cp = &sp->chars[ttisp->tt_abbrind];
+
+ if (strlen(cp) > TZ_ABBR_MAX_LEN &&
+ strcmp(cp, GRANDPARENTED) != 0)
+ *(cp + TZ_ABBR_MAX_LEN) = '\0';
+ }
+}
+
+static int
+differ_by_repeat(const time_t t1, const time_t t0)
+{
+ if (TYPE_BIT(time_t) - TYPE_SIGNED(time_t) < SECSPERREPEAT_BITS)
+ return 0;
+ return t1 - t0 == SECSPERREPEAT;
+}
+
+static int
+tzload(register const char *name, register struct state *const sp,
+ register const int doextend)
+{
+ register const char * p;
+ register int i;
+ register int fid;
+ register int stored;
+ register int nread;
+ typedef union {
+ struct tzhead tzhead;
+ char buf[2 * sizeof(struct tzhead) +
+ 2 * sizeof *sp +
+ 4 * TZ_MAX_TIMES];
+ } u_t;
+#ifdef ALL_STATE
+ register u_t * const up = malloc(sizeof *up);
+#else /* !defined ALL_STATE */
+ u_t u;
+ register u_t * const up = &u;
+#endif /* !defined ALL_STATE */
+
+ sp->goback = sp->goahead = false;
+
+ if (up == NULL)
+ return -1;
+
+ if (name == NULL && (name = TZDEFAULT) == NULL)
+ goto oops;
+ {
+ register int doaccess;
+ /*
+ ** Section 4.9.1 of the C standard says that
+ ** "FILENAME_MAX expands to an integral constant expression
+ ** that is the size needed for an array of char large enough
+ ** to hold the longest file name string that the implementation
+ ** guarantees can be opened."
+ */
+ char fullname[FILENAME_MAX + 1];
+
+ if (name[0] == ':')
+ ++name;
+ doaccess = name[0] == '/';
+ if (!doaccess) {
+ if ((p = TZDIR) == NULL)
+ goto oops;
+ if ((strlen(p) + strlen(name) + 1) >= sizeof fullname)
+ goto oops;
+ (void) strcpy(fullname, p);
+ (void) strcat(fullname, "/");
+ (void) strcat(fullname, name);
+ /*
+ ** Set doaccess if '.' (as in "../") shows up in name.
+ */
+ if (strchr(name, '.') != NULL)
+ doaccess = true;
+ name = fullname;
+ }
+ if (doaccess && access(name, R_OK) != 0)
+ goto oops;
+ if ((fid = open(name, OPEN_MODE)) == -1)
+ goto oops;
+ }
+ nread = read(fid, up->buf, sizeof up->buf);
+ if (close(fid) < 0 || nread <= 0)
+ goto oops;
+ for (stored = 4; stored <= 8; stored *= 2) {
+ int ttisstdcnt;
+ int ttisgmtcnt;
+ int timecnt;
+
+ ttisstdcnt = (int) detzcode(up->tzhead.tzh_ttisstdcnt);
+ ttisgmtcnt = (int) detzcode(up->tzhead.tzh_ttisgmtcnt);
+ sp->leapcnt = (int) detzcode(up->tzhead.tzh_leapcnt);
+ sp->timecnt = (int) detzcode(up->tzhead.tzh_timecnt);
+ sp->typecnt = (int) detzcode(up->tzhead.tzh_typecnt);
+ sp->charcnt = (int) detzcode(up->tzhead.tzh_charcnt);
+ p = up->tzhead.tzh_charcnt + sizeof up->tzhead.tzh_charcnt;
+ if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
+ sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
+ sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
+ sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
+ (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
+ (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
+ goto oops;
+ if (nread - (p - up->buf) <
+ sp->timecnt * stored + /* ats */
+ sp->timecnt + /* types */
+ sp->typecnt * 6 + /* ttinfos */
+ sp->charcnt + /* chars */
+ sp->leapcnt * (stored + 4) + /* lsinfos */
+ ttisstdcnt + /* ttisstds */
+ ttisgmtcnt) /* ttisgmts */
+ goto oops;
+ timecnt = 0;
+ for (i = 0; i < sp->timecnt; ++i) {
+ int_fast64_t at
+ = stored == 4 ? detzcode(p) : detzcode64(p);
+ sp->types[i] = ((TYPE_SIGNED(time_t)
+ ? time_t_min <= at
+ : 0 <= at)
+ && at <= time_t_max);
+ if (sp->types[i]) {
+ if (i && !timecnt && at != time_t_min) {
+ /*
+ ** Keep the earlier record, but tweak
+ ** it so that it starts with the
+ ** minimum time_t value.
+ */
+ sp->types[i - 1] = 1;
+ sp->ats[timecnt++] = time_t_min;
+ }
+ sp->ats[timecnt++] = at;
+ }
+ p += stored;
+ }
+ timecnt = 0;
+ for (i = 0; i < sp->timecnt; ++i) {
+ unsigned char typ = *p++;
+ if (sp->typecnt <= typ)
+ goto oops;
+ if (sp->types[i])
+ sp->types[timecnt++] = typ;
+ }
+ sp->timecnt = timecnt;
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ ttisp->tt_gmtoff = detzcode(p);
+ p += 4;
+ ttisp->tt_isdst = (unsigned char) *p++;
+ if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
+ goto oops;
+ ttisp->tt_abbrind = (unsigned char) *p++;
+ if (ttisp->tt_abbrind < 0 ||
+ ttisp->tt_abbrind > sp->charcnt)
+ goto oops;
+ }
+ for (i = 0; i < sp->charcnt; ++i)
+ sp->chars[i] = *p++;
+ sp->chars[i] = '\0'; /* ensure '\0' at end */
+ for (i = 0; i < sp->leapcnt; ++i) {
+ register struct lsinfo * lsisp;
+
+ lsisp = &sp->lsis[i];
+ lsisp->ls_trans = (stored == 4) ?
+ detzcode(p) : detzcode64(p);
+ p += stored;
+ lsisp->ls_corr = detzcode(p);
+ p += 4;
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ if (ttisstdcnt == 0)
+ ttisp->tt_ttisstd = false;
+ else {
+ ttisp->tt_ttisstd = *p++;
+ if (ttisp->tt_ttisstd != true &&
+ ttisp->tt_ttisstd != false)
+ goto oops;
+ }
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ if (ttisgmtcnt == 0)
+ ttisp->tt_ttisgmt = false;
+ else {
+ ttisp->tt_ttisgmt = *p++;
+ if (ttisp->tt_ttisgmt != true &&
+ ttisp->tt_ttisgmt != false)
+ goto oops;
+ }
+ }
+ /*
+ ** If this is an old file, we're done.
+ */
+ if (up->tzhead.tzh_version[0] == '\0')
+ break;
+ nread -= p - up->buf;
+ for (i = 0; i < nread; ++i)
+ up->buf[i] = p[i];
+ /*
+ ** If this is a signed narrow time_t system, we're done.
+ */
+ if (TYPE_SIGNED(time_t) && stored >= (int) sizeof(time_t))
+ break;
+ }
+ if (doextend && nread > 2 &&
+ up->buf[0] == '\n' && up->buf[nread - 1] == '\n' &&
+ sp->typecnt + 2 <= TZ_MAX_TYPES) {
+ struct state ts;
+ register int result;
+
+ up->buf[nread - 1] = '\0';
+ result = tzparse(&up->buf[1], &ts, false);
+ if (result == 0 && ts.typecnt == 2 &&
+ sp->charcnt + ts.charcnt <= TZ_MAX_CHARS) {
+ for (i = 0; i < 2; ++i)
+ ts.ttis[i].tt_abbrind +=
+ sp->charcnt;
+ for (i = 0; i < ts.charcnt; ++i)
+ sp->chars[sp->charcnt++] =
+ ts.chars[i];
+ i = 0;
+ while (i < ts.timecnt &&
+ ts.ats[i] <=
+ sp->ats[sp->timecnt - 1])
+ ++i;
+ while (i < ts.timecnt &&
+ sp->timecnt < TZ_MAX_TIMES) {
+ sp->ats[sp->timecnt] =
+ ts.ats[i];
+ sp->types[sp->timecnt] =
+ sp->typecnt +
+ ts.types[i];
+ ++sp->timecnt;
+ ++i;
+ }
+ sp->ttis[sp->typecnt++] = ts.ttis[0];
+ sp->ttis[sp->typecnt++] = ts.ttis[1];
+ }
+ }
+ 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;
+ }
+ }
+ /*
+ ** If type 0 is unused in transitions,
+ ** it's the type to use for early times.
+ */
+ for (i = 0; i < sp->typecnt; ++i)
+ if (sp->types[i] == 0)
+ break;
+ i = (i >= sp->typecnt) ? 0 : -1;
+ /*
+ ** 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;
+ }
+ /*
+ ** 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;
+ }
+ }
+ sp->defaulttype = i;
+#ifdef ALL_STATE
+ free(up);
+#endif /* defined ALL_STATE */
+ return 0;
+oops:
+#ifdef ALL_STATE
+ free(up);
+#endif /* defined ALL_STATE */
+ return -1;
+}
+
+static int
+typesequiv(const struct state *const sp, const int a, const int b)
+{
+ register int result;
+
+ if (sp == NULL ||
+ a < 0 || a >= sp->typecnt ||
+ b < 0 || b >= sp->typecnt)
+ result = false;
+ else {
+ register const struct ttinfo * ap = &sp->ttis[a];
+ register const struct ttinfo * bp = &sp->ttis[b];
+ result = ap->tt_gmtoff == bp->tt_gmtoff &&
+ ap->tt_isdst == bp->tt_isdst &&
+ ap->tt_ttisstd == bp->tt_ttisstd &&
+ ap->tt_ttisgmt == bp->tt_ttisgmt &&
+ strcmp(&sp->chars[ap->tt_abbrind],
+ &sp->chars[bp->tt_abbrind]) == 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 time zone string, scan until a character that is not
+** a valid character in a zone name is found. Return a pointer to that
+** character.
+*/
+
+static const char *
+getzname(register const char *strp)
+{
+ register char c;
+
+ while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' &&
+ c != '+')
+ ++strp;
+ return strp;
+}
+
+/*
+** Given a pointer into an extended time zone string, scan until the ending
+** delimiter of the zone name 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(register const char *strp, const int delim)
+{
+ register int c;
+
+ while ((c = *strp) != '\0' && c != delim)
+ ++strp;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone 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(register const char *strp, int *const nump, const int min, const int max)
+{
+ register char c;
+ register 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 time zone 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(register const char *strp, int_fast32_t *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 * (int_fast32_t) 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 time zone 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(register const char *strp, int_fast32_t *const offsetp)
+{
+ register int neg = 0;
+
+ if (*strp == '-') {
+ neg = 1;
+ ++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 time zone 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, register 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 int_fast32_t
+transtime(const int year, register const struct rule *const rulep,
+ const int_fast32_t offset)
+{
+ register int leapyear;
+ register int_fast32_t value;
+ register 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[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[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.
+*/
+
+static int
+tzparse(const char *name, register struct state *const sp,
+ const int lastditch)
+{
+ const char * stdname;
+ const char * dstname;
+ size_t stdlen;
+ size_t dstlen;
+ int_fast32_t stdoffset;
+ int_fast32_t dstoffset;
+ register char * cp;
+ register int load_result;
+ static struct ttinfo zttinfo;
+
+ INITIALIZE(dstname);
+ stdname = name;
+ if (lastditch) {
+ stdlen = strlen(name); /* length of standard zone name */
+ name += stdlen;
+ if (stdlen >= sizeof sp->chars)
+ stdlen = (sizeof sp->chars) - 1;
+ stdoffset = 0;
+ } else {
+ if (*name == '<') {
+ name++;
+ stdname = name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return (-1);
+ stdlen = name - stdname;
+ name++;
+ } else {
+ name = getzname(name);
+ stdlen = name - stdname;
+ }
+ if (*name == '\0')
+ return -1;
+ name = getoffset(name, &stdoffset);
+ if (name == NULL)
+ return -1;
+ }
+ load_result = tzload(TZDEFRULES, sp, false);
+ if (load_result != 0)
+ sp->leapcnt = 0; /* so, we're off a little */
+ if (*name != '\0') {
+ if (*name == '<') {
+ dstname = ++name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return -1;
+ dstlen = name - dstname;
+ name++;
+ } else {
+ dstname = name;
+ name = getzname(name);
+ dstlen = name - dstname; /* length of DST zone name */
+ }
+ if (*name != '\0' && *name != ',' && *name != ';') {
+ name = getoffset(name, &dstoffset);
+ if (name == NULL)
+ return -1;
+ } else dstoffset = stdoffset - SECSPERHOUR;
+ if (*name == '\0' && load_result != 0)
+ name = TZDEFRULESTRING;
+ if (*name == ',' || *name == ';') {
+ struct rule start;
+ struct rule end;
+ register int year;
+ register int yearlim;
+ register int timecnt;
+ time_t janfirst;
+
+ ++name;
+ if ((name = getrule(name, &start)) == NULL)
+ return -1;
+ if (*name++ != ',')
+ return -1;
+ if ((name = getrule(name, &end)) == NULL)
+ return -1;
+ if (*name != '\0')
+ return -1;
+ sp->typecnt = 2; /* standard time and DST */
+ /*
+ ** Two transitions per year, from EPOCH_YEAR forward.
+ */
+ sp->ttis[0] = sp->ttis[1] = zttinfo;
+ sp->ttis[0].tt_gmtoff = -dstoffset;
+ sp->ttis[0].tt_isdst = 1;
+ sp->ttis[0].tt_abbrind = stdlen + 1;
+ sp->ttis[1].tt_gmtoff = -stdoffset;
+ sp->ttis[1].tt_isdst = 0;
+ sp->ttis[1].tt_abbrind = 0;
+ sp->defaulttype = 0;
+ timecnt = 0;
+ janfirst = 0;
+ yearlim = EPOCH_YEAR + YEARSPERREPEAT;
+ for (year = EPOCH_YEAR; year < yearlim; year++) {
+ int_fast32_t
+ starttime = transtime(year, &start, stdoffset),
+ endtime = transtime(year, &end, dstoffset);
+ int_fast32_t
+ yearsecs = (year_lengths[isleap(year)]
+ * SECSPERDAY);
+ int reversed = endtime < starttime;
+ if (reversed) {
+ int_fast32_t swap = starttime;
+ starttime = endtime;
+ endtime = swap;
+ }
+ if (reversed
+ || (starttime < endtime
+ && (endtime - starttime
+ < (yearsecs
+ + (stdoffset - dstoffset))))) {
+ if (TZ_MAX_TIMES - 2 < timecnt)
+ break;
+ yearlim = year + YEARSPERREPEAT + 1;
+ sp->ats[timecnt] = janfirst;
+ if (increment_overflow_time
+ (&sp->ats[timecnt], starttime))
+ break;
+ sp->types[timecnt++] = reversed;
+ sp->ats[timecnt] = janfirst;
+ if (increment_overflow_time
+ (&sp->ats[timecnt], endtime))
+ break;
+ sp->types[timecnt++] = !reversed;
+ }
+ if (increment_overflow_time(&janfirst, yearsecs))
+ break;
+ }
+ sp->timecnt = timecnt;
+ if (!timecnt)
+ sp->typecnt = 1; /* Perpetual DST. */
+ } else {
+ register int_fast32_t theirstdoffset;
+ register int_fast32_t theirdstoffset;
+ register int_fast32_t theiroffset;
+ register int isdst;
+ register int i;
+ register int j;
+
+ if (*name != '\0')
+ return -1;
+ /*
+ ** 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_gmtoff;
+ 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_gmtoff;
+ 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_ttisgmt) {
+ /* No adjustment to transition time */
+ } else {
+ /*
+ ** If summer time is in effect, and the
+ ** transition time was not specified as
+ ** standard time, add the summer 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_gmtoff;
+ if (sp->ttis[j].tt_isdst)
+ theirdstoffset = theiroffset;
+ else theirstdoffset = theiroffset;
+ }
+ /*
+ ** Finally, fill in ttis.
+ */
+ sp->ttis[0] = sp->ttis[1] = zttinfo;
+ sp->ttis[0].tt_gmtoff = -stdoffset;
+ sp->ttis[0].tt_isdst = false;
+ sp->ttis[0].tt_abbrind = 0;
+ sp->ttis[1].tt_gmtoff = -dstoffset;
+ sp->ttis[1].tt_isdst = true;
+ sp->ttis[1].tt_abbrind = stdlen + 1;
+ sp->typecnt = 2;
+ sp->defaulttype = 0;
+ }
+ } else {
+ dstlen = 0;
+ sp->typecnt = 1; /* only standard time */
+ sp->timecnt = 0;
+ sp->ttis[0] = zttinfo;
+ sp->ttis[0].tt_gmtoff = -stdoffset;
+ sp->ttis[0].tt_isdst = 0;
+ sp->ttis[0].tt_abbrind = 0;
+ sp->defaulttype = 0;
+ }
+ sp->charcnt = stdlen + 1;
+ if (dstlen != 0)
+ sp->charcnt += dstlen + 1;
+ if ((size_t) sp->charcnt > sizeof sp->chars)
+ return -1;
+ cp = sp->chars;
+ (void) strncpy(cp, stdname, stdlen);
+ cp += stdlen;
+ *cp++ = '\0';
+ if (dstlen != 0) {
+ (void) strncpy(cp, dstname, dstlen);
+ *(cp + dstlen) = '\0';
+ }
+ return 0;
+}
+
+static void
+gmtload(struct state *const sp)
+{
+ if (tzload(gmt, sp, true) != 0)
+ (void) tzparse(gmt, sp, true);
+}
+
+#ifndef STD_INSPIRED
+/*
+** A non-static declaration of tzsetwall in a system header file
+** may cause a warning about this upcoming static declaration...
+*/
+static
+#endif /* !defined STD_INSPIRED */
+void
+tzsetwall(void)
+{
+ if (lcl_is_set < 0)
+ return;
+ lcl_is_set = -1;
+
+#ifdef ALL_STATE
+ if (lclptr == NULL) {
+ lclptr = malloc(sizeof *lclptr);
+ if (lclptr == NULL) {
+ settzname(); /* all we can do */
+ return;
+ }
+ }
+#endif /* defined ALL_STATE */
+ if (tzload(NULL, lclptr, true) != 0)
+ gmtload(lclptr);
+ settzname();
+}
+
+void
+tzset(void)
+{
+ register const char * name;
+
+ name = getenv("TZ");
+ if (name == NULL) {
+ tzsetwall();
+ return;
+ }
+
+ if (lcl_is_set > 0 && strcmp(lcl_TZname, name) == 0)
+ return;
+ lcl_is_set = strlen(name) < sizeof lcl_TZname;
+ if (lcl_is_set)
+ (void) strcpy(lcl_TZname, name);
+
+#ifdef ALL_STATE
+ if (lclptr == NULL) {
+ lclptr = malloc(sizeof *lclptr);
+ if (lclptr == NULL) {
+ settzname(); /* all we can do */
+ return;
+ }
+ }
+#endif /* defined ALL_STATE */
+ if (*name == '\0') {
+ /*
+ ** User wants it fast rather than right.
+ */
+ lclptr->leapcnt = 0; /* so, we're off a little */
+ lclptr->timecnt = 0;
+ lclptr->typecnt = 0;
+ lclptr->ttis[0].tt_isdst = 0;
+ lclptr->ttis[0].tt_gmtoff = 0;
+ lclptr->ttis[0].tt_abbrind = 0;
+ (void) strcpy(lclptr->chars, gmt);
+ } else if (tzload(name, lclptr, true) != 0)
+ if (name[0] == ':' || tzparse(name, lclptr, false) != 0)
+ (void) gmtload(lclptr);
+ settzname();
+}
+
+/*
+** 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.)
+**
+** The unused offset argument is for the benefit of mktime variants.
+*/
+
+/*ARGSUSED*/
+static struct tm *
+localsub(const time_t *const timep, const int_fast32_t offset,
+ struct tm *const tmp)
+{
+ register struct state * sp;
+ register const struct ttinfo * ttisp;
+ register int i;
+ register struct tm * result;
+ const time_t t = *timep;
+
+ sp = lclptr;
+ if (sp == NULL)
+ return gmtsub(timep, offset, tmp);
+ if ((sp->goback && t < sp->ats[0]) ||
+ (sp->goahead && t > sp->ats[sp->timecnt - 1])) {
+ time_t newt = t;
+ register time_t seconds;
+ register 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(&newt, offset, tmp);
+ if (result == tmp) {
+ register time_t newy;
+
+ newy = tmp->tm_year;
+ if (t < sp->ats[0])
+ newy -= years;
+ else newy += years;
+ tmp->tm_year = newy;
+ if (tmp->tm_year != newy)
+ return NULL;
+ }
+ return result;
+ }
+ if (sp->timecnt == 0 || t < sp->ats[0]) {
+ i = sp->defaulttype;
+ } else {
+ register int lo = 1;
+ register int hi = sp->timecnt;
+
+ while (lo < hi) {
+ register 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_gmtoff;
+ ** timesub(&t, 0L, sp, tmp);
+ */
+ result = timesub(&t, ttisp->tt_gmtoff, sp, tmp);
+ tmp->tm_isdst = ttisp->tt_isdst;
+ tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
+#ifdef TM_ZONE
+ tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind];
+#endif /* defined TM_ZONE */
+ return result;
+}
+
+struct tm *
+localtime(const time_t *const timep)
+{
+ tzset();
+ return localsub(timep, 0L, &tm);
+}
+
+/*
+** Re-entrant version of localtime.
+*/
+
+struct tm *
+localtime_r(const time_t *const timep, struct tm *tmp)
+{
+ return localsub(timep, 0L, tmp);
+}
+
+/*
+** gmtsub is to gmtime as localsub is to localtime.
+*/
+
+static struct tm *
+gmtsub(const time_t *const timep, const int_fast32_t offset,
+ struct tm *const tmp)
+{
+ register struct tm * result;
+
+ if (!gmt_is_set) {
+ gmt_is_set = true;
+#ifdef ALL_STATE
+ gmtptr = malloc(sizeof *gmtptr);
+#endif /* defined ALL_STATE */
+ if (gmtptr != NULL)
+ gmtload(gmtptr);
+ }
+ result = timesub(timep, offset, gmtptr, tmp);
+#ifdef TM_ZONE
+ /*
+ ** Could get fancy here and deliver something such as
+ ** "UT+xxxx" or "UT-xxxx" if offset is non-zero,
+ ** but this is no time for a treasure hunt.
+ */
+ tmp->TM_ZONE = offset ? wildabbr : gmtptr ? gmtptr->chars : gmt;
+#endif /* defined TM_ZONE */
+ return result;
+}
+
+struct tm *
+gmtime(const time_t *const timep)
+{
+ return gmtsub(timep, 0L, &tm);
+}
+
+/*
+* Re-entrant version of gmtime.
+*/
+
+struct tm *
+gmtime_r(const time_t *const timep, struct tm *tmp)
+{
+ return gmtsub(timep, 0L, tmp);
+}
+
+#ifdef STD_INSPIRED
+
+struct tm *
+offtime(const time_t *const timep, const long offset)
+{
+ return gmtsub(timep, offset, &tm);
+}
+
+#endif /* defined STD_INSPIRED */
+
+/*
+** 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(register const int y)
+{
+ return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+ -(leaps_thru_end_of(-(y + 1)) + 1);
+}
+
+static struct tm *
+timesub(const time_t *const timep, const int_fast32_t offset,
+ register const struct state *const sp,
+ register struct tm *const tmp)
+{
+ register const struct lsinfo * lp;
+ register time_t tdays;
+ register int idays; /* unsigned would be so 2003 */
+ register int_fast64_t rem;
+ int y;
+ register const int * ip;
+ register int_fast64_t corr;
+ register int hit;
+ register int i;
+
+ corr = 0;
+ hit = 0;
+ i = (sp == NULL) ? 0 : sp->leapcnt;
+ while (--i >= 0) {
+ lp = &sp->lsis[i];
+ if (*timep >= lp->ls_trans) {
+ if (*timep == lp->ls_trans) {
+ hit = ((i == 0 && lp->ls_corr > 0) ||
+ lp->ls_corr > sp->lsis[i - 1].ls_corr);
+ if (hit)
+ while (i > 0 &&
+ sp->lsis[i].ls_trans ==
+ sp->lsis[i - 1].ls_trans + 1 &&
+ sp->lsis[i].ls_corr ==
+ sp->lsis[i - 1].ls_corr + 1) {
+ ++hit;
+ --i;
+ }
+ }
+ corr = lp->ls_corr;
+ break;
+ }
+ }
+ y = EPOCH_YEAR;
+ tdays = *timep / SECSPERDAY;
+ rem = *timep - tdays * SECSPERDAY;
+ while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
+ int newy;
+ register time_t tdelta;
+ register int idelta;
+ register int leapdays;
+
+ tdelta = tdays / DAYSPERLYEAR;
+ if (! ((! TYPE_SIGNED(time_t) || INT_MIN <= tdelta)
+ && tdelta <= INT_MAX))
+ return NULL;
+ idelta = tdelta;
+ if (idelta == 0)
+ idelta = (tdays < 0) ? -1 : 1;
+ newy = y;
+ if (increment_overflow(&newy, idelta))
+ return NULL;
+ leapdays = leaps_thru_end_of(newy - 1) -
+ leaps_thru_end_of(y - 1);
+ tdays -= ((time_t) newy - y) * DAYSPERNYEAR;
+ tdays -= leapdays;
+ y = newy;
+ }
+ {
+ register int_fast32_t seconds;
+
+ seconds = tdays * SECSPERDAY;
+ tdays = seconds / SECSPERDAY;
+ rem += seconds - tdays * SECSPERDAY;
+ }
+ /*
+ ** 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))
+ return NULL;
+ idays += year_lengths[isleap(y)];
+ }
+ while (idays >= year_lengths[isleap(y)]) {
+ idays -= year_lengths[isleap(y)];
+ if (increment_overflow(&y, 1))
+ return NULL;
+ }
+ tmp->tm_year = y;
+ if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
+ return NULL;
+ 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;
+#ifdef TM_GMTOFF
+ tmp->TM_GMTOFF = offset;
+#endif /* defined TM_GMTOFF */
+ return tmp;
+}
+
+char *
+ctime(const time_t *const timep)
+{
+/*
+** Section 4.12.3.2 of X3.159-1989 requires that
+** The ctime function converts the calendar time pointed to by timer
+** to local time in the form of a string. It is equivalent to
+** asctime(localtime(timer))
+*/
+ return asctime(localtime(timep));
+}
+
+char *
+ctime_r(const time_t *const timep, char *buf)
+{
+ struct tm mytm;
+
+ return asctime_r(localtime_r(timep, &mytm), buf);
+}
+
+/*
+** Adapted from code provided by Robert Elz, who writes:
+** The "best" way to do mktime I think is based on an idea of Bob
+** Kridle's (so its said...) from a long time ago.
+** It does a binary search of the time_t space. Since time_t's are
+** just 32 bits, its a max of 32 iterations (even at 64 bits it
+** would still be very reasonable).
+*/
+
+#ifndef WRONG
+#define WRONG (-1)
+#endif /* !defined WRONG */
+
+/*
+** Normalize logic courtesy Paul Eggert.
+*/
+
+static int
+increment_overflow(int *const ip, int j)
+{
+ register 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 int
+increment_overflow32(int_fast32_t *const lp, int const m)
+{
+ register int_fast32_t const l = *lp;
+
+ if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l))
+ return true;
+ *lp += m;
+ return false;
+}
+
+static int
+increment_overflow_time(time_t *tp, int_fast32_t 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(time_t) ? time_t_min - j <= *tp : -1 - j < *tp)
+ : *tp <= time_t_max - j))
+ return true;
+ *tp += j;
+ return false;
+}
+
+static int
+normalize_overflow(int *const tensptr, int *const unitsptr, const int base)
+{
+ register int tensdelta;
+
+ tensdelta = (*unitsptr >= 0) ?
+ (*unitsptr / base) :
+ (-1 - (-1 - *unitsptr) / base);
+ *unitsptr -= tensdelta * base;
+ return increment_overflow(tensptr, tensdelta);
+}
+
+static int
+normalize_overflow32(int_fast32_t *const tensptr, int *const unitsptr,
+ const int base)
+{
+ register int tensdelta;
+
+ tensdelta = (*unitsptr >= 0) ?
+ (*unitsptr / base) :
+ (-1 - (-1 - *unitsptr) / base);
+ *unitsptr -= tensdelta * base;
+ return increment_overflow32(tensptr, tensdelta);
+}
+
+static int
+tmcomp(register const struct tm *const atmp,
+ register const struct tm *const btmp)
+{
+ register int result;
+
+ if (atmp->tm_year != btmp->tm_year)
+ return atmp->tm_year < btmp->tm_year ? -1 : 1;
+ if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 &&
+ (result = (atmp->tm_mday - btmp->tm_mday)) == 0 &&
+ (result = (atmp->tm_hour - btmp->tm_hour)) == 0 &&
+ (result = (atmp->tm_min - btmp->tm_min)) == 0)
+ result = atmp->tm_sec - btmp->tm_sec;
+ return result;
+}
+
+static time_t
+time2sub(struct tm *const tmp,
+ struct tm *(*const funcp)(const time_t *, int_fast32_t, struct tm *),
+ const int_fast32_t offset,
+ int *const okayp,
+ const int do_norm_secs)
+{
+ register const struct state * sp;
+ register int dir;
+ register int i, j;
+ register int saved_seconds;
+ register int_fast32_t li;
+ register time_t lo;
+ register time_t hi;
+ int_fast32_t y;
+ time_t newt;
+ time_t t;
+ struct tm yourtm, mytm;
+
+ *okayp = false;
+ yourtm = *tmp;
+ if (do_norm_secs) {
+ if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
+ SECSPERMIN))
+ return WRONG;
+ }
+ if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
+ return WRONG;
+ if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
+ return WRONG;
+ y = yourtm.tm_year;
+ if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR))
+ return WRONG;
+ /*
+ ** Turn y into an actual year number for now.
+ ** It is converted back to an offset from TM_YEAR_BASE later.
+ */
+ if (increment_overflow32(&y, TM_YEAR_BASE))
+ return WRONG;
+ while (yourtm.tm_mday <= 0) {
+ if (increment_overflow32(&y, -1))
+ return WRONG;
+ li = y + (1 < yourtm.tm_mon);
+ yourtm.tm_mday += year_lengths[isleap(li)];
+ }
+ while (yourtm.tm_mday > DAYSPERLYEAR) {
+ li = y + (1 < yourtm.tm_mon);
+ yourtm.tm_mday -= year_lengths[isleap(li)];
+ if (increment_overflow32(&y, 1))
+ return WRONG;
+ }
+ for ( ; ; ) {
+ i = mon_lengths[isleap(y)][yourtm.tm_mon];
+ if (yourtm.tm_mday <= i)
+ break;
+ yourtm.tm_mday -= i;
+ if (++yourtm.tm_mon >= MONSPERYEAR) {
+ yourtm.tm_mon = 0;
+ if (increment_overflow32(&y, 1))
+ return WRONG;
+ }
+ }
+ if (increment_overflow32(&y, -TM_YEAR_BASE))
+ return WRONG;
+ yourtm.tm_year = y;
+ if (yourtm.tm_year != y)
+ return WRONG;
+ if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN)
+ saved_seconds = 0;
+ else if (y + TM_YEAR_BASE < EPOCH_YEAR) {
+ /*
+ ** We can't set tm_sec to 0, because that might push the
+ ** time below the minimum representable time.
+ ** Set tm_sec to 59 instead.
+ ** This assumes that the minimum representable time is
+ ** not in the same minute that a leap second was deleted from,
+ ** which is a safer assumption than using 58 would be.
+ */
+ if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
+ return WRONG;
+ saved_seconds = yourtm.tm_sec;
+ yourtm.tm_sec = SECSPERMIN - 1;
+ } else {
+ saved_seconds = yourtm.tm_sec;
+ yourtm.tm_sec = 0;
+ }
+ /*
+ ** Do a binary search (this works whatever time_t's type is).
+ */
+ if (!TYPE_SIGNED(time_t)) {
+ lo = 0;
+ hi = lo - 1;
+ } else {
+ lo = 1;
+ for (i = 0; i < (int) TYPE_BIT(time_t) - 1; ++i)
+ lo *= 2;
+ hi = -(lo + 1);
+ }
+ for ( ; ; ) {
+ t = lo / 2 + hi / 2;
+ if (t < lo)
+ t = lo;
+ else if (t > hi)
+ t = hi;
+ if ((*funcp)(&t, offset, &mytm) == NULL) {
+ /*
+ ** Assume that t is too extreme to be represented in
+ ** a struct tm; arrange things so that it is less
+ ** extreme on the next pass.
+ */
+ dir = (t > 0) ? 1 : -1;
+ } else dir = tmcomp(&mytm, &yourtm);
+ if (dir != 0) {
+ if (t == lo) {
+ if (t == time_t_max)
+ return WRONG;
+ ++t;
+ ++lo;
+ } else if (t == hi) {
+ if (t == time_t_min)
+ return WRONG;
+ --t;
+ --hi;
+ }
+ if (lo > hi)
+ return WRONG;
+ if (dir > 0)
+ hi = t;
+ else lo = t;
+ continue;
+ }
+ if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst)
+ break;
+ /*
+ ** Right time, wrong type.
+ ** Hunt for right time, right type.
+ ** It's okay to guess wrong since the guess
+ ** gets checked.
+ */
+ sp = (const struct state *)
+ ((funcp == localsub) ? lclptr : gmtptr);
+ if (sp == NULL)
+ return WRONG;
+ for (i = sp->typecnt - 1; i >= 0; --i) {
+ if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
+ continue;
+ for (j = sp->typecnt - 1; j >= 0; --j) {
+ if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
+ continue;
+ newt = t + sp->ttis[j].tt_gmtoff -
+ sp->ttis[i].tt_gmtoff;
+ if ((*funcp)(&newt, offset, &mytm) == NULL)
+ continue;
+ if (tmcomp(&mytm, &yourtm) != 0)
+ continue;
+ if (mytm.tm_isdst != yourtm.tm_isdst)
+ continue;
+ /*
+ ** We have a match.
+ */
+ t = newt;
+ goto label;
+ }
+ }
+ return WRONG;
+ }
+label:
+ newt = t + saved_seconds;
+ if ((newt < t) != (saved_seconds < 0))
+ return WRONG;
+ t = newt;
+ if ((*funcp)(&t, offset, tmp))
+ *okayp = true;
+ return t;
+}
+
+static time_t
+time2(struct tm * const tmp,
+ struct tm * (*const funcp)(const time_t *, int_fast32_t, struct tm *),
+ const int_fast32_t offset,
+ int *const okayp)
+{
+ time_t t;
+
+ /*
+ ** First try without normalization of seconds
+ ** (in case tm_sec contains a value associated with a leap second).
+ ** If that fails, try with normalization of seconds.
+ */
+ t = time2sub(tmp, funcp, offset, okayp, false);
+ return *okayp ? t : time2sub(tmp, funcp, offset, okayp, true);
+}
+
+static time_t
+time1(struct tm *const tmp,
+ struct tm *(*const funcp) (const time_t *, int_fast32_t, struct tm *),
+ const int_fast32_t offset)
+{
+ register time_t t;
+ register const struct state * sp;
+ register int samei, otheri;
+ register int sameind, otherind;
+ register int i;
+ register int nseen;
+ int seen[TZ_MAX_TYPES];
+ int types[TZ_MAX_TYPES];
+ int okay;
+
+ if (tmp == NULL) {
+ errno = EINVAL;
+ return WRONG;
+ }
+ if (tmp->tm_isdst > 1)
+ tmp->tm_isdst = 1;
+ t = time2(tmp, funcp, offset, &okay);
+ if (okay)
+ return t;
+ if (tmp->tm_isdst < 0)
+#ifdef PCTS
+ /*
+ ** POSIX Conformance Test Suite code courtesy Grant Sullivan.
+ */
+ tmp->tm_isdst = 0; /* reset to std and try again */
+#else
+ return t;
+#endif /* !defined PCTS */
+ /*
+ ** We're supposed to assume that somebody took a time of one type
+ ** and did some math on it that yielded a "struct tm" that's bad.
+ ** We try to divine the type they started from and adjust to the
+ ** type they need.
+ */
+ sp = (const struct state *) ((funcp == localsub) ? lclptr : gmtptr);
+ if (sp == NULL)
+ return WRONG;
+ for (i = 0; i < sp->typecnt; ++i)
+ seen[i] = false;
+ nseen = 0;
+ for (i = sp->timecnt - 1; i >= 0; --i)
+ if (!seen[sp->types[i]]) {
+ seen[sp->types[i]] = true;
+ types[nseen++] = sp->types[i];
+ }
+ for (sameind = 0; sameind < nseen; ++sameind) {
+ samei = types[sameind];
+ if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
+ continue;
+ for (otherind = 0; otherind < nseen; ++otherind) {
+ otheri = types[otherind];
+ if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst)
+ continue;
+ tmp->tm_sec += sp->ttis[otheri].tt_gmtoff -
+ sp->ttis[samei].tt_gmtoff;
+ tmp->tm_isdst = !tmp->tm_isdst;
+ t = time2(tmp, funcp, offset, &okay);
+ if (okay)
+ return t;
+ tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff -
+ sp->ttis[samei].tt_gmtoff;
+ tmp->tm_isdst = !tmp->tm_isdst;
+ }
+ }
+ return WRONG;
+}
+
+time_t
+mktime(struct tm *const tmp)
+{
+ tzset();
+ return time1(tmp, localsub, 0L);
+}
+
+#ifdef STD_INSPIRED
+
+time_t
+timelocal(struct tm *const tmp)
+{
+ if (tmp != NULL)
+ tmp->tm_isdst = -1; /* in case it wasn't initialized */
+ return mktime(tmp);
+}
+
+time_t
+timegm(struct tm *const tmp)
+{
+ if (tmp != NULL)
+ tmp->tm_isdst = 0;
+ return time1(tmp, gmtsub, 0L);
+}
+
+time_t
+timeoff(struct tm *const tmp, const long offset)
+{
+ if (tmp != NULL)
+ tmp->tm_isdst = 0;
+ return time1(tmp, gmtsub, offset);
+}
+
+#endif /* defined STD_INSPIRED */
+
+#ifdef CMUCS
+
+/*
+** The following is supplied for compatibility with
+** previous versions of the CMUCS runtime library.
+*/
+
+long
+gtime(struct tm *const tmp)
+{
+ const time_t t = mktime(tmp);
+
+ if (t == WRONG)
+ return -1;
+ return t;
+}
+
+#endif /* defined CMUCS */
+
+/*
+** XXX--is the below the right way to conditionalize??
+*/
+
+#ifdef STD_INSPIRED
+
+/*
+** IEEE Std 1003.1-1988 (POSIX) legislates that 536457599
+** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which
+** is not the case if we are accounting for leap seconds.
+** So, we provide the following conversion routines for use
+** when exchanging timestamps with POSIX conforming systems.
+*/
+
+static int_fast64_t
+leapcorr(time_t *timep)
+{
+ register struct state * sp;
+ register struct lsinfo * lp;
+ register int i;
+
+ sp = lclptr;
+ i = sp->leapcnt;
+ while (--i >= 0) {
+ lp = &sp->lsis[i];
+ if (*timep >= lp->ls_trans)
+ return lp->ls_corr;
+ }
+ return 0;
+}
+
+time_t
+time2posix(time_t t)
+{
+ tzset();
+ return t - leapcorr(&t);
+}
+
+time_t
+posix2time(time_t t)
+{
+ time_t x;
+ time_t y;
+
+ tzset();
+ /*
+ ** For a positive leap second hit, the result
+ ** is not unique. For a negative leap second
+ ** hit, the corresponding time doesn't exist,
+ ** so we return an adjacent second.
+ */
+ x = t + leapcorr(&t);
+ y = x - leapcorr(&x);
+ if (y < t) {
+ do {
+ x++;
+ y = x - leapcorr(&x);
+ } while (y < t);
+ if (t != y)
+ return x - 1;
+ } else if (y > t) {
+ do {
+ --x;
+ y = x - leapcorr(&x);
+ } while (y > t);
+ if (t != y)
+ return x + 1;
+ }
+ return x;
+}
+
+#endif /* defined STD_INSPIRED */
diff --git a/intl/icu/source/tools/tzcode/private.h b/intl/icu/source/tools/tzcode/private.h
new file mode 100644
index 0000000000..1f35483dc4
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/private.h
@@ -0,0 +1,415 @@
+#ifndef PRIVATE_H
+
+#define PRIVATE_H
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** 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!
+*/
+
+#define GRANDPARENTED "Local time zone must be set--see zic manual page"
+
+/*
+** Defaults for preprocessor symbols.
+** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'.
+*/
+
+#ifndef HAVE_ADJTIME
+#define HAVE_ADJTIME 1
+#endif /* !defined HAVE_ADJTIME */
+
+#ifndef HAVE_GETTEXT
+#define HAVE_GETTEXT 0
+#endif /* !defined HAVE_GETTEXT */
+
+#ifndef HAVE_INCOMPATIBLE_CTIME_R
+#define HAVE_INCOMPATIBLE_CTIME_R 0
+#endif /* !defined INCOMPATIBLE_CTIME_R */
+
+#ifndef HAVE_LINK
+#define HAVE_LINK 1
+#endif /* !defined HAVE_LINK */
+
+#ifndef HAVE_SETTIMEOFDAY
+#define HAVE_SETTIMEOFDAY 3
+#endif /* !defined HAVE_SETTIMEOFDAY */
+
+#ifndef HAVE_SYMLINK
+#define HAVE_SYMLINK 1
+#endif /* !defined HAVE_SYMLINK */
+
+#ifndef HAVE_SYS_STAT_H
+#define HAVE_SYS_STAT_H 1
+#endif /* !defined HAVE_SYS_STAT_H */
+
+#ifndef HAVE_SYS_WAIT_H
+#define HAVE_SYS_WAIT_H 1
+#endif /* !defined HAVE_SYS_WAIT_H */
+
+#ifndef HAVE_UNISTD_H
+#define HAVE_UNISTD_H 1
+#endif /* !defined HAVE_UNISTD_H */
+
+#ifndef HAVE_UTMPX_H
+#define HAVE_UTMPX_H 0
+#endif /* !defined HAVE_UTMPX_H */
+
+#ifndef LOCALE_HOME
+#define LOCALE_HOME "/usr/lib/locale"
+#endif /* !defined LOCALE_HOME */
+
+#if HAVE_INCOMPATIBLE_CTIME_R
+#define asctime_r _incompatible_asctime_r
+#define ctime_r _incompatible_ctime_r
+#endif /* HAVE_INCOMPATIBLE_CTIME_R */
+
+/*
+** Nested includes
+*/
+
+#include "sys/types.h" /* for time_t */
+#include "stdio.h"
+#include "errno.h"
+#include "string.h"
+#include "limits.h" /* for CHAR_BIT et al. */
+#include "time.h"
+#include "stdlib.h"
+
+#if HAVE_GETTEXT
+#include "libintl.h"
+#endif /* HAVE_GETTEXT */
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */
+#endif /* HAVE_SYS_WAIT_H */
+
+#ifndef WIFEXITED
+#define WIFEXITED(status) (((status) & 0xff) == 0)
+#endif /* !defined WIFEXITED */
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(status) (((status) >> 8) & 0xff)
+#endif /* !defined WEXITSTATUS */
+
+#if HAVE_UNISTD_H
+#include "unistd.h" /* for F_OK, R_OK, and other POSIX goodness */
+#endif /* HAVE_UNISTD_H */
+
+#ifndef F_OK
+#define F_OK 0
+#endif /* !defined F_OK */
+#ifndef R_OK
+#define R_OK 4
+#endif /* !defined R_OK */
+
+/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
+#define is_digit(c) ((unsigned)(c) - '0' <= 9)
+
+/*
+** Define HAVE_STDINT_H's default value here, rather than at the
+** start, since __GLIBC__'s value depends on previously-included
+** files.
+** (glibc 2.1 and later have stdint.h, even with pre-C99 compilers.)
+*/
+#ifndef HAVE_STDINT_H
+#define HAVE_STDINT_H \
+ (199901 <= __STDC_VERSION__ || \
+ 2 < (__GLIBC__ + (0 < __GLIBC_MINOR__)))
+#endif /* !defined HAVE_STDINT_H */
+
+#if HAVE_STDINT_H
+#include "stdint.h"
+#endif /* !HAVE_STDINT_H */
+
+#ifndef HAVE_INTTYPES_H
+# define HAVE_INTTYPES_H HAVE_STDINT_H
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+#ifndef INT_FAST64_MAX
+/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */
+#if defined LLONG_MAX || defined __LONG_LONG_MAX__
+typedef long long int_fast64_t;
+# ifdef LLONG_MAX
+# define INT_FAST64_MIN LLONG_MIN
+# define INT_FAST64_MAX LLONG_MAX
+# else
+# define INT_FAST64_MIN __LONG_LONG_MIN__
+# define INT_FAST64_MAX __LONG_LONG_MAX__
+# endif
+# define SCNdFAST64 "lld"
+#else /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */
+#if (LONG_MAX >> 31) < 0xffffffff
+Please use a compiler that supports a 64-bit integer type (or wider);
+you may need to compile with "-DHAVE_STDINT_H".
+#endif /* (LONG_MAX >> 31) < 0xffffffff */
+typedef long int_fast64_t;
+# define INT_FAST64_MIN LONG_MIN
+# define INT_FAST64_MAX LONG_MAX
+# define SCNdFAST64 "ld"
+#endif /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */
+#endif /* !defined INT_FAST64_MAX */
+
+#ifndef INT_FAST32_MAX
+# if INT_MAX >> 31 == 0
+typedef long int_fast32_t;
+# else
+typedef int int_fast32_t;
+# endif
+#endif
+
+#ifndef INTMAX_MAX
+# if defined LLONG_MAX || defined __LONG_LONG_MAX__
+typedef long long intmax_t;
+# define strtoimax strtoll
+# define PRIdMAX "lld"
+# ifdef LLONG_MAX
+# define INTMAX_MAX LLONG_MAX
+# define INTMAX_MIN LLONG_MIN
+# else
+# define INTMAX_MAX __LONG_LONG_MAX__
+# define INTMAX_MIN __LONG_LONG_MIN__
+# endif
+# else
+typedef long intmax_t;
+# define strtoimax strtol
+# define PRIdMAX "ld"
+# define INTMAX_MAX LONG_MAX
+# define INTMAX_MIN LONG_MIN
+# endif
+#endif
+
+#ifndef UINTMAX_MAX
+# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
+typedef unsigned long long uintmax_t;
+# define PRIuMAX "llu"
+# else
+typedef unsigned long uintmax_t;
+# define PRIuMAX "lu"
+# endif
+#endif
+
+#ifndef INT32_MAX
+#define INT32_MAX 0x7fffffff
+#endif /* !defined INT32_MAX */
+#ifndef INT32_MIN
+#define INT32_MIN (-1 - INT32_MAX)
+#endif /* !defined INT32_MIN */
+
+#ifndef SIZE_MAX
+#define SIZE_MAX ((size_t) -1)
+#endif
+
+#if 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define ATTRIBUTE_CONST __attribute__ ((const))
+# define ATTRIBUTE_PURE __attribute__ ((__pure__))
+# define ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
+#else
+# define ATTRIBUTE_CONST /* empty */
+# define ATTRIBUTE_PURE /* empty */
+# define ATTRIBUTE_FORMAT(spec) /* empty */
+#endif
+
+#if !defined _Noreturn && __STDC_VERSION__ < 201112
+# if 2 < __GNUC__ + (8 <= __GNUC_MINOR__)
+# define _Noreturn __attribute__ ((__noreturn__))
+# else
+# define _Noreturn
+# endif
+#endif
+
+#if __STDC_VERSION__ < 199901 && !defined restrict
+# define restrict /* empty */
+#endif
+
+/*
+** Workarounds for compilers/systems.
+*/
+
+/*
+** Some time.h implementations don't declare asctime_r.
+** Others might define it as a macro.
+** Fix the former without affecting the latter.
+*/
+
+#ifndef asctime_r
+extern char * asctime_r(struct tm const *, char *);
+#endif
+
+/*
+** Compile with -Dtime_tz=T to build the tz package with a private
+** time_t type equivalent to T rather than the system-supplied time_t.
+** This debugging feature can test unusual design decisions
+** (e.g., time_t wider than 'long', or unsigned time_t) even on
+** typical platforms.
+*/
+#ifdef time_tz
+static time_t sys_time(time_t *x) { return time(x); }
+
+# undef ctime
+# define ctime tz_ctime
+# undef ctime_r
+# define ctime_r tz_ctime_r
+# undef difftime
+# define difftime tz_difftime
+# undef gmtime
+# define gmtime tz_gmtime
+# undef gmtime_r
+# define gmtime_r tz_gmtime_r
+# undef localtime
+# define localtime tz_localtime
+# undef localtime_r
+# define localtime_r tz_localtime_r
+# undef mktime
+# define mktime tz_mktime
+# undef time
+# define time tz_time
+# undef time_t
+# define time_t tz_time_t
+
+typedef time_tz time_t;
+
+char *ctime(time_t const *);
+char *ctime_r(time_t const *, char *);
+double difftime(time_t, time_t);
+struct tm *gmtime(time_t const *);
+struct tm *gmtime_r(time_t const *restrict, struct tm *restrict);
+struct tm *localtime(time_t const *);
+struct tm *localtime_r(time_t const *restrict, struct tm *restrict);
+time_t mktime(struct tm *);
+
+static time_t
+time(time_t *p)
+{
+ time_t r = sys_time(0);
+ if (p)
+ *p = r;
+ return r;
+}
+#endif
+
+/*
+** Private function declarations.
+*/
+
+char * icatalloc(char * old, const char * new);
+char * icpyalloc(const char * string);
+const char * scheck(const char * string, const char * format);
+
+/*
+** Finally, some convenience items.
+*/
+
+#ifndef TYPE_BIT
+#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
+#endif /* !defined TYPE_BIT */
+
+#ifndef TYPE_SIGNED
+#define TYPE_SIGNED(type) (((type) -1) < 0)
+#endif /* !defined TYPE_SIGNED */
+
+/* The minimum and maximum finite time values. */
+static time_t const time_t_min =
+ (TYPE_SIGNED(time_t)
+ ? (time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1)
+ : 0);
+static time_t const time_t_max =
+ (TYPE_SIGNED(time_t)
+ ? - (~ 0 < 0) - ((time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1))
+ : -1);
+
+#ifndef INT_STRLEN_MAXIMUM
+/*
+** 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))
+#endif /* !defined INT_STRLEN_MAXIMUM */
+
+/*
+** INITIALIZE(x)
+*/
+
+#ifndef GNUC_or_lint
+#ifdef lint
+#define GNUC_or_lint
+#endif /* defined lint */
+#ifndef lint
+#ifdef __GNUC__
+#define GNUC_or_lint
+#endif /* defined __GNUC__ */
+#endif /* !defined lint */
+#endif /* !defined GNUC_or_lint */
+
+#ifndef INITIALIZE
+#ifdef GNUC_or_lint
+#define INITIALIZE(x) ((x) = 0)
+#endif /* defined GNUC_or_lint */
+#ifndef GNUC_or_lint
+#define INITIALIZE(x)
+#endif /* !defined GNUC_or_lint */
+#endif /* !defined INITIALIZE */
+
+/*
+** For the benefit of GNU folk...
+** `_(MSGID)' uses the current locale's message library string for MSGID.
+** The default is to use gettext if available, and use MSGID otherwise.
+*/
+
+#ifndef _
+#if HAVE_GETTEXT
+#define _(msgid) gettext(msgid)
+#else /* !HAVE_GETTEXT */
+#define _(msgid) msgid
+#endif /* !HAVE_GETTEXT */
+#endif /* !defined _ */
+
+#ifndef TZ_DOMAIN
+#define TZ_DOMAIN "tz"
+#endif /* !defined TZ_DOMAIN */
+
+#if HAVE_INCOMPATIBLE_CTIME_R
+#undef asctime_r
+#undef ctime_r
+char *asctime_r(struct tm const *, char *);
+char *ctime_r(time_t const *, char *);
+#endif /* HAVE_INCOMPATIBLE_CTIME_R */
+
+#ifndef YEARSPERREPEAT
+#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */
+#endif /* !defined YEARSPERREPEAT */
+
+/*
+** The Gregorian year averages 365.2425 days, which is 31556952 seconds.
+*/
+
+#ifndef AVGSECSPERYEAR
+#define AVGSECSPERYEAR 31556952L
+#endif /* !defined AVGSECSPERYEAR */
+
+#ifndef SECSPERREPEAT
+#define SECSPERREPEAT ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR)
+#endif /* !defined SECSPERREPEAT */
+
+#ifndef SECSPERREPEAT_BITS
+#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */
+#endif /* !defined SECSPERREPEAT_BITS */
+
+/*
+** UNIX was a registered trademark of The Open Group in 2003.
+*/
+
+#endif /* !defined PRIVATE_H */
diff --git a/intl/icu/source/tools/tzcode/readme.txt b/intl/icu/source/tools/tzcode/readme.txt
new file mode 100644
index 0000000000..9a3cf97703
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/readme.txt
@@ -0,0 +1,95 @@
+* Copyright (C) 2016 and later: Unicode, Inc. and others.
+* License & terms of use: http://www.unicode.org/copyright.html
+**********************************************************************
+* Copyright (c) 2003-2014, International Business Machines
+* Corporation and others. All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: August 18 2003
+* Since: ICU 2.8
+**********************************************************************
+
+Note: this directory currently contains tzcode as of tzcode2014b.tar.gz
+ with localtime.c patches from tzcode2014b.tar.gz
+
+
+----------------------------------------------------------------------
+OVERVIEW
+
+This file describes the tools in icu/source/tools/tzcode
+
+The purpose of these tools is to process the zoneinfo or "Olson" time
+zone database into a form usable by ICU4C (release 2.8 and later).
+Unlike earlier releases, ICU4C 2.8 supports historical time zone
+behavior, as well as the full set of Olson compatibility IDs.
+
+References:
+
+ICU4C: https://icu.unicode.org/
+Olson: ftp://ftp.iana.org/tz/releases/
+
+----------------------------------------------------------------------
+ICU4C vs. ICU4J
+
+For ICU releases >= 2.8, both ICU4C and ICU4J implement full
+historical time zones, based on Olson data. The implementations in C
+and Java are somewhat different. The C implementation is a
+self-contained implementation, whereas ICU4J uses the underlying JDK
+1.3 or 1.4 time zone implementation.
+
+Older versions of ICU (C and Java <= 2.6) implement a "present day
+snapshot". This only reflects current time zone behavior, without
+historical variation. Furthermore, it lacks the full set of Olson
+compatibility IDs.
+
+----------------------------------------------------------------------
+BACKGROUND
+
+The zoneinfo or "Olson" time zone package is used by various systems
+to describe the behavior of time zones. The package consists of
+several parts. E.g.:
+
+ Index of ftp://ftp.iana.org/tz/releases/
+
+ tzcode2014b.tar.gz 172 KB 3/25/2014 05:11:00 AM
+ tzdata2014b.tar.gz 216 KB 3/25/2014 05:11:00 AM
+
+ICU only uses the tzdataYYYYV.tar.gz files,
+where YYYY is the year and V is the version letter ('a'...'z').
+
+This directory has partial contents of tzcode checked into ICU
+
+----------------------------------------------------------------------
+HOWTO
+
+0. Note, these instructions will only work on POSIX type systems.
+
+1. Obtain the current versions of tzdataYYYYV.tar.gz (aka `tzdata') from
+ the FTP site given above. Either manually download or use wget:
+
+ $ cd {path_to}/icu/source/tools/tzcode
+ $ wget "ftp://ftp.iana.org/tz/releases/tzdata*.tar.gz"
+
+2. Copy only one tzdata*.tar.gz file into the icu/source/tools/tzcode/
+ directory (this directory).
+
+ *** Make sure you only have ONE FILE named tzdata*.tar.gz in the
+ directory.
+
+3. Build ICU normally. You will see a notice "updating zoneinfo.txt..."
+
+### Following instructions for ICU maintainers only ###
+
+4. Obtain the current version of tzcodeYYYY.tar.gz from the FTP site to
+ this directory.
+
+5. Run make target "check-dump". This target extract makes the original
+ tzcode and compile the original tzdata with icu supplemental data
+ (icuzones). Then it makes zdump / icuzdump and dump all time
+ transitions for all ICU timezone to files under zdumpout / icuzdumpout
+ directory. When they produce different results, the target returns
+ the error.
+
+6. Don't forget to check in the new zoneinfo64.txt (from its location at
+ {path_to}/icu/source/data/misc/zoneinfo64.txt) into SVN.
+
diff --git a/intl/icu/source/tools/tzcode/scheck.c b/intl/icu/source/tools/tzcode/scheck.c
new file mode 100644
index 0000000000..8bd01a858f
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/scheck.c
@@ -0,0 +1,64 @@
+/*
+** This file is in the public domain, so clarified as of
+** 2006-07-17 by Arthur David Olson.
+*/
+
+/*LINTLIBRARY*/
+
+#include "private.h"
+
+const char *
+scheck(const char *const string, const char *const format)
+{
+ register char * fbuf;
+ register const char * fp;
+ register char * tp;
+ register int c;
+ register const char * result;
+ char dummy;
+
+ result = "";
+ if (string == NULL || format == NULL)
+ return result;
+ fbuf = malloc(2 * strlen(format) + 4);
+ if (fbuf == NULL)
+ return result;
+ fp = format;
+ tp = fbuf;
+
+ /*
+ ** Copy directives, suppressing each conversion that is not
+ ** already suppressed. Scansets containing '%' are not
+ ** supported; e.g., the conversion specification "%[%]" is not
+ ** supported. Also, multibyte characters containing a
+ ** non-leading '%' byte are not supported.
+ */
+ while ((*tp++ = c = *fp++) != '\0') {
+ if (c != '%')
+ continue;
+ if (is_digit(*fp)) {
+ char const *f = fp;
+ char *t = tp;
+ do {
+ *t++ = c = *f++;
+ } while (is_digit(c));
+ if (c == '$') {
+ fp = f;
+ tp = t;
+ }
+ }
+ *tp++ = '*';
+ if (*fp == '*')
+ ++fp;
+ if ((*tp++ = *fp++) == '\0')
+ break;
+ }
+
+ *(tp - 1) = '%';
+ *tp++ = 'c';
+ *tp = '\0';
+ if (sscanf(string, fbuf, &dummy) != 1)
+ result = format;
+ free(fbuf);
+ return result;
+}
diff --git a/intl/icu/source/tools/tzcode/tz2icu.cpp b/intl/icu/source/tools/tzcode/tz2icu.cpp
new file mode 100644
index 0000000000..0adb1bdeb3
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/tz2icu.cpp
@@ -0,0 +1,1881 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+**********************************************************************
+* Copyright (c) 2003-2014, International Business Machines
+* Corporation and others. All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: July 10 2003
+* Since: ICU 2.8
+**********************************************************************
+*/
+#include "tzfile.h" // from Olson tzcode archive, copied to this dir
+
+#ifdef WIN32
+
+ #include <windows.h>
+ #undef min // windows.h/STL conflict
+ #undef max // windows.h/STL conflict
+ // "identifier was truncated to 'number' characters" warning
+ #pragma warning(disable: 4786)
+
+#else
+
+ #include <unistd.h>
+ #include <stdio.h>
+ #include <dirent.h>
+ #include <string.h>
+ #include <sys/stat.h>
+
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <ctime>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <map>
+#include <set>
+#include <sstream>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "tz2icu.h"
+#include "unicode/uversion.h"
+
+using namespace std;
+
+bool ICU44PLUS = true;
+string TZ_RESOURCE_NAME = ICU_TZ_RESOURCE;
+
+//--------------------------------------------------------------------
+// Time utilities
+//--------------------------------------------------------------------
+
+const int64_t SECS_PER_YEAR = 31536000; // 365 days
+const int64_t SECS_PER_LEAP_YEAR = 31622400; // 366 days
+const int64_t LOWEST_TIME32 = (int64_t)((int32_t)0x80000000);
+const int64_t HIGHEST_TIME32 = (int64_t)((int32_t)0x7fffffff);
+
+bool isLeap(int32_t y) {
+ return (y%4 == 0) && ((y%100 != 0) || (y%400 == 0)); // Gregorian
+}
+
+int64_t secsPerYear(int32_t y) {
+ return isLeap(y) ? SECS_PER_LEAP_YEAR : SECS_PER_YEAR;
+}
+
+/**
+ * Given a calendar year, return the GMT epoch seconds for midnight
+ * GMT of January 1 of that year. yearToSeconds(1970) == 0.
+ */
+int64_t yearToSeconds(int32_t year) {
+ // inefficient but foolproof
+ int64_t s = 0;
+ int32_t y = 1970;
+ while (y < year) {
+ s += secsPerYear(y++);
+ }
+ while (y > year) {
+ s -= secsPerYear(--y);
+ }
+ return s;
+}
+
+/**
+ * Given 1970 GMT epoch seconds, return the calendar year containing
+ * that time. secondsToYear(0) == 1970.
+ */
+int32_t secondsToYear(int64_t seconds) {
+ // inefficient but foolproof
+ int32_t y = 1970;
+ int64_t s = 0;
+ if (seconds >= 0) {
+ for (;;) {
+ s += secsPerYear(y++);
+ if (s > seconds) break;
+ }
+ --y;
+ } else {
+ for (;;) {
+ s -= secsPerYear(--y);
+ if (s <= seconds) break;
+ }
+ }
+ return y;
+}
+
+//--------------------------------------------------------------------
+// Types
+//--------------------------------------------------------------------
+
+struct FinalZone;
+struct FinalRule;
+struct SimplifiedZoneType;
+
+// A transition from one ZoneType to another
+// Minimal size = 5 bytes (4+1)
+struct Transition {
+ int64_t time; // seconds, 1970 epoch
+ int32_t type; // index into 'ZoneInfo.types' 0..255
+ Transition(int64_t _time, int32_t _type) {
+ time = _time;
+ type = _type;
+ }
+};
+
+// A behavior mode (what zic calls a 'type') of a time zone.
+// Minimal size = 6 bytes (4+1+3bits)
+// SEE: SimplifiedZoneType
+struct ZoneType {
+ int64_t rawoffset; // raw seconds offset from GMT
+ int64_t dstoffset; // dst seconds offset from GMT
+
+ // We don't really need any of the following, but they are
+ // retained for possible future use. See SimplifiedZoneType.
+ int32_t abbr; // index into ZoneInfo.abbrs 0..n-1
+ bool isdst;
+ bool isstd;
+ bool isgmt;
+
+ ZoneType(const SimplifiedZoneType&); // used by optimizeTypeList
+
+ ZoneType() : rawoffset(-1), dstoffset(-1), abbr(-1) {}
+
+ // A restricted equality, of just the raw and dst offset
+ bool matches(const ZoneType& other) {
+ return rawoffset == other.rawoffset &&
+ dstoffset == other.dstoffset;
+ }
+};
+
+// A collection of transitions from one ZoneType to another, together
+// with a list of the ZoneTypes. A ZoneInfo object may have a long
+// list of transitions between a smaller list of ZoneTypes.
+//
+// This object represents the contents of a single zic-created
+// zoneinfo file.
+struct ZoneInfo {
+ vector<Transition> transitions;
+ vector<ZoneType> types;
+ vector<string> abbrs;
+
+ string finalRuleID;
+ int32_t finalOffset;
+ int32_t finalYear; // -1 if none
+
+ // If this is an alias, then all other fields are meaningless, and
+ // this field will point to the "real" zone 0..n-1.
+ int32_t aliasTo; // -1 if this is a "real" zone
+
+ // If there are aliases TO this zone, then the following set will
+ // contain their index numbers (each index >= 0).
+ set<int32_t> aliases;
+
+ ZoneInfo() : finalYear(-1), aliasTo(-1) {}
+
+ void mergeFinalData(const FinalZone& fz);
+
+ void optimizeTypeList();
+
+ // Set this zone to be an alias TO another zone.
+ void setAliasTo(int32_t index);
+
+ // Clear the list of aliases OF this zone.
+ void clearAliases();
+
+ // Add an alias to the list of aliases OF this zone.
+ void addAlias(int32_t index);
+
+ // Is this an alias to another zone?
+ bool isAlias() const {
+ return aliasTo >= 0;
+ }
+
+ // Retrieve alias list
+ const set<int32_t>& getAliases() const {
+ return aliases;
+ }
+
+ void print(ostream& os, const string& id) const;
+};
+
+void ZoneInfo::clearAliases() {
+ assert(aliasTo < 0);
+ aliases.clear();
+}
+
+void ZoneInfo::addAlias(int32_t index) {
+ assert(aliasTo < 0 && index >= 0 && aliases.find(index) == aliases.end());
+ aliases.insert(index);
+}
+
+void ZoneInfo::setAliasTo(int32_t index) {
+ assert(index >= 0);
+ assert(aliases.size() == 0);
+ aliasTo = index;
+}
+
+typedef map<string, ZoneInfo> ZoneMap;
+
+typedef ZoneMap::const_iterator ZoneMapIter;
+
+//--------------------------------------------------------------------
+// ZONEINFO
+//--------------------------------------------------------------------
+
+// Global map holding all our ZoneInfo objects, indexed by id.
+ZoneMap ZONEINFO;
+
+//--------------------------------------------------------------------
+// zoneinfo file parsing
+//--------------------------------------------------------------------
+
+// Read zic-coded 32-bit integer from file
+int64_t readcoded(ifstream& file, int64_t minv=numeric_limits<int64_t>::min(),
+ int64_t maxv=numeric_limits<int64_t>::max()) {
+ unsigned char buf[4]; // must be UNSIGNED
+ int64_t val=0;
+ file.read((char*)buf, 4);
+ for(int32_t i=0,shift=24;i<4;++i,shift-=8) {
+ val |= buf[i] << shift;
+ }
+ if (val < minv || val > maxv) {
+ ostringstream os;
+ os << "coded value out-of-range: " << val << ", expected ["
+ << minv << ", " << maxv << "]";
+ throw out_of_range(os.str());
+ }
+ return val;
+}
+
+// Read zic-coded 64-bit integer from file
+int64_t readcoded64(ifstream& file, int64_t minv=numeric_limits<int64_t>::min(),
+ int64_t maxv=numeric_limits<int64_t>::max()) {
+ unsigned char buf[8]; // must be UNSIGNED
+ int64_t val=0;
+ file.read((char*)buf, 8);
+ for(int32_t i=0,shift=56;i<8;++i,shift-=8) {
+ val |= (int64_t)buf[i] << shift;
+ }
+ if (val < minv || val > maxv) {
+ ostringstream os;
+ os << "coded value out-of-range: " << val << ", expected ["
+ << minv << ", " << maxv << "]";
+ throw out_of_range(os.str());
+ }
+ return val;
+}
+
+// Read a boolean value
+bool readbool(ifstream& file) {
+ char c;
+ file.read(&c, 1);
+ if (c!=0 && c!=1) {
+ ostringstream os;
+ os << "boolean value out-of-range: " << (int32_t)c;
+ throw out_of_range(os.str());
+ }
+ return (c!=0);
+}
+
+/**
+ * Read the zoneinfo file structure (see tzfile.h) into a ZoneInfo
+ * @param file an already-open file stream
+ */
+void readzoneinfo(ifstream& file, ZoneInfo& info, bool is64bitData) {
+ int32_t i;
+
+ // Check for TZ_ICU_MAGIC signature at file start. If we get a
+ // signature mismatch, it means we're trying to read a file which
+ // isn't a ICU-modified-zic-created zoneinfo file. Typically this
+ // means the user is passing in a "normal" zoneinfo directory, or
+ // a zoneinfo directory that is polluted with other files, or that
+ // the user passed in the wrong directory.
+ char buf[32];
+ file.read(buf, 4);
+ if (strncmp(buf, TZ_ICU_MAGIC, 4) != 0) {
+ throw invalid_argument("TZ_ICU_MAGIC signature missing");
+ }
+ // skip additional Olson byte version
+ file.read(buf, 1);
+ // if '\0', we have just one copy of data, if '2' or '3', there is additional
+ // 64 bit version at the end.
+ if(buf[0]!=0 && buf[0]!='2' && buf[0]!='3') {
+ throw invalid_argument("Bad Olson version info");
+ }
+
+ // Read reserved bytes. The first of these will be a version byte.
+ file.read(buf, 15);
+ if (*(ICUZoneinfoVersion*)&buf != TZ_ICU_VERSION) {
+ throw invalid_argument("File version mismatch");
+ }
+
+ // Read array sizes
+ int64_t isgmtcnt = readcoded(file, 0);
+ int64_t isdstcnt = readcoded(file, 0);
+ int64_t leapcnt = readcoded(file, 0);
+ int64_t timecnt = readcoded(file, 0);
+ int64_t typecnt = readcoded(file, 0);
+ int64_t charcnt = readcoded(file, 0);
+
+ // Confirm sizes that we assume to be equal. These assumptions
+ // are drawn from a reading of the zic source (2003a), so they
+ // should hold unless the zic source changes.
+ if (isgmtcnt != typecnt || isdstcnt != typecnt) {
+ throw invalid_argument("count mismatch between tzh_ttisgmtcnt, tzh_ttisdstcnt, tth_typecnt");
+ }
+
+ // Used temporarily to store transition times and types. We need
+ // to do this because the times and types are stored in two
+ // separate arrays.
+ vector<int64_t> transitionTimes(timecnt, -1); // temporary
+ vector<int32_t> transitionTypes(timecnt, -1); // temporary
+
+ // Read transition times
+ for (i=0; i<timecnt; ++i) {
+ if (is64bitData) {
+ transitionTimes[i] = readcoded64(file);
+ } else {
+ transitionTimes[i] = readcoded(file);
+ }
+ }
+
+ // Read transition types
+ for (i=0; i<timecnt; ++i) {
+ unsigned char c;
+ file.read((char*) &c, 1);
+ int32_t t = (int32_t) c;
+ if (t < 0 || t >= typecnt) {
+ ostringstream os;
+ os << "illegal type: " << t << ", expected [0, " << (typecnt-1) << "]";
+ throw out_of_range(os.str());
+ }
+ transitionTypes[i] = t;
+ }
+
+ // Build transitions vector out of corresponding times and types.
+ bool insertInitial = false;
+ if (is64bitData && !ICU44PLUS) {
+ if (timecnt > 0) {
+ int32_t minidx = -1;
+ for (i=0; i<timecnt; ++i) {
+ if (transitionTimes[i] < LOWEST_TIME32) {
+ if (minidx == -1 || transitionTimes[i] > transitionTimes[minidx]) {
+ // Preserve the latest transition before the 32bit minimum time
+ minidx = i;
+ }
+ } else if (transitionTimes[i] > HIGHEST_TIME32) {
+ // Skipping the rest of the transition data. We cannot put such
+ // transitions into zoneinfo.res, because data is limited to signed
+ // 32bit int by the ICU resource bundle.
+ break;
+ } else {
+ info.transitions.push_back(Transition(transitionTimes[i], transitionTypes[i]));
+ }
+ }
+
+ if (minidx != -1) {
+ // If there are any transitions before the 32bit minimum time,
+ // put the type information with the 32bit minimum time
+ vector<Transition>::iterator itr = info.transitions.begin();
+ info.transitions.insert(itr, Transition(LOWEST_TIME32, transitionTypes[minidx]));
+ } else {
+ // Otherwise, we need insert the initial type later
+ insertInitial = true;
+ }
+ }
+ } else {
+ for (i=0; i<timecnt; ++i) {
+ info.transitions.push_back(Transition(transitionTimes[i], transitionTypes[i]));
+ }
+ }
+
+ // Read types (except for the isdst and isgmt flags, which come later (why??))
+ for (i=0; i<typecnt; ++i) {
+ ZoneType type;
+
+ type.rawoffset = readcoded(file);
+ type.dstoffset = readcoded(file);
+ type.isdst = readbool(file);
+
+ unsigned char c;
+ file.read((char*) &c, 1);
+ type.abbr = (int32_t) c;
+
+ if (type.isdst != (type.dstoffset != 0)) {
+ throw invalid_argument("isdst does not reflect dstoffset");
+ }
+
+ info.types.push_back(type);
+ }
+
+ assert(info.types.size() == (unsigned) typecnt);
+
+ if (insertInitial) {
+ assert(timecnt > 0);
+ assert(typecnt > 0);
+
+ int32_t initialTypeIdx = -1;
+
+ // Check if the first type is not dst
+ if (info.types.at(0).dstoffset != 0) {
+ // Initial type's rawoffset is same with the rawoffset after the
+ // first transition, but no DST is observed.
+ int64_t rawoffset0 = (info.types.at(info.transitions.at(0).type)).rawoffset;
+ // Look for matching type
+ for (i=0; i<(int32_t)info.types.size(); ++i) {
+ if (info.types.at(i).rawoffset == rawoffset0
+ && info.types.at(i).dstoffset == 0) {
+ initialTypeIdx = i;
+ break;
+ }
+ }
+ } else {
+ initialTypeIdx = 0;
+ }
+ assert(initialTypeIdx >= 0);
+ // Add the initial type associated with the lowest int32 time
+ vector<Transition>::iterator itr = info.transitions.begin();
+ info.transitions.insert(itr, Transition(LOWEST_TIME32, initialTypeIdx));
+ }
+
+
+ // Read the abbreviation string
+ if (charcnt) {
+ // All abbreviations are concatenated together, with a 0 at
+ // the end of each abbr.
+ char* str = new char[charcnt + 8];
+ file.read(str, charcnt);
+
+ // Split abbreviations apart into individual strings. Record
+ // offset of each abbr in a vector.
+ vector<int32_t> abbroffset;
+ char *limit=str+charcnt;
+ for (char* p=str; p<limit; ++p) {
+ char* start = p;
+ while (*p != 0) ++p;
+ info.abbrs.push_back(string(start, p-start));
+ abbroffset.push_back(start-str);
+ }
+
+ // Remap all the abbrs. Old value is offset into concatenated
+ // raw abbr strings. New value is index into vector of
+ // strings. E.g., 0,5,10,14 => 0,1,2,3.
+
+ // Keep track of which abbreviations get used.
+ vector<bool> abbrseen(abbroffset.size(), false);
+
+ for (vector<ZoneType>::iterator it=info.types.begin();
+ it!=info.types.end();
+ ++it) {
+ vector<int32_t>::const_iterator x=
+ find(abbroffset.begin(), abbroffset.end(), it->abbr);
+ if (x==abbroffset.end()) {
+ // TODO: Modify code to add a new string to the end of
+ // the abbr list when a middle offset is given, e.g.,
+ // "abc*def*" where * == '\0', take offset of 1 and
+ // make the array "abc", "def", "bc", and translate 1
+ // => 2. NOT CRITICAL since we don't even use the
+ // abbr at this time.
+#if 0
+ // TODO: Re-enable this warning if we start using
+ // the Olson abbr data, or if the above TODO is completed.
+ ostringstream os;
+ os << "Warning: unusual abbr offset " << it->abbr
+ << ", expected one of";
+ for (vector<int32_t>::const_iterator y=abbroffset.begin();
+ y!=abbroffset.end(); ++y) {
+ os << ' ' << *y;
+ }
+ cerr << os.str() << "; using 0" << endl;
+#endif
+ it->abbr = 0;
+ } else {
+ int32_t index = x - abbroffset.begin();
+ it->abbr = index;
+ abbrseen[index] = true;
+ }
+ }
+
+ for (int32_t ii=0;ii<(int32_t) abbrseen.size();++ii) {
+ if (!abbrseen[ii]) {
+ cerr << "Warning: unused abbreviation: " << ii << endl;
+ }
+ }
+ }
+
+ // Read leap second info, if any.
+ // *** We discard leap second data. ***
+ for (i=0; i<leapcnt; ++i) {
+ readcoded(file); // transition time
+ readcoded(file); // total correction after above
+ }
+
+ // Read isstd flags
+ for (i=0; i<typecnt; ++i) info.types[i].isstd = readbool(file);
+
+ // Read isgmt flags
+ for (i=0; i<typecnt; ++i) info.types[i].isgmt = readbool(file);
+}
+
+//--------------------------------------------------------------------
+// Directory and file reading
+//--------------------------------------------------------------------
+
+/**
+ * Process a single zoneinfo file, adding the data to ZONEINFO
+ * @param path the full path to the file, e.g., ".\zoneinfo\America\Los_Angeles"
+ * @param id the zone ID, e.g., "America/Los_Angeles"
+ */
+void handleFile(string path, string id) {
+ // Check for duplicate id
+ if (ZONEINFO.find(id) != ZONEINFO.end()) {
+ ostringstream os;
+ os << "duplicate zone ID: " << id;
+ throw invalid_argument(os.str());
+ }
+
+ ifstream file(path.c_str(), ios::in | ios::binary);
+ if (!file) {
+ throw invalid_argument("can't open file");
+ }
+
+ // eat 32bit data part
+ ZoneInfo info;
+ readzoneinfo(file, info, false);
+
+ // Check for errors
+ if (!file) {
+ throw invalid_argument("read error");
+ }
+
+ // we only use 64bit part
+ ZoneInfo info64;
+ readzoneinfo(file, info64, true);
+
+ bool alldone = false;
+ int64_t eofPos = (int64_t) file.tellg();
+
+ // '\n' + <envvar string> + '\n' after the 64bit version data
+ char ch = file.get();
+ if (ch == 0x0a) {
+ bool invalidchar = false;
+ while (file.get(ch)) {
+ if (ch == 0x0a) {
+ break;
+ }
+ if (ch < 0x20) {
+ // must be printable ascii
+ invalidchar = true;
+ break;
+ }
+ }
+ if (!invalidchar) {
+ eofPos = (int64_t) file.tellg();
+ file.seekg(0, ios::end);
+ eofPos = eofPos - (int64_t) file.tellg();
+ if (eofPos == 0) {
+ alldone = true;
+ }
+ }
+ }
+ if (!alldone) {
+ ostringstream os;
+ os << (-eofPos) << " unprocessed bytes at end";
+ throw invalid_argument(os.str());
+ }
+
+ ZONEINFO[id] = info64;
+}
+
+/**
+ * Recursively scan the given directory, calling handleFile() for each
+ * file in the tree. The user should call with the root directory and
+ * a prefix of "". The function will call itself with non-empty
+ * prefix values.
+ */
+#ifdef WIN32
+
+void scandir(string dirname, string prefix="") {
+ HANDLE hList;
+ WIN32_FIND_DATA FileData;
+
+ // Get the first file
+ hList = FindFirstFile((dirname + "\\*").c_str(), &FileData);
+ if (hList == INVALID_HANDLE_VALUE) {
+ cerr << "Error: Invalid directory: " << dirname << endl;
+ exit(1);
+ }
+ for (;;) {
+ string name(FileData.cFileName);
+ string path(dirname + "\\" + name);
+ if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (name != "." && name != "..") {
+ scandir(path, prefix + name + "/");
+ }
+ } else {
+ try {
+ string id = prefix + name;
+ handleFile(path, id);
+ } catch (const exception& e) {
+ cerr << "Error: While processing \"" << path << "\", "
+ << e.what() << endl;
+ exit(1);
+ }
+ }
+
+ if (!FindNextFile(hList, &FileData)) {
+ if (GetLastError() == ERROR_NO_MORE_FILES) {
+ break;
+ } // else...?
+ }
+ }
+ FindClose(hList);
+}
+
+#else
+
+void scandir(string dir, string prefix="") {
+ DIR *dp;
+ struct dirent *dir_entry;
+ struct stat stat_info;
+ char pwd[512];
+ vector<string> subdirs;
+ vector<string> subfiles;
+
+ if ((dp = opendir(dir.c_str())) == nullptr) {
+ cerr << "Error: Invalid directory: " << dir << endl;
+ exit(1);
+ }
+ if (!getcwd(pwd, sizeof(pwd))) {
+ cerr << "Error: Directory name too long" << endl;
+ exit(1);
+ }
+ chdir(dir.c_str());
+ while ((dir_entry = readdir(dp)) != nullptr) {
+ string name = dir_entry->d_name;
+ string path = dir + "/" + name;
+ lstat(dir_entry->d_name,&stat_info);
+ if (S_ISDIR(stat_info.st_mode)) {
+ if (name != "." && name != "..") {
+ subdirs.push_back(path);
+ subdirs.push_back(prefix + name + "/");
+ // scandir(path, prefix + name + "/");
+ }
+ } else {
+ try {
+ string id = prefix + name;
+ subfiles.push_back(path);
+ subfiles.push_back(id);
+ // handleFile(path, id);
+ } catch (const exception& e) {
+ cerr << "Error: While processing \"" << path << "\", "
+ << e.what() << endl;
+ exit(1);
+ }
+ }
+ }
+ closedir(dp);
+ chdir(pwd);
+
+ for(int32_t i=0;i<(int32_t)subfiles.size();i+=2) {
+ try {
+ handleFile(subfiles[i], subfiles[i+1]);
+ } catch (const exception& e) {
+ cerr << "Error: While processing \"" << subfiles[i] << "\", "
+ << e.what() << endl;
+ exit(1);
+ }
+ }
+ for(int32_t i=0;i<(int32_t)subdirs.size();i+=2) {
+ scandir(subdirs[i], subdirs[i+1]);
+ }
+}
+
+#endif
+
+//--------------------------------------------------------------------
+// Final zone and rule info
+//--------------------------------------------------------------------
+
+/**
+ * Read and discard the current line.
+ */
+void consumeLine(istream& in) {
+ int32_t c;
+ do {
+ c = in.get();
+ } while (c != EOF && c != '\n');
+}
+
+enum {
+ DOM = 0,
+ DOWGEQ = 1,
+ DOWLEQ = 2
+};
+
+const char* TIME_MODE[] = {"w", "s", "u"};
+
+// Allow 29 days in February because zic outputs February 29
+// for rules like "last Sunday in February".
+const int32_t MONTH_LEN[] = {31,29,31,30,31,30,31,31,30,31,30,31};
+
+const int32_t HOUR = 3600;
+
+struct FinalZone {
+ int32_t offset; // raw offset
+ int32_t year; // takes effect for y >= year
+ string ruleid;
+ set<string> aliases;
+ FinalZone(int32_t _offset, int32_t _year, const string& _ruleid) :
+ offset(_offset), year(_year), ruleid(_ruleid) {
+ if (offset <= -16*HOUR || offset >= 16*HOUR) {
+ ostringstream os;
+ os << "Invalid input offset " << offset
+ << " for year " << year
+ << " and rule ID " << ruleid;
+ throw invalid_argument(os.str());
+ }
+ if (year < 1900) {
+ ostringstream os;
+ os << "Invalid input year " << year
+ << " with offset " << offset
+ << " and rule ID " << ruleid;
+ throw invalid_argument(os.str());
+ }
+ }
+ FinalZone() : offset(-1), year(-1) {}
+ void addLink(const string& alias) {
+ if (aliases.find(alias) != aliases.end()) {
+ ostringstream os;
+ os << "Duplicate alias " << alias;
+ throw invalid_argument(os.str());
+ }
+ aliases.insert(alias);
+ }
+};
+
+struct FinalRulePart {
+ int32_t mode;
+ int32_t month;
+ int32_t dom;
+ int32_t dow;
+ int32_t time;
+ int32_t offset; // dst offset, usually either 0 or 1:00
+
+ // Isstd and isgmt only have 3 valid states, corresponding to local
+ // wall time, local standard time, and GMT standard time.
+ // Here is how the isstd & isgmt flags are set by zic:
+ //| case 's': /* Standard */
+ //| rp->r_todisstd = true;
+ //| rp->r_todisgmt = false;
+ //| case 'w': /* Wall */
+ //| rp->r_todisstd = false;
+ //| rp->r_todisgmt = false;
+ //| case 'g': /* Greenwich */
+ //| case 'u': /* Universal */
+ //| case 'z': /* Zulu */
+ //| rp->r_todisstd = true;
+ //| rp->r_todisgmt = true;
+ bool isstd;
+ bool isgmt;
+
+ bool isset; // used during building; later ignored
+
+ FinalRulePart() : isset(false) {}
+ void set(const string& id,
+ const string& _mode,
+ int32_t _month,
+ int32_t _dom,
+ int32_t _dow,
+ int32_t _time,
+ bool _isstd,
+ bool _isgmt,
+ int32_t _offset) {
+ if (isset) {
+ throw invalid_argument("FinalRulePart set twice");
+ }
+ isset = true;
+ if (_mode == "DOWLEQ") {
+ mode = DOWLEQ;
+ } else if (_mode == "DOWGEQ") {
+ mode = DOWGEQ;
+ } else if (_mode == "DOM") {
+ mode = DOM;
+ } else {
+ throw invalid_argument("Unrecognized FinalRulePart mode");
+ }
+ month = _month;
+ dom = _dom;
+ dow = _dow;
+ time = _time;
+ isstd = _isstd;
+ isgmt = _isgmt;
+ offset = _offset;
+
+ ostringstream os;
+ if (month < 0 || month >= 12) {
+ os << "Invalid input month " << month;
+ }
+ if (dom < 1 || dom > MONTH_LEN[month]) {
+ os << "Invalid input day of month " << dom;
+ }
+ if (mode != DOM && (dow < 0 || dow >= 7)) {
+ os << "Invalid input day of week " << dow;
+ }
+ if (offset < (-1 * HOUR) || offset > (2 * HOUR)) {
+ os << "Invalid input offset " << offset;
+ }
+ if (isgmt && !isstd) {
+ os << "Invalid input isgmt && !isstd";
+ }
+ if (!os.str().empty()) {
+ os << " for rule "
+ << id
+ << _mode
+ << month << dom << dow << time
+ << isstd << isgmt
+ << offset;
+ throw invalid_argument(os.str());
+ }
+ }
+
+ /**
+ * Return the time mode as an ICU SimpleTimeZone int from 0..2;
+ * see simpletz.h.
+ */
+ int32_t timemode() const {
+ if (isgmt) {
+ assert(isstd);
+ return 2; // gmt standard
+ }
+ if (isstd) {
+ return 1; // local standard
+ }
+ return 0; // local wall
+ }
+
+ // The SimpleTimeZone encoding method for rules is as follows:
+ // stz_dowim stz_dow
+ // DOM: dom 0
+ // DOWGEQ: dom -(dow+1)
+ // DOWLEQ: -dom -(dow+1)
+ // E.g., to encode Mon>=7, use stz_dowim=7, stz_dow=-2
+ // to encode Mon<=7, use stz_dowim=-7, stz_dow=-2
+ // to encode 7, use stz_dowim=7, stz_dow=0
+ // Note that for this program and for SimpleTimeZone, 0==Jan,
+ // but for this program 0==Sun while for SimpleTimeZone 1==Sun.
+
+ /**
+ * Return a "dowim" param suitable for SimpleTimeZone.
+ */
+ int32_t stz_dowim() const {
+ return (mode == DOWLEQ) ? -dom : dom;
+ }
+
+ /**
+ * Return a "dow" param suitable for SimpleTimeZone.
+ */
+ int32_t stz_dow() const {
+ return (mode == DOM) ? 0 : -(dow+1);
+ }
+};
+
+struct FinalRule {
+ FinalRulePart part[2];
+
+ bool isset() const {
+ return part[0].isset && part[1].isset;
+ }
+
+ void print(ostream& os) const;
+};
+
+map<string,FinalZone> finalZones;
+map<string,FinalRule> finalRules;
+
+map<string, set<string> > links;
+map<string, string> reverseLinks;
+
+/**
+ * Predicate used to find FinalRule objects that do not have both
+ * sub-parts set (indicating an error in the input file).
+ */
+bool isNotSet(const pair<const string,FinalRule>& p) {
+ return !p.second.isset();
+}
+
+/**
+ * Predicate used to find FinalZone objects that do not map to a known
+ * rule (indicating an error in the input file).
+ */
+bool mapsToUnknownRule(const pair<const string,FinalZone>& p) {
+ return finalRules.find(p.second.ruleid) == finalRules.end();
+}
+
+/**
+ * This set is used to make sure each rule in finalRules is used at
+ * least once. First we populate it with all the rules from
+ * finalRules; then we remove all the rules referred to in
+ * finaleZones.
+ */
+set<string> ruleIDset;
+
+void insertRuleID(const pair<string,FinalRule>& p) {
+ ruleIDset.insert(p.first);
+}
+
+void eraseRuleID(const pair<string,FinalZone>& p) {
+ ruleIDset.erase(p.second.ruleid);
+}
+
+/**
+ * Populate finalZones and finalRules from the given istream.
+ */
+void readFinalZonesAndRules(istream& in) {
+
+ for (;;) {
+ string token;
+ in >> token;
+ if (in.eof() || !in) {
+ break;
+ } else if (token == "zone") {
+ // zone Africa/Cairo 7200 1995 Egypt # zone Africa/Cairo, offset 7200, year >= 1995, rule Egypt (0)
+ string id, ruleid;
+ int32_t offset, year;
+ in >> id >> offset >> year >> ruleid;
+ consumeLine(in);
+ finalZones[id] = FinalZone(offset, year, ruleid);
+ } else if (token == "rule") {
+ // rule US DOWGEQ 3 1 0 7200 0 0 3600 # 52: US, file data/northamerica, line 119, mode DOWGEQ, April, dom 1, Sunday, time 7200, isstd 0, isgmt 0, offset 3600
+ // rule US DOWLEQ 9 31 0 7200 0 0 0 # 53: US, file data/northamerica, line 114, mode DOWLEQ, October, dom 31, Sunday, time 7200, isstd 0, isgmt 0, offset 0
+ string id, mode;
+ int32_t month, dom, dow, time, offset;
+ bool isstd, isgmt;
+ in >> id >> mode >> month >> dom >> dow >> time >> isstd >> isgmt >> offset;
+ consumeLine(in);
+ FinalRule& fr = finalRules[id];
+ int32_t p = fr.part[0].isset ? 1 : 0;
+ fr.part[p].set(id, mode, month, dom, dow, time, isstd, isgmt, offset);
+ } else if (token == "link") {
+ string fromid, toid; // fromid == "real" zone, toid == alias
+ in >> fromid >> toid;
+ // DO NOT consumeLine(in);
+ if (finalZones.find(toid) != finalZones.end()) {
+ throw invalid_argument("Bad link: `to' id is a \"real\" zone");
+ }
+
+ links[fromid].insert(toid);
+ reverseLinks[toid] = fromid;
+ } else if (token.length() > 0 && token[0] == '#') {
+ consumeLine(in);
+ } else {
+ throw invalid_argument("Unrecognized keyword");
+ }
+ }
+
+ if (!in.eof() && !in) {
+ throw invalid_argument("Parse failure");
+ }
+
+ // Perform validity check: Each rule should have data for 2 parts.
+ if (count_if(finalRules.begin(), finalRules.end(), isNotSet) != 0) {
+ throw invalid_argument("One or more incomplete rule pairs");
+ }
+
+ // Perform validity check: Each zone should map to a known rule.
+ if (count_if(finalZones.begin(), finalZones.end(), mapsToUnknownRule) != 0) {
+ throw invalid_argument("One or more zones refers to an unknown rule");
+ }
+
+ // Perform validity check: Each rule should be referred to by a zone.
+ ruleIDset.clear();
+ for_each(finalRules.begin(), finalRules.end(), insertRuleID);
+ for_each(finalZones.begin(), finalZones.end(), eraseRuleID);
+ if (ruleIDset.size() != 0) {
+ throw invalid_argument("Unused rules");
+ }
+}
+
+//--------------------------------------------------------------------
+// Resource bundle output
+//--------------------------------------------------------------------
+
+// SEE olsontz.h FOR RESOURCE BUNDLE DATA LAYOUT
+
+void ZoneInfo::print(ostream& os, const string& id) const {
+ // Implement compressed format #2:
+ os << " /* " << id << " */ ";
+
+ if (aliasTo >= 0) {
+ assert(aliases.size() == 0);
+ os << ":int { " << aliasTo << " } "; // No endl - save room for comment.
+ return;
+ }
+
+ if (ICU44PLUS) {
+ os << ":table {" << endl;
+ } else {
+ os << ":array {" << endl;
+ }
+
+ vector<Transition>::const_iterator trn;
+ vector<ZoneType>::const_iterator typ;
+
+ bool first;
+
+ if (ICU44PLUS) {
+ trn = transitions.begin();
+
+ // pre 32bit transitions
+ if (trn != transitions.end() && trn->time < LOWEST_TIME32) {
+ os << " transPre32:intvector { ";
+ for (first = true; trn != transitions.end() && trn->time < LOWEST_TIME32; ++trn) {
+ if (!first) {
+ os<< ", ";
+ }
+ first = false;
+ os << (int32_t)(trn->time >> 32) << ", " << (int32_t)(trn->time & 0x00000000ffffffff);
+ }
+ os << " }" << endl;
+ }
+
+ // 32bit transitions
+ if (trn != transitions.end() && trn->time < HIGHEST_TIME32) {
+ os << " trans:intvector { ";
+ for (first = true; trn != transitions.end() && trn->time < HIGHEST_TIME32; ++trn) {
+ if (!first) {
+ os << ", ";
+ }
+ first = false;
+ os << trn->time;
+ }
+ os << " }" << endl;
+ }
+
+ // post 32bit transitions
+ if (trn != transitions.end()) {
+ os << " transPost32:intvector { ";
+ for (first = true; trn != transitions.end(); ++trn) {
+ if (!first) {
+ os<< ", ";
+ }
+ first = false;
+ os << (int32_t)(trn->time >> 32) << ", " << (int32_t)(trn->time & 0x00000000ffffffff);
+ }
+ os << " }" << endl;
+ }
+ } else {
+ os << " :intvector { ";
+ for (trn = transitions.begin(), first = true; trn != transitions.end(); ++trn) {
+ if (!first) os << ", ";
+ first = false;
+ os << trn->time;
+ }
+ os << " }" << endl;
+ }
+
+
+ first=true;
+ if (ICU44PLUS) {
+ os << " typeOffsets:intvector { ";
+ } else {
+ os << " :intvector { ";
+ }
+ for (typ = types.begin(); typ != types.end(); ++typ) {
+ if (!first) os << ", ";
+ first = false;
+ os << typ->rawoffset << ", " << typ->dstoffset;
+ }
+ os << " }" << endl;
+
+ if (ICU44PLUS) {
+ if (transitions.size() != 0) {
+ os << " typeMap:bin { \"" << hex << setfill('0');
+ for (trn = transitions.begin(); trn != transitions.end(); ++trn) {
+ os << setw(2) << trn->type;
+ }
+ os << dec << "\" }" << endl;
+ }
+ } else {
+ os << " :bin { \"" << hex << setfill('0');
+ for (trn = transitions.begin(); trn != transitions.end(); ++trn) {
+ os << setw(2) << trn->type;
+ }
+ os << dec << "\" }" << endl;
+ }
+
+ // Final zone info, if any
+ if (finalYear != -1) {
+ if (ICU44PLUS) {
+ os << " finalRule { \"" << finalRuleID << "\" }" << endl;
+ os << " finalRaw:int { " << finalOffset << " }" << endl;
+ os << " finalYear:int { " << finalYear << " }" << endl;
+ } else {
+ os << " \"" << finalRuleID << "\"" << endl;
+ os << " :intvector { " << finalOffset << ", "
+ << finalYear << " }" << endl;
+ }
+ }
+
+ // Alias list, if any
+ if (aliases.size() != 0) {
+ first = true;
+ if (ICU44PLUS) {
+ os << " links:intvector { ";
+ } else {
+ os << " :intvector { ";
+ }
+ for (set<int32_t>::const_iterator i=aliases.begin(); i!=aliases.end(); ++i) {
+ if (!first) os << ", ";
+ first = false;
+ os << *i;
+ }
+ os << " }" << endl;
+ }
+
+ os << " } "; // no trailing 'endl', so comments can be placed.
+}
+
+inline ostream&
+operator<<(ostream& os, const ZoneMap& zoneinfo) {
+ int32_t c = 0;
+ for (ZoneMapIter it = zoneinfo.begin();
+ it != zoneinfo.end();
+ ++it) {
+ if(c && !ICU44PLUS) os << ",";
+ it->second.print(os, it->first);
+ os << "//Z#" << c++ << endl;
+ }
+ return os;
+}
+
+// print the string list
+ostream& printStringList( ostream& os, const ZoneMap& zoneinfo) {
+ int32_t n = 0; // count
+ int32_t col = 0; // column
+ os << " Names {" << endl
+ << " ";
+ for (ZoneMapIter it = zoneinfo.begin();
+ it != zoneinfo.end();
+ ++it) {
+ if(n) {
+ os << ",";
+ col ++;
+ }
+ const string& id = it->first;
+ os << "\"" << id << "\"";
+ col += id.length() + 2;
+ if(col >= 50) {
+ os << " // " << n << endl
+ << " ";
+ col = 0;
+ }
+ n++;
+ }
+ os << " // " << (n-1) << endl
+ << " }" << endl;
+
+ return os;
+}
+
+//--------------------------------------------------------------------
+// main
+//--------------------------------------------------------------------
+
+// Unary predicate for finding transitions after a given time
+bool isAfter(const Transition t, int64_t thresh) {
+ return t.time >= thresh;
+}
+
+/**
+ * A zone type that contains only the raw and dst offset. Used by the
+ * optimizeTypeList() method.
+ */
+struct SimplifiedZoneType {
+ int64_t rawoffset;
+ int64_t dstoffset;
+ SimplifiedZoneType() : rawoffset(-1), dstoffset(-1) {}
+ SimplifiedZoneType(const ZoneType& t) : rawoffset(t.rawoffset),
+ dstoffset(t.dstoffset) {}
+ bool operator<(const SimplifiedZoneType& t) const {
+ return rawoffset < t.rawoffset ||
+ (rawoffset == t.rawoffset &&
+ dstoffset < t.dstoffset);
+ }
+};
+
+/**
+ * Construct a ZoneType from a SimplifiedZoneType. Note that this
+ * discards information; the new ZoneType will have meaningless
+ * (empty) abbr, isdst, isstd, and isgmt flags; this is appropriate,
+ * since ignoring these is how we do optimization (we have no use for
+ * these in historical transitions).
+ */
+ZoneType::ZoneType(const SimplifiedZoneType& t) :
+ rawoffset(t.rawoffset), dstoffset(t.dstoffset),
+ abbr(-1), isdst(false), isstd(false), isgmt(false) {}
+
+/**
+ * Optimize the type list to remove excess entries. The type list may
+ * contain entries that are distinct only in terms of their dst, std,
+ * or gmt flags. Since we don't care about those flags, we can reduce
+ * the type list to a set of unique raw/dst offset pairs, and remap
+ * the type indices in the transition list, which stores, for each
+ * transition, a transition time and a type index.
+ */
+void ZoneInfo::optimizeTypeList() {
+ // Assemble set of unique types; only those in the `transitions'
+ // list, since there may be unused types in the `types' list
+ // corresponding to transitions that have been trimmed (during
+ // merging of final data).
+
+ if (aliasTo >= 0) return; // Nothing to do for aliases
+
+ if (!ICU44PLUS) {
+ // This is the old logic which has a bug, which occasionally removes
+ // the type before the first transition. The problem was fixed
+ // by inserting the dummy transition indirectly.
+
+ // If there are zero transitions and one type, then leave that as-is.
+ if (transitions.size() == 0) {
+ if (types.size() != 1) {
+ cerr << "Error: transition count = 0, type count = " << types.size() << endl;
+ }
+ return;
+ }
+
+ set<SimplifiedZoneType> simpleset;
+ for (vector<Transition>::const_iterator i=transitions.begin();
+ i!=transitions.end(); ++i) {
+ assert(i->type < (int32_t)types.size());
+ simpleset.insert(types[i->type]);
+ }
+
+ // Map types to integer indices
+ map<SimplifiedZoneType,int32_t> simplemap;
+ int32_t n=0;
+ for (set<SimplifiedZoneType>::const_iterator i=simpleset.begin();
+ i!=simpleset.end(); ++i) {
+ simplemap[*i] = n++;
+ }
+
+ // Remap transitions
+ for (vector<Transition>::iterator i=transitions.begin();
+ i!=transitions.end(); ++i) {
+ assert(i->type < (int32_t)types.size());
+ ZoneType oldtype = types[i->type];
+ SimplifiedZoneType newtype(oldtype);
+ assert(simplemap.find(newtype) != simplemap.end());
+ i->type = simplemap[newtype];
+ }
+
+ // Replace type list
+ types.clear();
+ copy(simpleset.begin(), simpleset.end(), back_inserter(types));
+
+ } else {
+ if (types.size() > 1) {
+ // Note: localtime uses the very first non-dst type as initial offsets.
+ // If all types are DSTs, the very first type is treated as the initial offsets.
+
+ // Decide a type used as the initial offsets. ICU put the type at index 0.
+ ZoneType initialType = types[0];
+ for (vector<ZoneType>::const_iterator i=types.begin(); i!=types.end(); ++i) {
+ if (i->dstoffset == 0) {
+ initialType = *i;
+ break;
+ }
+ }
+
+ SimplifiedZoneType initialSimplifiedType(initialType);
+
+ // create a set of unique types, but ignoring fields which we're not interested in
+ set<SimplifiedZoneType> simpleset;
+ simpleset.insert(initialSimplifiedType);
+ for (vector<Transition>::const_iterator i=transitions.begin(); i!=transitions.end(); ++i) {
+ assert(i->type < (int32_t)types.size());
+ simpleset.insert(types[i->type]);
+ }
+
+ // Map types to integer indices, however, keeping the first type at offset 0
+ map<SimplifiedZoneType,int32_t> simplemap;
+ simplemap[initialSimplifiedType] = 0;
+ int32_t n = 1;
+ for (set<SimplifiedZoneType>::const_iterator i=simpleset.begin(); i!=simpleset.end(); ++i) {
+ if (*i < initialSimplifiedType || initialSimplifiedType < *i) {
+ simplemap[*i] = n++;
+ }
+ }
+
+ // Remap transitions
+ for (vector<Transition>::iterator i=transitions.begin();
+ i!=transitions.end(); ++i) {
+ assert(i->type < (int32_t)types.size());
+ ZoneType oldtype = types[i->type];
+ SimplifiedZoneType newtype(oldtype);
+ assert(simplemap.find(newtype) != simplemap.end());
+ i->type = simplemap[newtype];
+ }
+
+ // Replace type list
+ types.clear();
+ types.push_back(initialSimplifiedType);
+ for (set<SimplifiedZoneType>::const_iterator i=simpleset.begin(); i!=simpleset.end(); ++i) {
+ if (*i < initialSimplifiedType || initialSimplifiedType < *i) {
+ types.push_back(*i);
+ }
+ }
+
+ // Reiterating transitions to remove any transitions which
+ // do not actually change the raw/dst offsets
+ int32_t prevTypeIdx = 0;
+ for (vector<Transition>::iterator i=transitions.begin(); i!=transitions.end();) {
+ if (i->type == prevTypeIdx) {
+ // this is not a time transition, probably just name change
+ // e.g. America/Resolute after 2006 in 2010b
+ transitions.erase(i);
+ } else {
+ prevTypeIdx = i->type;
+ i++;
+ }
+ }
+ }
+ }
+
+}
+
+/**
+ * Merge final zone data into this zone.
+ */
+void ZoneInfo::mergeFinalData(const FinalZone& fz) {
+ int32_t year = fz.year;
+ int64_t seconds = yearToSeconds(year);
+
+ if (!ICU44PLUS) {
+ if (seconds > HIGHEST_TIME32) {
+ // Avoid transitions beyond signed 32bit max second.
+ // This may result incorrect offset computation around
+ // HIGHEST_TIME32. This is a limitation of ICU
+ // before 4.4.
+ seconds = HIGHEST_TIME32;
+ }
+ }
+
+ vector<Transition>::iterator it =
+ find_if(transitions.begin(), transitions.end(),
+ bind2nd(ptr_fun(isAfter), seconds));
+ transitions.erase(it, transitions.end());
+
+ if (finalYear != -1) {
+ throw invalid_argument("Final zone already merged in");
+ }
+ finalYear = fz.year;
+ finalOffset = fz.offset;
+ finalRuleID = fz.ruleid;
+}
+
+/**
+ * Merge the data from the given final zone into the core zone data by
+ * calling the ZoneInfo member function mergeFinalData.
+ */
+void mergeOne(const string& zoneid, const FinalZone& fz) {
+ if (ZONEINFO.find(zoneid) == ZONEINFO.end()) {
+ throw invalid_argument("Unrecognized final zone ID");
+ }
+ ZONEINFO[zoneid].mergeFinalData(fz);
+}
+
+/**
+ * Visitor function that merges the final zone data into the main zone
+ * data structures. It calls mergeOne for each final zone and its
+ * list of aliases.
+ */
+void mergeFinalZone(const pair<string,FinalZone>& p) {
+ const string& id = p.first;
+ const FinalZone& fz = p.second;
+
+ mergeOne(id, fz);
+}
+
+/**
+ * Print this rule in resource bundle format to os. ID and enclosing
+ * braces handled elsewhere.
+ */
+void FinalRule::print(ostream& os) const {
+ // First print the rule part that enters DST; then the rule part
+ // that exits it.
+ int32_t whichpart = (part[0].offset != 0) ? 0 : 1;
+ assert(part[whichpart].offset != 0);
+ assert(part[1-whichpart].offset == 0);
+
+ os << " ";
+ for (int32_t i=0; i<2; ++i) {
+ const FinalRulePart& p = part[whichpart];
+ whichpart = 1-whichpart;
+ os << p.month << ", " << p.stz_dowim() << ", " << p.stz_dow() << ", "
+ << p.time << ", " << p.timemode() << ", ";
+ }
+ os << part[whichpart].offset << endl;
+}
+
+#define ICU_ZONE_OVERRIDE_SUFFIX "--ICU"
+#define ICU_ZONE_OVERRIDE_SUFFIX_LEN 5
+
+int main(int argc, char *argv[]) {
+ string rootpath, zonetab, version;
+ bool validArgs = false;
+
+ if (argc == 4 || argc == 5) {
+ validArgs = true;
+ rootpath = argv[1];
+ zonetab = argv[2];
+ version = argv[3];
+ if (argc == 5) {
+ if (strcmp(argv[4], "--old") == 0) {
+ ICU44PLUS = false;
+ TZ_RESOURCE_NAME = ICU_TZ_RESOURCE_OLD;
+ } else {
+ validArgs = false;
+ }
+ }
+ }
+ if (!validArgs) {
+ cout << "Usage: tz2icu <dir> <cmap> <tzver> [--old]" << endl
+ << " <dir> path to zoneinfo file tree generated by" << endl
+ << " ICU-patched version of zic" << endl
+ << " <cmap> country map, from tzdata archive," << endl
+ << " typically named \"zone.tab\"" << endl
+ << " <tzver> version string, such as \"2003e\"" << endl
+ << " --old generating resource format before ICU4.4" << endl;
+ exit(1);
+ }
+
+ cout << "Olson data version: " << version << endl;
+ cout << "ICU 4.4+ format: " << (ICU44PLUS ? "Yes" : "No") << endl;
+
+ try {
+ ifstream finals(ICU_ZONE_FILE);
+ if (finals) {
+ readFinalZonesAndRules(finals);
+
+ cout << "Finished reading " << finalZones.size()
+ << " final zones and " << finalRules.size()
+ << " final rules from " ICU_ZONE_FILE << endl;
+ } else {
+ cerr << "Error: Unable to open " ICU_ZONE_FILE << endl;
+ return 1;
+ }
+ } catch (const exception& error) {
+ cerr << "Error: While reading " ICU_ZONE_FILE ": " << error.what() << endl;
+ return 1;
+ }
+
+ try {
+ // Recursively scan all files below the given path, accumulating
+ // their data into ZONEINFO. All files must be TZif files. Any
+ // failure along the way will result in a call to exit(1).
+ scandir(rootpath);
+ } catch (const exception& error) {
+ cerr << "Error: While scanning " << rootpath << ": " << error.what() << endl;
+ return 1;
+ }
+
+ cout << "Finished reading " << ZONEINFO.size() << " zoneinfo files ["
+ << (ZONEINFO.begin())->first << ".."
+ << (--ZONEINFO.end())->first << "]" << endl;
+
+ // Overrides TZ database zones with ICU custom zone definition.
+ // These ICU zone overrides are defined in icuzones, with suffix --ICU.
+ // If there is a matching TZ database zone, the zoneinfo is replaced
+ // with the ICU definition. Then, the zone ID with --ICU suffix
+ // will be deleted from the final list.
+ // For example, zoneinfo for Europe/Dublin imported from the TZ database
+ // will be replaced with the zone definition for Europe/Dublin--ICU
+ // in icuzones.
+
+ // Collect zone IDs to be modified with ICU definition.
+ vector<string> customZones;
+ for (ZoneMapIter i = ZONEINFO.begin(); i != ZONEINFO.end(); ++i) {
+ const string& id = i->first;
+ size_t idx = id.rfind(ICU_ZONE_OVERRIDE_SUFFIX);
+ if (idx != string::npos && idx == id.length() - ICU_ZONE_OVERRIDE_SUFFIX_LEN) {
+ cout << "ICU zone override: " << id << endl;
+ customZones.push_back(id.substr(0, idx));
+ }
+ }
+
+ //
+ // BEGIN ICU Custom ZoneInfo Override Handling
+ //
+
+ // Replace zoneinfo with ICU definition, then remove ICU zone ID with
+ // the special suffix.
+ for (vector<string>::iterator i = customZones.begin(); i != customZones.end(); i++) {
+ string& origId = *i;
+ string custId = origId + ICU_ZONE_OVERRIDE_SUFFIX;
+
+ map<string,ZoneInfo>::iterator origZi = ZONEINFO.find(origId);
+ map<string,ZoneInfo>::iterator custZi = ZONEINFO.find(custId);
+ if (origZi != ZONEINFO.end() && custZi != ZONEINFO.end()) {
+ // replace original zone info with custom override,
+ // then delete one custom ID
+ cout << "Replacing ZoneInfo " << origId << " with " << custId << endl;
+ origZi->second = custZi->second;
+ ZONEINFO.erase(custZi);
+ }
+
+ // Also replace final rule
+ map<string,FinalZone>::iterator origFz = finalZones.find(origId);
+ map<string,FinalZone>::iterator custFz = finalZones.find(custId);
+ if (origFz != finalZones.end() && custFz != finalZones.end()) {
+ // replace original final zone with custom override,
+ // then delete one for custom ID
+ cout << "Replacing FinalZone for " << origId << " with " << custId << endl;
+ origFz->second = custFz->second;
+ finalZones.erase(custFz);
+ }
+ }
+
+ // Also remove aliases for ICU custom zoneinfo overrides.
+ for (map<string,set<string>>::const_iterator i = links.begin(); i != links.end(); ) {
+ const string& id = i->first;
+ size_t idx = id.rfind(ICU_ZONE_OVERRIDE_SUFFIX);
+ if (idx != string::npos && idx == id.length() - ICU_ZONE_OVERRIDE_SUFFIX_LEN) {
+ const set<string>& aliases = i->second;
+ // Also remove all revserse links
+ for (set<string>::const_iterator j = aliases.begin(); j != aliases.end(); j++) {
+ const string& alias = *j;
+ cout << "Removing alias " << alias << endl;
+ reverseLinks.erase(alias);
+ }
+
+ links.erase(i++);
+ } else {
+ i++;
+ }
+ }
+
+
+ //
+ // END ICU Custom ZoneInfo Override Handling
+ //
+
+ try {
+ for_each(finalZones.begin(), finalZones.end(), mergeFinalZone);
+ } catch (const exception& error) {
+ cerr << "Error: While merging final zone data: " << error.what() << endl;
+ return 1;
+ }
+
+ // Process links (including ICU aliases). For each link set we have
+ // a canonical ID (e.g., America/Los_Angeles) and a set of one or more
+ // aliases (e.g., PST, PST8PDT, ...).
+
+ // 1. Add all aliases as zone objects in ZONEINFO
+ for (map<string,set<string> >::const_iterator i = links.begin();
+ i!=links.end(); ++i) {
+ const string& olson = i->first;
+ const set<string>& aliases = i->second;
+ if (ZONEINFO.find(olson) == ZONEINFO.end()) {
+ cerr << "Error: Invalid 'Link' to non-existent \""
+ << olson << "\"" << endl;
+ return 1;
+ }
+ for (set<string>::const_iterator j=aliases.begin();
+ j!=aliases.end(); ++j) {
+ ZONEINFO[*j] = ZoneInfo();
+ }
+ }
+
+ // 2. Create a mapping from zones to index numbers 0..n-1.
+ map<string,int32_t> zoneIDs;
+ vector<string> zoneIDlist;
+ int32_t z=0;
+ for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
+ zoneIDs[i->first] = z++;
+ zoneIDlist.push_back(i->first);
+ }
+ assert(z == (int32_t) ZONEINFO.size());
+
+ // 3. Merge aliases. Sometimes aliases link to other aliases; we
+ // resolve these into simplest possible sets.
+ map<string,set<string> > links2;
+ map<string,string> reverse2;
+ for (map<string,set<string> >::const_iterator i = links.begin();
+ i!=links.end(); ++i) {
+ string olson = i->first;
+ while (reverseLinks.find(olson) != reverseLinks.end()) {
+ olson = reverseLinks[olson];
+ }
+ for (set<string>::const_iterator j=i->second.begin(); j!=i->second.end(); ++j) {
+ links2[olson].insert(*j);
+ reverse2[*j] = olson;
+ }
+ }
+ links = links2;
+ reverseLinks = reverse2;
+
+ if (false) { // Debugging: Emit link map
+ for (map<string,set<string> >::const_iterator i = links.begin();
+ i!=links.end(); ++i) {
+ cout << i->first << ": ";
+ for (set<string>::const_iterator j=i->second.begin(); j!=i->second.end(); ++j) {
+ cout << *j << ", ";
+ }
+ cout << endl;
+ }
+ }
+
+ // 4. Update aliases
+ for (map<string,set<string> >::const_iterator i = links.begin();
+ i!=links.end(); ++i) {
+ const string& olson = i->first;
+ const set<string>& aliases = i->second;
+ ZONEINFO[olson].clearAliases();
+ ZONEINFO[olson].addAlias(zoneIDs[olson]);
+ for (set<string>::const_iterator j=aliases.begin();
+ j!=aliases.end(); ++j) {
+ assert(zoneIDs.find(olson) != zoneIDs.end());
+ assert(zoneIDs.find(*j) != zoneIDs.end());
+ assert(ZONEINFO.find(*j) != ZONEINFO.end());
+ ZONEINFO[*j].setAliasTo(zoneIDs[olson]);
+ ZONEINFO[olson].addAlias(zoneIDs[*j]);
+ }
+ }
+
+ // Once merging of final data is complete, we can optimize the type list
+ for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
+ i->second.optimizeTypeList();
+ }
+
+ // Create the country map
+ map<string, string> icuRegions; // ICU's custom zone -> country override
+ map<string, set<string> > countryMap; // country -> set of zones
+ map<string, string> reverseCountryMap; // zone -> country
+
+ try {
+ // Read icuregions file to collect ICU's own zone-region mapping data.
+ ifstream frg(ICU_REGIONS);
+ if (frg) {
+ string line;
+ while (getline(frg, line)) {
+ if (line[0] == '#') continue;
+
+ string zone, country;
+ istringstream is(line);
+ is >> zone >> country;
+ if (zone.size() == 0) continue;
+ if (country.size() < 2) {
+ cerr << "Error: Can't parse " << line << " in " << ICU_REGIONS << endl;
+ return 1;
+ }
+ icuRegions[zone] = country;
+ }
+ } else {
+ cout << "No custom region map [icuregions]" << endl;
+ }
+ } catch (const exception& error) {
+ cerr << "Error: While reading " << ICU_REGIONS << ": " << error.what() << endl;
+ return 1;
+ }
+
+ try {
+ ifstream f(zonetab.c_str());
+ if (!f) {
+ cerr << "Error: Unable to open " << zonetab << endl;
+ return 1;
+ }
+ int32_t n = 0;
+ string line;
+ while (getline(f, line)) {
+ string::size_type lb = line.find('#');
+ if (lb != string::npos) {
+ line.resize(lb); // trim comments
+ }
+ string country, coord, zone;
+ istringstream is(line);
+ is >> country >> coord >> zone;
+ if (country.size() == 0) continue;
+ if (country.size() != 2 || zone.size() < 1) {
+ cerr << "Error: Can't parse " << line << " in " << zonetab << endl;
+ return 1;
+ }
+ if (ZONEINFO.find(zone) == ZONEINFO.end()) {
+ cerr << "Error: Country maps to invalid zone " << zone
+ << " in " << zonetab << endl;
+ return 1;
+ }
+ if (icuRegions.find(zone) != icuRegions.end()) {
+ // Custom override
+ string customCountry = icuRegions[zone];
+ cout << "Region Mapping: custom override for " << zone
+ << " " << country << " -> " << customCountry << endl;
+ country = customCountry;
+ }
+ countryMap[country].insert(zone);
+ reverseCountryMap[zone] = country;
+ //cerr << (n+1) << ": " << country << " <=> " << zone << endl;
+ ++n;
+ }
+ cout << "Finished reading " << n
+ << " country entries from " << zonetab << endl;
+ } catch (const exception& error) {
+ cerr << "Error: While reading " << zonetab << ": " << error.what() << endl;
+ return 1;
+ }
+
+ // Merge ICU's own zone-region mapping data
+ for (map<string,string>::const_iterator i = icuRegions.begin();
+ i != icuRegions.end(); ++i) {
+ const string& zid(i->first);
+ if (reverseCountryMap.find(zid) != reverseCountryMap.end()) {
+ continue;
+ }
+ cout << "Region Mapping: custom data zone=" << zid
+ << ", region=" << i->second << endl;
+
+ reverseCountryMap[zid] = i->second;
+ countryMap[i->second].insert(zid);
+ }
+
+ // Merge ICU aliases into country map. Don't merge any alias
+ // that already has a country map, since that doesn't make sense.
+ // E.g. "Link Europe/Oslo Arctic/Longyearbyen" doesn't mean we
+ // should cross-map the countries between these two zones.
+ for (map<string,set<string> >::const_iterator i = links.begin();
+ i!=links.end(); ++i) {
+ const string& olson(i->first);
+ if (reverseCountryMap.find(olson) == reverseCountryMap.end()) {
+ continue;
+ }
+ string c = reverseCountryMap[olson];
+ const set<string>& aliases(i->second);
+ for (set<string>::const_iterator j=aliases.begin();
+ j != aliases.end(); ++j) {
+ if (reverseCountryMap.find(*j) == reverseCountryMap.end()) {
+ countryMap[c].insert(*j);
+ reverseCountryMap[*j] = c;
+ //cerr << "Aliased country: " << c << " <=> " << *j << endl;
+ }
+ }
+ }
+
+ // Create a pseudo-country containing all zones belonging to no country
+ set<string> nocountry;
+ for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
+ if (reverseCountryMap.find(i->first) == reverseCountryMap.end()) {
+ nocountry.insert(i->first);
+ }
+ }
+ countryMap[""] = nocountry;
+
+ // Get local time & year for below
+ time_t sec;
+ time(&sec);
+ struct tm* now = localtime(&sec);
+
+ string filename = TZ_RESOURCE_NAME + ".txt";
+ // Write out a resource-bundle source file containing data for
+ // all zones.
+ ofstream file(filename.c_str());
+ if (file) {
+ file << "//---------------------------------------------------------" << endl
+ << "// Copyright (C) 2016 and later: Unicode, Inc. and others." << endl
+ << "// License & terms of use: http://www.unicode.org/copyright.html" << endl
+ << "//---------------------------------------------------------" << endl
+ << "// Build tool: tz2icu" << endl
+ << "// Build date: " << asctime(now) /* << endl -- asctime emits CR */
+ << "// tz database: ftp://ftp.iana.org/tz/" << endl
+ << "// tz version: " << version << endl
+ << "// ICU version: " << U_ICU_VERSION << endl
+ << "//---------------------------------------------------------" << endl
+ << "// >> !!! >> THIS IS A MACHINE-GENERATED FILE << !!! <<" << endl
+ << "// >> !!! >>> DO NOT EDIT <<< !!! <<" << endl
+ << "//---------------------------------------------------------" << endl
+ << endl
+ << TZ_RESOURCE_NAME << ":table(nofallback) {" << endl
+ << " TZVersion { \"" << version << "\" }" << endl
+ << " Zones:array { " << endl
+ << ZONEINFO // Zones (the actual data)
+ << " }" << endl;
+
+ // Names correspond to the Zones list, used for binary searching.
+ printStringList ( file, ZONEINFO ); // print the Names list
+
+ // Final Rules are used if requested by the zone
+ file << " Rules { " << endl;
+ // Emit final rules
+ int32_t frc = 0;
+ for(map<string,FinalRule>::iterator i=finalRules.begin();
+ i!=finalRules.end(); ++i) {
+ const string& id = i->first;
+ const FinalRule& r = i->second;
+ file << " " << id << ":intvector {" << endl;
+ r.print(file);
+ file << " } //_#" << frc++ << endl;
+ }
+ file << " }" << endl;
+
+ // Emit country (region) map.
+ if (ICU44PLUS) {
+ file << " Regions:array {" << endl;
+ int32_t zn = 0;
+ for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
+ map<string, string>::iterator cit = reverseCountryMap.find(i->first);
+ if (cit == reverseCountryMap.end()) {
+ file << " \"001\",";
+ } else {
+ file << " \"" << cit->second << "\", ";
+ }
+ file << "//Z#" << zn++ << " " << i->first << endl;
+ }
+ file << " }" << endl;
+ } else {
+ file << " Regions { " << endl;
+ int32_t rc = 0;
+ for (map<string, set<string> >::const_iterator i=countryMap.begin();
+ i != countryMap.end(); ++i) {
+ string country = i->first;
+ const set<string>& zones(i->second);
+ file << " ";
+ if(country[0]==0) {
+ file << "Default";
+ }
+ file << country << ":intvector { ";
+ bool first = true;
+ for (set<string>::const_iterator j=zones.begin();
+ j != zones.end(); ++j) {
+ if (!first) file << ", ";
+ first = false;
+ if (zoneIDs.find(*j) == zoneIDs.end()) {
+ cerr << "Error: Nonexistent zone in country map: " << *j << endl;
+ return 1;
+ }
+ file << zoneIDs[*j]; // emit the zone's index number
+ }
+ file << " } //R#" << rc++ << endl;
+ }
+ file << " }" << endl;
+ }
+
+ file << "}" << endl;
+ }
+
+ file.close();
+
+ if (file) { // recheck error bit
+ cout << "Finished writing " << TZ_RESOURCE_NAME << ".txt" << endl;
+ } else {
+ cerr << "Error: Unable to open/write to " << TZ_RESOURCE_NAME << ".txt" << endl;
+ return 1;
+ }
+}
+//eof
diff --git a/intl/icu/source/tools/tzcode/tz2icu.h b/intl/icu/source/tools/tzcode/tz2icu.h
new file mode 100644
index 0000000000..c077c21697
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/tz2icu.h
@@ -0,0 +1,46 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+**********************************************************************
+* Copyright (c) 2003-2013, International Business Machines
+* Corporation and others. All Rights Reserved.
+**********************************************************************
+* Author: Alan Liu
+* Created: July 10 2003
+* Since: ICU 2.8
+**********************************************************************
+*/
+
+#ifndef _TZ2ICU_H_
+#define _TZ2ICU_H_
+
+/* We have modified the zoneinfo binary format (we write raw offset
+ * and DST offset separately instead of their sum) so we notate the
+ * file with a distinct signature. This prevents someone from trying
+ * to use our output files as normal zoneinfo files, and also prevents
+ * someone from trying to use normal zoneinfo files for ICU. We also
+ * use the first byte of the reserved section as a version integer, to
+ * be incremented each time the data format changes.
+ */
+
+#define TZ_ICU_MAGIC "TZic" /* cf. TZ_MAGIC = "TZif" */
+
+typedef unsigned char ICUZoneinfoVersion;
+
+#define TZ_ICU_VERSION ((ICUZoneinfoVersion) 1)
+
+/* File into which we will write supplemental ICU data. This allows
+ * zic to communicate final zone data to tz2icu. */
+#define ICU_ZONE_FILE "icu_zone.txt"
+
+/* Output resource name. This determines both the file name and the
+ * resource name within the file. That is, the output will be to the
+ * file ICU_TZ_RESOURCE ".txt" and the resource within it will be
+ * ICU_TZ_RESOURCE. */
+#define ICU_TZ_RESOURCE_OLD "zoneinfo"
+#define ICU_TZ_RESOURCE "zoneinfo64"
+
+/* File containing custom zone-region mapping. */
+#define ICU_REGIONS "icuregions"
+
+#endif
diff --git a/intl/icu/source/tools/tzcode/tzfile.h b/intl/icu/source/tools/tzcode/tzfile.h
new file mode 100644
index 0000000000..8fa197529e
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/tzfile.h
@@ -0,0 +1,169 @@
+#ifndef TZFILE_H
+
+#define TZFILE_H
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** 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.
+*/
+
+#ifndef TZDIR
+#define TZDIR "/usr/local/etc/zoneinfo" /* Time zone object file directory */
+#endif /* !defined TZDIR */
+
+#ifndef TZDEFAULT
+#define TZDEFAULT "localtime"
+#endif /* !defined TZDEFAULT */
+
+#ifndef TZDEFRULES
+#define TZDEFRULES "posixrules"
+#endif /* !defined TZDEFRULES */
+
+/*
+** 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_ttisgmtcnt[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 true, transition
+** time is standard time, if false,
+** transition time is wall clock time
+** if absent, transition times are
+** assumed to be wall clock time
+** tzh_ttisgmtcnt (char)s indexed by type; if true, transition
+** time is UT, if false,
+** transition time is local time
+** if absent, transition times are
+** assumed to be local time
+*/
+
+/*
+** 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.
+*/
+
+#ifndef TZ_MAX_TIMES
+#define TZ_MAX_TIMES 2000
+#endif /* !defined TZ_MAX_TIMES */
+
+#ifndef TZ_MAX_TYPES
+/* 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 */
+#endif /* !defined TZ_MAX_TYPES */
+
+#ifndef TZ_MAX_CHARS
+#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
+ /* (limited by what unsigned chars can hold) */
+#endif /* !defined TZ_MAX_CHARS */
+
+#ifndef TZ_MAX_LEAPS
+#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
+#endif /* !defined TZ_MAX_LEAPS */
+
+#define SECSPERMIN 60
+#define MINSPERHOUR 60
+#define HOURSPERDAY 24
+#define DAYSPERWEEK 7
+#define DAYSPERNYEAR 365
+#define DAYSPERLYEAR 366
+#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
+#define SECSPERDAY ((int_fast32_t) 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 C99).
+** We use this to avoid addition overflow problems.
+*/
+
+#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
+
+#endif /* !defined TZFILE_H */
diff --git a/intl/icu/source/tools/tzcode/tzselect.ksh b/intl/icu/source/tools/tzcode/tzselect.ksh
new file mode 100644
index 0000000000..26dfa98476
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/tzselect.ksh
@@ -0,0 +1,308 @@
+#! /bin/ksh
+
+# '@(#)tzselect.ksh 8.1'
+
+# Ask the user about the time zone, and output the resulting TZ value to stdout.
+# Interact with the user via stderr and stdin.
+
+# Contributed by Paul Eggert.
+
+# Porting notes:
+#
+# This script requires several features of the Korn shell.
+# If your host lacks the Korn shell,
+# you can use either of the following free programs instead:
+#
+# <a href=ftp://ftp.gnu.org/pub/gnu/>
+# Bourne-Again shell (bash)
+# </a>
+#
+# <a href=ftp://ftp.cs.mun.ca/pub/pdksh/pdksh.tar.gz>
+# Public domain ksh
+# </a>
+#
+# This script also uses several features of modern awk programs.
+# If your host lacks awk, or has an old awk that does not conform to Posix.2,
+# you can use either of the following free programs instead:
+#
+# <a href=ftp://ftp.gnu.org/pub/gnu/>
+# GNU awk (gawk)
+# </a>
+#
+# <a href=ftp://ftp.whidbey.net/pub/brennan/>
+# mawk
+# </a>
+
+
+# Specify default values for environment variables if they are unset.
+: ${AWK=awk}
+: ${TZDIR=$(pwd)}
+
+# Check for awk Posix compliance.
+($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
+[ $? = 123 ] || {
+ echo >&2 "$0: Sorry, your \`$AWK' program is not Posix compatible."
+ exit 1
+}
+
+# Make sure the tables are readable.
+TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
+TZ_ZONE_TABLE=$TZDIR/zone.tab
+for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
+do
+ <$f || {
+ echo >&2 "$0: time zone files are not set up correctly"
+ exit 1
+ }
+done
+
+newline='
+'
+IFS=$newline
+
+
+# Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
+case $(echo 1 | (select x in x; do break; done) 2>/dev/null) in
+?*) PS3=
+esac
+
+
+# Begin the main loop. We come back here if the user wants to retry.
+while
+
+ echo >&2 'Please identify a location' \
+ 'so that time zone rules can be set correctly.'
+
+ continent=
+ country=
+ region=
+
+
+ # Ask the user for continent or ocean.
+
+ echo >&2 'Please select a continent or ocean.'
+
+ select continent in \
+ Africa \
+ Americas \
+ Antarctica \
+ 'Arctic Ocean' \
+ Asia \
+ 'Atlantic Ocean' \
+ Australia \
+ Europe \
+ 'Indian Ocean' \
+ 'Pacific Ocean' \
+ 'none - I want to specify the time zone using the Posix TZ format.'
+ do
+ case $continent in
+ '')
+ echo >&2 'Please enter a number in range.';;
+ ?*)
+ case $continent in
+ Americas) continent=America;;
+ *' '*) continent=$(expr "$continent" : '\([^ ]*\)')
+ esac
+ break
+ esac
+ done
+ case $continent in
+ '')
+ exit 1;;
+ none)
+ # Ask the user for a Posix TZ string. Check that it conforms.
+ while
+ echo >&2 'Please enter the desired value' \
+ 'of the TZ environment variable.'
+ echo >&2 'For example, GST-10 is a zone named GST' \
+ 'that is 10 hours ahead (east) of UTC.'
+ read TZ
+ $AWK -v TZ="$TZ" 'BEGIN {
+ tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
+ time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
+ offset = "[-+]?" time
+ date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
+ datetime = "," date "(/" time ")?"
+ tzpattern = "^(:.*|" tzname offset "(" tzname \
+ "(" offset ")?(" datetime datetime ")?)?)$"
+ if (TZ ~ tzpattern) exit 1
+ exit 0
+ }'
+ do
+ echo >&2 "\`$TZ' is not a conforming" \
+ 'Posix time zone string.'
+ done
+ TZ_for_date=$TZ;;
+ *)
+ # Get list of names of countries in the continent or ocean.
+ countries=$($AWK -F'\t' \
+ -v continent="$continent" \
+ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
+ '
+ /^#/ { next }
+ $3 ~ ("^" continent "/") {
+ if (!cc_seen[$1]++) cc_list[++ccs] = $1
+ }
+ END {
+ while (getline <TZ_COUNTRY_TABLE) {
+ if ($0 !~ /^#/) cc_name[$1] = $2
+ }
+ for (i = 1; i <= ccs; i++) {
+ country = cc_list[i]
+ if (cc_name[country]) {
+ country = cc_name[country]
+ }
+ print country
+ }
+ }
+ ' <$TZ_ZONE_TABLE | sort -f)
+
+
+ # If there's more than one country, ask the user which one.
+ case $countries in
+ *"$newline"*)
+ echo >&2 'Please select a country.'
+ select country in $countries
+ do
+ case $country in
+ '') echo >&2 'Please enter a number in range.';;
+ ?*) break
+ esac
+ done
+
+ case $country in
+ '') exit 1
+ esac;;
+ *)
+ country=$countries
+ esac
+
+
+ # Get list of names of time zone rule regions in the country.
+ regions=$($AWK -F'\t' \
+ -v country="$country" \
+ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
+ '
+ BEGIN {
+ cc = country
+ while (getline <TZ_COUNTRY_TABLE) {
+ if ($0 !~ /^#/ && country == $2) {
+ cc = $1
+ break
+ }
+ }
+ }
+ $1 == cc { print $4 }
+ ' <$TZ_ZONE_TABLE)
+
+
+ # If there's more than one region, ask the user which one.
+ case $regions in
+ *"$newline"*)
+ echo >&2 'Please select one of the following' \
+ 'time zone regions.'
+ select region in $regions
+ do
+ case $region in
+ '') echo >&2 'Please enter a number in range.';;
+ ?*) break
+ esac
+ done
+ case $region in
+ '') exit 1
+ esac;;
+ *)
+ region=$regions
+ esac
+
+ # Determine TZ from country and region.
+ TZ=$($AWK -F'\t' \
+ -v country="$country" \
+ -v region="$region" \
+ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
+ '
+ BEGIN {
+ cc = country
+ while (getline <TZ_COUNTRY_TABLE) {
+ if ($0 !~ /^#/ && country == $2) {
+ cc = $1
+ break
+ }
+ }
+ }
+ $1 == cc && $4 == region { print $3 }
+ ' <$TZ_ZONE_TABLE)
+
+ # Make sure the corresponding zoneinfo file exists.
+ TZ_for_date=$TZDIR/$TZ
+ <$TZ_for_date || {
+ echo >&2 "$0: time zone files are not set up correctly"
+ exit 1
+ }
+ esac
+
+
+ # Use the proposed TZ to output the current date relative to UTC.
+ # Loop until they agree in seconds.
+ # Give up after 8 unsuccessful tries.
+
+ extra_info=
+ for i in 1 2 3 4 5 6 7 8
+ do
+ TZdate=$(LANG=C TZ="$TZ_for_date" date)
+ UTdate=$(LANG=C TZ=UTC0 date)
+ TZsec=$(expr "$TZdate" : '.*:\([0-5][0-9]\)')
+ UTsec=$(expr "$UTdate" : '.*:\([0-5][0-9]\)')
+ case $TZsec in
+ $UTsec)
+ extra_info="
+Local time is now: $TZdate.
+Universal Time is now: $UTdate."
+ break
+ esac
+ done
+
+
+ # Output TZ info and ask the user to confirm.
+
+ echo >&2 ""
+ echo >&2 "The following information has been given:"
+ echo >&2 ""
+ case $country+$region in
+ ?*+?*) echo >&2 " $country$newline $region";;
+ ?*+) echo >&2 " $country";;
+ +) echo >&2 " TZ='$TZ'"
+ esac
+ echo >&2 ""
+ echo >&2 "Therefore TZ='$TZ' will be used.$extra_info"
+ echo >&2 "Is the above information OK?"
+
+ ok=
+ select ok in Yes No
+ do
+ case $ok in
+ '') echo >&2 'Please enter 1 for Yes, or 2 for No.';;
+ ?*) break
+ esac
+ done
+ case $ok in
+ '') exit 1;;
+ Yes) break
+ esac
+do :
+done
+
+case $SHELL in
+*csh) file=.login line="setenv TZ '$TZ'";;
+*) file=.profile line="TZ='$TZ'; export TZ"
+esac
+
+echo >&2 "
+You can make this change permanent for yourself by appending the line
+ $line
+to the file '$file' in your home directory; then log out and log in again.
+
+Here is that TZ value again, this time on standard output so that you
+can use the $0 command in shell scripts:"
+
+echo "$TZ"
diff --git a/intl/icu/source/tools/tzcode/zdump.c b/intl/icu/source/tools/tzcode/zdump.c
new file mode 100644
index 0000000000..ebd7a5ce32
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/zdump.c
@@ -0,0 +1,1089 @@
+/*
+** This file is in the public domain, so clarified as of
+** 2009-05-17 by Arthur David Olson.
+*/
+
+#include "version.h"
+
+/*
+** This code has been made independent of the rest of the time
+** conversion package to increase confidence in the verification it provides.
+** You can use this code to help in verifying other implementations.
+**
+** However, include private.h when debugging, so that it overrides
+** time_t consistently with the rest of the package.
+*/
+
+#ifdef time_tz
+# include "private.h"
+#endif
+
+#include <stdbool.h>
+
+#include "stdio.h" /* for stdout, stderr, perror */
+#include "string.h" /* for strcpy */
+#include "sys/types.h" /* for time_t */
+#include "time.h" /* for struct tm */
+#include "stdlib.h" /* for exit, malloc, atoi */
+#include "limits.h" /* for CHAR_BIT, LLONG_MAX */
+#include "ctype.h" /* for isalpha et al. */
+
+/* Enable extensions and modifications for ICU. */
+#define ICU
+
+#ifdef ICU
+#include "dirent.h"
+#include "sys/stat.h"
+#endif
+
+#ifndef isascii
+#define isascii(x) 1
+#endif /* !defined isascii */
+
+/*
+** Substitutes for pre-C99 compilers.
+** Much of this section of code is stolen from private.h.
+*/
+
+#ifndef HAVE_STDINT_H
+# define HAVE_STDINT_H \
+ (199901 <= __STDC_VERSION__ || 2 < (__GLIBC__ + (0 < __GLIBC_MINOR__)))
+#endif
+#if HAVE_STDINT_H
+# include "stdint.h"
+#endif
+#ifndef HAVE_INTTYPES_H
+# define HAVE_INTTYPES_H HAVE_STDINT_H
+#endif
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+#ifndef INT_FAST32_MAX
+# if INT_MAX >> 31 == 0
+typedef long int_fast32_t;
+# else
+typedef int int_fast32_t;
+# endif
+#endif
+
+#ifndef INTMAX_MAX
+# if defined LLONG_MAX || defined __LONG_LONG_MAX__
+typedef long long intmax_t;
+# define strtoimax strtoll
+# define PRIdMAX "lld"
+# ifdef LLONG_MAX
+# define INTMAX_MAX LLONG_MAX
+# else
+# define INTMAX_MAX __LONG_LONG_MAX__
+# endif
+# else
+typedef long intmax_t;
+# define strtoimax strtol
+# define PRIdMAX "ld"
+# define INTMAX_MAX LONG_MAX
+# endif
+#endif
+
+
+#ifndef ZDUMP_LO_YEAR
+#define ZDUMP_LO_YEAR (-500)
+#endif /* !defined ZDUMP_LO_YEAR */
+
+#ifndef ZDUMP_HI_YEAR
+#define ZDUMP_HI_YEAR 2500
+#endif /* !defined ZDUMP_HI_YEAR */
+
+#ifndef MAX_STRING_LENGTH
+#define MAX_STRING_LENGTH 1024
+#endif /* !defined MAX_STRING_LENGTH */
+
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif /* !defined EXIT_SUCCESS */
+
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif /* !defined EXIT_FAILURE */
+
+#ifndef SECSPERMIN
+#define SECSPERMIN 60
+#endif /* !defined SECSPERMIN */
+
+#ifndef MINSPERHOUR
+#define MINSPERHOUR 60
+#endif /* !defined MINSPERHOUR */
+
+#ifndef SECSPERHOUR
+#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
+#endif /* !defined SECSPERHOUR */
+
+#ifndef HOURSPERDAY
+#define HOURSPERDAY 24
+#endif /* !defined HOURSPERDAY */
+
+#ifndef EPOCH_YEAR
+#define EPOCH_YEAR 1970
+#endif /* !defined EPOCH_YEAR */
+
+#ifndef TM_YEAR_BASE
+#define TM_YEAR_BASE 1900
+#endif /* !defined TM_YEAR_BASE */
+
+#ifndef DAYSPERNYEAR
+#define DAYSPERNYEAR 365
+#endif /* !defined DAYSPERNYEAR */
+
+#ifndef isleap
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+#endif /* !defined isleap */
+
+#ifndef isleap_sum
+/*
+** See tzfile.h for details on isleap_sum.
+*/
+#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
+#endif /* !defined isleap_sum */
+
+#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
+#define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
+#define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
+#define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
+ + SECSPERLYEAR * (intmax_t) (100 - 3))
+
+/*
+** True if SECSPER400YEARS is known to be representable as an
+** intmax_t. It's OK that SECSPER400YEARS_FITS can in theory be false
+** even if SECSPER400YEARS is representable, because when that happens
+** the code merely runs a bit more slowly, and this slowness doesn't
+** occur on any practical platform.
+*/
+enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
+
+#ifndef HAVE_GETTEXT
+#define HAVE_GETTEXT 0
+#endif
+#if HAVE_GETTEXT
+#include "locale.h" /* for setlocale */
+#include "libintl.h"
+#endif /* HAVE_GETTEXT */
+
+#ifndef GNUC_or_lint
+#ifdef lint
+#define GNUC_or_lint
+#else /* !defined lint */
+#ifdef __GNUC__
+#define GNUC_or_lint
+#endif /* defined __GNUC__ */
+#endif /* !defined lint */
+#endif /* !defined GNUC_or_lint */
+
+#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__)
+# define ATTRIBUTE_PURE __attribute__ ((__pure__))
+#else
+# define ATTRIBUTE_PURE /* empty */
+#endif
+
+/*
+** For the benefit of GNU folk...
+** `_(MSGID)' uses the current locale's message library string for MSGID.
+** The default is to use gettext if available, and use MSGID otherwise.
+*/
+
+#ifndef _
+#if HAVE_GETTEXT
+#define _(msgid) gettext(msgid)
+#else /* !HAVE_GETTEXT */
+#define _(msgid) msgid
+#endif /* !HAVE_GETTEXT */
+#endif /* !defined _ */
+
+#ifndef TZ_DOMAIN
+#define TZ_DOMAIN "tz"
+#endif /* !defined TZ_DOMAIN */
+
+extern char ** environ;
+extern int getopt(int argc, char * const argv[],
+ const char * options);
+extern char * optarg;
+extern int optind;
+extern char * tzname[2];
+
+/* The minimum and maximum finite time values. */
+static time_t const absolute_min_time =
+ ((time_t) -1 < 0
+ ? (time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1)
+ : 0);
+static time_t const absolute_max_time =
+ ((time_t) -1 < 0
+ ? - (~ 0 < 0) - ((time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1))
+ : -1);
+static size_t longest;
+static char * progname;
+static int warned;
+
+static char * abbr(struct tm * tmp);
+static void abbrok(const char * abbrp, const char * zone);
+static intmax_t delta(struct tm * newp, struct tm * oldp) ATTRIBUTE_PURE;
+static void dumptime(const struct tm * tmp);
+static time_t hunt(char * name, time_t lot, time_t hit);
+static void show(char * zone, time_t t, int v);
+static const char * tformat(void);
+static time_t yeartot(intmax_t y) ATTRIBUTE_PURE;
+#ifdef ICU
+typedef struct listentry {
+ char * name;
+ struct listentry * next;
+} listentry;
+
+static time_t huntICU(char * name, time_t lot, time_t hit, FILE *fp);
+static void dumptimeICU(FILE * fp, time_t t);
+static void showICU(FILE * fp, char * zone, time_t t1, time_t t2);
+static int getall(struct listentry ** namelist);
+static void getzones(char * basedir, char * subdir, struct listentry ** last, int * count);
+#endif
+
+#ifndef TYPECHECK
+#define my_localtime localtime
+#else /* !defined TYPECHECK */
+static struct tm *
+my_localtime(time_t *tp)
+{
+ register struct tm * tmp;
+
+ tmp = localtime(tp);
+ if (tp != NULL && tmp != NULL) {
+ struct tm tm;
+ register time_t t;
+
+ tm = *tmp;
+ t = mktime(&tm);
+ if (t != *tp) {
+ (void) fflush(stdout);
+ (void) fprintf(stderr, "\n%s: ", progname);
+ (void) fprintf(stderr, tformat(), *tp);
+ (void) fprintf(stderr, " ->");
+ (void) fprintf(stderr, " year=%d", tmp->tm_year);
+ (void) fprintf(stderr, " mon=%d", tmp->tm_mon);
+ (void) fprintf(stderr, " mday=%d", tmp->tm_mday);
+ (void) fprintf(stderr, " hour=%d", tmp->tm_hour);
+ (void) fprintf(stderr, " min=%d", tmp->tm_min);
+ (void) fprintf(stderr, " sec=%d", tmp->tm_sec);
+ (void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
+ (void) fprintf(stderr, " -> ");
+ (void) fprintf(stderr, tformat(), t);
+ (void) fprintf(stderr, "\n");
+ }
+ }
+ return tmp;
+}
+#endif /* !defined TYPECHECK */
+
+static void
+abbrok(const char *const abbrp, const char *const zone)
+{
+ register const char * cp;
+ register const char * wp;
+
+ if (warned)
+ return;
+ cp = abbrp;
+ wp = NULL;
+ while (isascii((unsigned char) *cp) && isalpha((unsigned char) *cp))
+ ++cp;
+ if (cp - abbrp == 0)
+ wp = _("lacks alphabetic at start");
+ else if (cp - abbrp < 3)
+ wp = _("has fewer than 3 alphabetics");
+ else if (cp - abbrp > 6)
+ wp = _("has more than 6 alphabetics");
+ if (wp == NULL && (*cp == '+' || *cp == '-')) {
+ ++cp;
+ if (isascii((unsigned char) *cp) &&
+ isdigit((unsigned char) *cp))
+ if (*cp++ == '1' && *cp >= '0' && *cp <= '4')
+ ++cp;
+ if (*cp != '\0')
+ wp = _("differs from POSIX standard");
+ }
+ if (wp == NULL)
+ return;
+ (void) fflush(stdout);
+ (void) fprintf(stderr,
+ _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
+ progname, zone, abbrp, wp);
+ warned = true;
+}
+
+static void
+usage(FILE * const stream, const int status)
+{
+ (void) fprintf(stream,
+_("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n"
+ "\n"
+ "Report bugs to %s.\n"),
+ progname, progname, REPORT_BUGS_TO);
+ exit(status);
+}
+
+int
+main(int argc, char *argv[])
+{
+ register int i;
+ register int vflag;
+ register int Vflag;
+ register char * cutarg;
+ register char * cuttimes;
+ register time_t cutlotime;
+ register time_t cuthitime;
+ register char ** fakeenv;
+ time_t now;
+ time_t t;
+ time_t newt;
+ struct tm tm;
+ struct tm newtm;
+ register struct tm * tmp;
+ register struct tm * newtmp;
+#ifdef ICU
+ int nextopt;
+ char * dirarg;
+ int aflag;
+ int iflag;
+ listentry * namelist = NULL;
+ FILE * fp = stdout;
+#endif
+
+ cutlotime = absolute_min_time;
+ cuthitime = absolute_max_time;
+#if HAVE_GETTEXT
+ (void) setlocale(LC_ALL, "");
+#ifdef TZ_DOMAINDIR
+ (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
+#endif /* defined TEXTDOMAINDIR */
+ (void) textdomain(TZ_DOMAIN);
+#endif /* HAVE_GETTEXT */
+ progname = argv[0];
+ for (i = 1; i < argc; ++i)
+ if (strcmp(argv[i], "--version") == 0) {
+ (void) printf("zdump %s%s\n", PKGVERSION, TZVERSION);
+ exit(EXIT_SUCCESS);
+ } else if (strcmp(argv[i], "--help") == 0) {
+ usage(stdout, EXIT_SUCCESS);
+ }
+ vflag = Vflag = 0;
+ cutarg = cuttimes = NULL;
+#ifdef ICU
+ aflag = 0;
+ iflag = 0;
+ dirarg = NULL;
+ for (;;)
+ switch(getopt(argc, argv, "ac:d:it:vV")) {
+ case 'a': aflag = 1; break;
+ case 'c': cutarg = optarg; break;
+ case 'd': dirarg = optarg; break;
+ case 'i': iflag = 1; break;
+ case 't': cuttimes = optarg; break;
+ case 'v': vflag = 1; break;
+ case 'V': Vflag = 1; break;
+ case -1:
+ if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
+ goto arg_processing_done;
+ /* Fall through. */
+ default:
+ (void) fprintf(stderr,
+ _("%s: usage is %s [ --version ] [ -a ] [ -v ] [ -V ] [ -i ] [ -c [loyear,]hiyear ] [ -t [lotime,]hitime] ][ -d dir ] [ zonename ... ]\n"),
+ progname, progname);
+ exit(EXIT_FAILURE);
+ }
+#else
+ for (;;)
+ switch (getopt(argc, argv, "c:t:vV")) {
+ case 'c': cutarg = optarg; break;
+ case 't': cuttimes = optarg; break;
+ case 'v': vflag = 1; break;
+ case 'V': Vflag = 1; break;
+ case -1:
+ if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
+ goto arg_processing_done;
+ /* Fall through. */
+ default:
+ usage(stderr, EXIT_FAILURE);
+ }
+#endif
+ arg_processing_done:;
+
+#ifdef ICU
+ if (dirarg != NULL) {
+ DIR * dp;
+ /* create the output directory */
+ mkdir(dirarg, 0777);
+ if ((dp = opendir(dirarg)) == NULL) {
+ fprintf(stderr, "cannot create the target directory");
+ exit(EXIT_FAILURE);
+ }
+ closedir(dp);
+ }
+#endif
+
+ if (vflag | Vflag) {
+ intmax_t lo;
+ intmax_t hi;
+ char *loend, *hiend;
+ register intmax_t cutloyear = ZDUMP_LO_YEAR;
+ register intmax_t cuthiyear = ZDUMP_HI_YEAR;
+ if (cutarg != NULL) {
+ lo = strtoimax(cutarg, &loend, 10);
+ if (cutarg != loend && !*loend) {
+ hi = lo;
+ cuthiyear = hi;
+ } else if (cutarg != loend && *loend == ','
+ && (hi = strtoimax(loend + 1, &hiend, 10),
+ loend + 1 != hiend && !*hiend)) {
+ cutloyear = lo;
+ cuthiyear = hi;
+ } else {
+(void) fprintf(stderr, _("%s: wild -c argument %s\n"),
+ progname, cutarg);
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (cutarg != NULL || cuttimes == NULL) {
+ cutlotime = yeartot(cutloyear);
+ cuthitime = yeartot(cuthiyear);
+ }
+ if (cuttimes != NULL) {
+ lo = strtoimax(cuttimes, &loend, 10);
+ if (cuttimes != loend && !*loend) {
+ hi = lo;
+ if (hi < cuthitime) {
+ if (hi < absolute_min_time)
+ hi = absolute_min_time;
+ cuthitime = hi;
+ }
+ } else if (cuttimes != loend && *loend == ','
+ && (hi = strtoimax(loend + 1, &hiend, 10),
+ loend + 1 != hiend && !*hiend)) {
+ if (cutlotime < lo) {
+ if (absolute_max_time < lo)
+ lo = absolute_max_time;
+ cutlotime = lo;
+ }
+ if (hi < cuthitime) {
+ if (hi < absolute_min_time)
+ hi = absolute_min_time;
+ cuthitime = hi;
+ }
+ } else {
+ (void) fprintf(stderr,
+ _("%s: wild -t argument %s\n"),
+ progname, cuttimes);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+#ifdef ICU
+ if (aflag) {
+ /* get all available zones */
+ char ** fakeargv;
+ int i;
+ int count;
+
+ count = getall(&namelist);
+ fakeargv = (char **) malloc((size_t) (argc + count) * sizeof *argv);
+ /*
+ if ((fakeargv = (char **) malloc((size_t) (argc + count) * sizeof *argv)) == NULL) {
+ exit(EXIT_FAILURE);
+ }
+ */
+ for (i = 0; i < argc; i++) {
+ fakeargv[i] = argv[i];
+ }
+ for (i = 0; i < count; i++) {
+ fakeargv[i + argc] = namelist->name;
+ namelist = namelist->next;
+ }
+ argv = fakeargv;
+ argc += count;
+ }
+#endif
+ (void) time(&now);
+ longest = 0;
+ for (i = optind; i < argc; ++i)
+ if (strlen(argv[i]) > longest)
+ longest = strlen(argv[i]);
+ {
+ register int from;
+ register int to;
+
+ for (i = 0; environ[i] != NULL; ++i)
+ continue;
+ fakeenv = malloc((i + 2) * sizeof *fakeenv);
+ if (fakeenv == NULL
+ || (fakeenv[0] = malloc(longest + 4)) == NULL) {
+ (void) perror(progname);
+ exit(EXIT_FAILURE);
+ }
+ to = 0;
+ (void) strcpy(fakeenv[to++], "TZ=");
+ for (from = 0; environ[from] != NULL; ++from)
+ if (strncmp(environ[from], "TZ=", 3) != 0)
+ fakeenv[to++] = environ[from];
+ fakeenv[to] = NULL;
+ environ = fakeenv;
+ }
+ for (i = optind; i < argc; ++i) {
+ static char buf[MAX_STRING_LENGTH];
+
+ (void) strcpy(&fakeenv[0][3], argv[i]);
+ if (! (vflag | Vflag)) {
+ show(argv[i], now, false);
+ continue;
+ }
+#ifdef ICU
+ fp = NULL;
+ if (iflag) {
+ if (dirarg == NULL) {
+ /* we want to display a zone name here */
+ if (i != optind) {
+ printf("\n");
+ }
+ printf("ZONE: %s\n", argv[i]);
+ } else {
+ int zstart;
+ char path[FILENAME_MAX + 1];
+ strcpy(path, dirarg);
+ strcat(path, "/");
+ zstart = strlen(path);
+ strcat(path, argv[i]);
+ /* replace '/' with '-' */
+ while(path[++zstart] != 0) {
+ if (path[zstart] == '/') {
+ path[zstart] = '-';
+ }
+ }
+ if ((fp = fopen(path, "w")) == NULL) {
+ fprintf(stderr, "cannot create output file %s\n", path);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+#endif
+ warned = false;
+ t = absolute_min_time;
+#ifdef ICU
+ /* skip displaying info for the lowest time, which is actually not
+ * a transition when -i option is set */
+ if (!iflag) {
+#endif
+ if (!Vflag) {
+ show(argv[i], t, true);
+ t += SECSPERDAY;
+ show(argv[i], t, true);
+ }
+#ifdef ICU
+ }
+#endif
+ if (t < cutlotime)
+ t = cutlotime;
+ tmp = my_localtime(&t);
+ if (tmp != NULL) {
+ tm = *tmp;
+ (void) strncpy(buf, abbr(&tm), (sizeof buf) - 1);
+ }
+ for ( ; ; ) {
+ newt = (t < absolute_max_time - SECSPERDAY / 2
+ ? t + SECSPERDAY / 2
+ : absolute_max_time);
+ if (cuthitime <= newt)
+ break;
+ newtmp = localtime(&newt);
+ if (newtmp != NULL)
+ newtm = *newtmp;
+#ifdef ICU
+ if (iflag) {
+ /* We do not want to capture transitions just for
+ * abbreviated zone name changes */
+ if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
+ (delta(&newtm, &tm) != (newt - t) ||
+ newtm.tm_isdst != tm.tm_isdst)) {
+ newt = huntICU(argv[i], t, newt, fp);
+ newtmp = localtime(&newt);
+ if (newtmp != NULL) {
+ newtm = *newtmp;
+ (void) strncpy(buf,
+ abbr(&newtm),
+ (sizeof buf) - 1);
+ }
+ }
+ } else {
+#endif
+ if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
+ (delta(&newtm, &tm) != (newt - t) ||
+ newtm.tm_isdst != tm.tm_isdst ||
+ strcmp(abbr(&newtm), buf) != 0)) {
+ newt = hunt(argv[i], t, newt);
+ newtmp = localtime(&newt);
+ if (newtmp != NULL) {
+ newtm = *newtmp;
+ (void) strncpy(buf,
+ abbr(&newtm),
+ (sizeof buf) - 1);
+ }
+ }
+#ifdef ICU
+ }
+#endif
+ t = newt;
+ tm = newtm;
+ tmp = newtmp;
+ }
+#ifdef ICU
+ if (!iflag) {
+ /* skip displaying info for the highest time, which is actually not
+ * a transition when -i option is used*/
+#endif
+ if (!Vflag) {
+ t = absolute_max_time;
+ t -= SECSPERDAY;
+ show(argv[i], t, true);
+ t += SECSPERDAY;
+ show(argv[i], t, true);
+ }
+#ifdef ICU
+ }
+ /* close file */
+ if (fp != NULL) {
+ fclose(fp);
+ }
+#endif
+ }
+ if (fflush(stdout) || ferror(stdout)) {
+ (void) fprintf(stderr, "%s: ", progname);
+ (void) perror(_("Error writing to standard output"));
+ exit(EXIT_FAILURE);
+ }
+#ifdef ICU
+ if (aflag) {
+ struct listentry * entry = namelist;
+ struct listentry * next;
+ while (entry != NULL) {
+ free(entry->name);
+ next = entry->next;
+ free(entry);
+ entry = next;
+ }
+ }
+#endif
+ exit(EXIT_SUCCESS);
+ /* If exit fails to exit... */
+ return EXIT_FAILURE;
+}
+
+static time_t
+yeartot(const intmax_t y)
+{
+ register intmax_t myy, seconds, years;
+ register time_t t;
+
+ myy = EPOCH_YEAR;
+ t = 0;
+ while (myy < y) {
+ if (SECSPER400YEARS_FITS && 400 <= y - myy) {
+ intmax_t diff400 = (y - myy) / 400;
+ if (INTMAX_MAX / SECSPER400YEARS < diff400)
+ return absolute_max_time;
+ seconds = diff400 * SECSPER400YEARS;
+ years = diff400 * 400;
+ } else {
+ seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
+ years = 1;
+ }
+ myy += years;
+ if (t > absolute_max_time - seconds)
+ return absolute_max_time;
+ t += seconds;
+ }
+ while (y < myy) {
+ if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
+ intmax_t diff400 = (myy - y) / 400;
+ if (INTMAX_MAX / SECSPER400YEARS < diff400)
+ return absolute_min_time;
+ seconds = diff400 * SECSPER400YEARS;
+ years = diff400 * 400;
+ } else {
+ seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
+ years = 1;
+ }
+ myy -= years;
+ if (t < absolute_min_time + seconds)
+ return absolute_min_time;
+ t -= seconds;
+ }
+ return t;
+}
+
+static time_t
+hunt(char *name, time_t lot, time_t hit)
+{
+ time_t t;
+ struct tm lotm;
+ register struct tm * lotmp;
+ struct tm tm;
+ register struct tm * tmp;
+ char loab[MAX_STRING_LENGTH];
+
+ lotmp = my_localtime(&lot);
+ if (lotmp != NULL) {
+ lotm = *lotmp;
+ (void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1);
+ }
+ for ( ; ; ) {
+ time_t diff = hit - lot;
+ if (diff < 2)
+ break;
+ t = lot;
+ t += diff / 2;
+ if (t <= lot)
+ ++t;
+ else if (t >= hit)
+ --t;
+ tmp = my_localtime(&t);
+ if (tmp != NULL)
+ tm = *tmp;
+ if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
+ (delta(&tm, &lotm) == (t - lot) &&
+ tm.tm_isdst == lotm.tm_isdst &&
+ strcmp(abbr(&tm), loab) == 0)) {
+ lot = t;
+ lotm = tm;
+ lotmp = tmp;
+ } else hit = t;
+ }
+ show(name, lot, true);
+ show(name, hit, true);
+ return hit;
+}
+
+/*
+** Thanks to Paul Eggert for logic used in delta.
+*/
+
+static intmax_t
+delta(struct tm * newp, struct tm *oldp)
+{
+ register intmax_t result;
+ register int tmy;
+
+ if (newp->tm_year < oldp->tm_year)
+ return -delta(oldp, newp);
+ result = 0;
+ for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
+ result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
+ result += newp->tm_yday - oldp->tm_yday;
+ result *= HOURSPERDAY;
+ result += newp->tm_hour - oldp->tm_hour;
+ result *= MINSPERHOUR;
+ result += newp->tm_min - oldp->tm_min;
+ result *= SECSPERMIN;
+ result += newp->tm_sec - oldp->tm_sec;
+ return result;
+}
+
+static void
+show(char *zone, time_t t, int v)
+{
+ register struct tm * tmp;
+
+ (void) printf("%-*s ", (int) longest, zone);
+ if (v) {
+ tmp = gmtime(&t);
+ if (tmp == NULL) {
+ (void) printf(tformat(), t);
+ } else {
+ dumptime(tmp);
+ (void) printf(" UT");
+ }
+ (void) printf(" = ");
+ }
+ tmp = my_localtime(&t);
+ dumptime(tmp);
+ if (tmp != NULL) {
+ if (*abbr(tmp) != '\0')
+ (void) printf(" %s", abbr(tmp));
+ if (v) {
+ (void) printf(" isdst=%d", tmp->tm_isdst);
+#ifdef TM_GMTOFF
+ (void) printf(" gmtoff=%ld", tmp->TM_GMTOFF);
+#endif /* defined TM_GMTOFF */
+ }
+ }
+ (void) printf("\n");
+ if (tmp != NULL && *abbr(tmp) != '\0')
+ abbrok(abbr(tmp), zone);
+}
+
+static char *
+abbr(struct tm *tmp)
+{
+ register char * result;
+ static char nada;
+
+ if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)
+ return &nada;
+ result = tzname[tmp->tm_isdst];
+ return (result == NULL) ? &nada : result;
+}
+
+/*
+** The code below can fail on certain theoretical systems;
+** it works on all known real-world systems as of 2004-12-30.
+*/
+
+static const char *
+tformat(void)
+{
+ if (0 > (time_t) -1) { /* signed */
+ if (sizeof (time_t) == sizeof (intmax_t))
+ return "%"PRIdMAX;
+ if (sizeof (time_t) > sizeof (long))
+ return "%lld";
+ if (sizeof (time_t) > sizeof (int))
+ return "%ld";
+ return "%d";
+ }
+#ifdef PRIuMAX
+ if (sizeof (time_t) == sizeof (uintmax_t))
+ return "%"PRIuMAX;
+#endif
+ if (sizeof (time_t) > sizeof (unsigned long))
+ return "%llu";
+ if (sizeof (time_t) > sizeof (unsigned int))
+ return "%lu";
+ return "%u";
+}
+
+static void
+dumptime(register const struct tm *timeptr)
+{
+ static const char wday_name[][3] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ static const char mon_name[][3] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ register const char * wn;
+ register const char * mn;
+ register int lead;
+ register int trail;
+
+ if (timeptr == NULL) {
+ (void) printf("NULL");
+ return;
+ }
+ /*
+ ** The packaged versions of localtime and gmtime never put out-of-range
+ ** values in tm_wday or tm_mon, but since this code might be compiled
+ ** with other (perhaps experimental) versions, paranoia is in order.
+ */
+ if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
+ (int) (sizeof wday_name / sizeof wday_name[0]))
+ wn = "???";
+ else wn = wday_name[timeptr->tm_wday];
+ if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
+ (int) (sizeof mon_name / sizeof mon_name[0]))
+ mn = "???";
+ else mn = mon_name[timeptr->tm_mon];
+ (void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
+ wn, mn,
+ timeptr->tm_mday, timeptr->tm_hour,
+ timeptr->tm_min, timeptr->tm_sec);
+#define DIVISOR 10
+ trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
+ lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
+ trail / DIVISOR;
+ trail %= DIVISOR;
+ if (trail < 0 && lead > 0) {
+ trail += DIVISOR;
+ --lead;
+ } else if (lead < 0 && trail > 0) {
+ trail -= DIVISOR;
+ ++lead;
+ }
+ if (lead == 0)
+ (void) printf("%d", trail);
+ else (void) printf("%d%d", lead, ((trail < 0) ? -trail : trail));
+}
+
+#ifdef ICU
+static time_t
+huntICU(char *name, time_t lot, time_t hit, FILE * fp)
+{
+ time_t t;
+ long diff;
+ struct tm lotm;
+ register struct tm * lotmp;
+ struct tm tm;
+ register struct tm * tmp;
+ char loab[MAX_STRING_LENGTH];
+
+ lotmp = my_localtime(&lot);
+ if (lotmp != NULL) {
+ lotm = *lotmp;
+ (void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1);
+ }
+ for ( ; ; ) {
+ diff = (long) (hit - lot);
+ if (diff < 2)
+ break;
+ t = lot;
+ t += diff / 2;
+ if (t <= lot)
+ ++t;
+ else if (t >= hit)
+ --t;
+ tmp = my_localtime(&t);
+ if (tmp != NULL)
+ tm = *tmp;
+ /* We do not want to capture transitions just for
+ * abbreviated zone name changes */
+ if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
+ (delta(&tm, &lotm) == (t - lot) &&
+ tm.tm_isdst == lotm.tm_isdst)) {
+ lot = t;
+ lotm = tm;
+ lotmp = tmp;
+ } else hit = t;
+ }
+ showICU(fp, name, lot, hit);
+ return hit;
+}
+
+static void showICU(FILE * fp, char *zone, time_t t1, time_t t2)
+{
+ if (fp == NULL) {
+ fp = stdout;
+ }
+ dumptimeICU(fp, t1);
+ fprintf(fp, " > ");
+ dumptimeICU(fp, t2);
+ fprintf(fp, "\n");
+}
+
+static void dumptimeICU(FILE * fp, time_t t)
+{
+ static const char wday_name[][3] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ struct tm gmt;
+ struct tm loc;
+ register int lead;
+ register int trail;
+ long offset;
+ long hour, min, sec;
+
+ loc = *my_localtime(&t);
+
+ trail = loc.tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
+ lead = loc.tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR + trail / DIVISOR;
+ trail %= DIVISOR;
+ if (trail < 0 && lead > 0) {
+ trail += DIVISOR;
+ --lead;
+ } else if (lead < 0 && trail > 0) {
+ trail -= DIVISOR;
+ ++lead;
+ }
+
+ fprintf(fp, "%04d-%02d-%02d", lead * DIVISOR + trail, loc.tm_mon + 1, loc.tm_mday);
+ fprintf(fp, " %.3s ", wday_name[loc.tm_wday]);
+ fprintf(fp, "%02d:%02d:%02d", loc.tm_hour, loc.tm_min, loc.tm_sec);
+
+ gmt = *gmtime(&t);
+ offset = delta(&loc, &gmt);
+ if (offset < 0) {
+ offset = -offset;
+ fprintf(fp, "-");
+ } else {
+ fprintf(fp, "+");
+ }
+
+ sec = offset % 60;
+ offset = (offset - sec) / 60;
+ min = offset % 60;
+ hour = offset / 60;
+
+ fprintf(fp, "%02ld", hour);
+ fprintf(fp, "%02ld", min);
+ fprintf(fp, "%02ld", sec);
+ fprintf(fp, "[DST=%d]", loc.tm_isdst);
+}
+
+static int getall(struct listentry ** namelist) {
+ int count = 0;
+ struct listentry dummyentry;
+ struct listentry * last = &dummyentry;
+
+ getzones(TZDIR, NULL, &last, &count);
+ if (count > 0) {
+ *namelist = dummyentry.next;
+ }
+
+ return count;
+}
+
+static void getzones(char * basedir, char * relpath, struct listentry ** last, int * count) {
+ char path[FILENAME_MAX + 1];
+ struct dirent * dir;
+ DIR * dp;
+
+ strcpy(path, basedir);
+ if (relpath != NULL) {
+ strcat(path, "/");
+ strcat(path, relpath);
+ }
+
+ if ((dp = opendir(path)) == NULL) {
+ /* file */
+ if (strstr(relpath, ".tab") == NULL && strcmp(relpath, "Etc/Unknown") != 0) {
+ char * pzonename;
+ listentry * pentry;
+
+ if ((pzonename = malloc(strlen(relpath) + 1)) == NULL) {
+ exit(EXIT_FAILURE);
+ }
+ strcpy(pzonename, relpath);
+
+ if ((pentry = malloc(sizeof(listentry))) == NULL) {
+ exit(EXIT_FAILURE);
+ }
+
+ pentry->name = pzonename;
+ pentry->next = NULL;
+ (*last)->next = pentry;
+ *last = pentry;
+ (*count)++;
+ }
+ } else {
+ /* directory */
+ while ((dir = readdir(dp)) != NULL) {
+ char subpath[FILENAME_MAX + 1];
+
+ if (strcmp(dir->d_name, ".") == 0
+ || strcmp(dir->d_name, "..") == 0) {
+ continue;
+ }
+ if (relpath != NULL) {
+ strcpy(subpath, relpath);
+ strcat(subpath, "/");
+ strcat(subpath, dir->d_name);
+ } else {
+ strcpy(subpath, dir->d_name);
+ }
+ getzones(basedir, subpath, last, count);
+ }
+ closedir(dp);
+ }
+}
+#endif
diff --git a/intl/icu/source/tools/tzcode/zic.c b/intl/icu/source/tools/tzcode/zic.c
new file mode 100644
index 0000000000..54576780d5
--- /dev/null
+++ b/intl/icu/source/tools/tzcode/zic.c
@@ -0,0 +1,3156 @@
+/*
+** This file is in the public domain, so clarified as of
+** 2006-07-17 by Arthur David Olson.
+*/
+
+/* Enable extensions and modifications for ICU. */
+#define ICU
+
+/* Continue executing after link failure. Even if ICU is undefined
+ * (for vanilla zic behavior), ICU_LINKS should be defined, since zic
+ * appears to fail on the 2003 data the first time through during the
+ * linking phase. Running zic twice, with ICU_LINKS defined, causes
+ * links to be handled correctly. */
+#define ICU_LINKS
+
+#define LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
+
+#ifdef ICU
+/* These constants are embedded in dynamically generated header
+ * version.h in the standard tzcode distribution. */
+static char const PKGVERSION[]="N/A";
+static char const TZVERSION[]="N/A";
+static char const REPORT_BUGS_TO[]="N/A";
+#else
+#include "version.h"
+#endif
+#include "private.h"
+#include "locale.h"
+#include "tzfile.h"
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+#define ZIC_VERSION_PRE_2013 '2'
+#define ZIC_VERSION '3'
+
+typedef int_fast64_t zic_t;
+#define ZIC_MIN INT_FAST64_MIN
+#define ZIC_MAX INT_FAST64_MAX
+#define SCNdZIC SCNdFAST64
+
+#ifndef ZIC_MAX_ABBR_LEN_WO_WARN
+#define ZIC_MAX_ABBR_LEN_WO_WARN 6
+#endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
+
+#if HAVE_SYS_STAT_H
+#include "sys/stat.h"
+#endif
+#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
+
+#ifdef ICU
+#include "tz2icu.h"
+#endif
+
+/*
+** On some ancient hosts, predicates like `isspace(C)' are defined
+** only if isascii(C) || C == EOF. Modern hosts obey the C Standard,
+** which says they are defined only if C == ((unsigned char) C) || C == EOF.
+** Neither the C Standard nor Posix require that `isascii' exist.
+** For portability, we check both ancient and modern requirements.
+** If isascii is not defined, the isascii check succeeds trivially.
+*/
+#include "ctype.h"
+#ifndef isascii
+#define isascii(x) 1
+#endif
+
+#define end(cp) (strchr((cp), '\0'))
+
+struct rule {
+ const char * r_filename;
+ int r_linenum;
+ const char * r_name;
+
+ zic_t r_loyear; /* for example, 1986 */
+ zic_t r_hiyear; /* for example, 1986 */
+ const char * r_yrtype;
+ int r_lowasnum;
+ int 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 */
+ int r_todisstd; /* above is standard time if true */
+ /* or wall clock time if false */
+ int r_todisgmt; /* above is GMT if true */
+ /* or local time if false */
+ zic_t r_stdoff; /* offset from standard time */
+ const char * r_abbrvar; /* variable part of abbreviation */
+
+ int 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;
+ int z_linenum;
+
+ const char * z_name;
+ zic_t z_gmtoff;
+ const char * z_rule;
+ const char * z_format;
+
+ zic_t z_stdoff;
+
+ struct rule * z_rules;
+ int z_nrules;
+
+ struct rule z_untilrule;
+ zic_t z_untiltime;
+};
+
+extern int getopt(int argc, char * const argv[],
+ const char * options);
+extern int link(const char * fromname, const char * toname);
+extern char * optarg;
+extern int optind;
+
+#if ! HAVE_LINK
+# define link(from, to) (-1)
+#endif
+#if ! HAVE_SYMLINK
+# define symlink(from, to) (-1)
+#endif
+
+static void addtt(zic_t starttime, int type);
+#ifdef ICU
+static int addtype(const zic_t gmtoff, const zic_t rawoff, const zic_t dstoff,
+ char *const abbr, int isdst,
+ int ttisstd, int ttisgmt);
+#else
+static int addtype(zic_t gmtoff, const char * abbr, int isdst,
+ int ttisstd, int ttisgmt);
+#endif
+static void leapadd(zic_t t, int positive, int rolling, int count);
+static void adjleap(void);
+static void associate(void);
+static void dolink(const char * fromfield, const char * tofield);
+static char ** getfields(char * buf);
+static zic_t gethms(const char * string, const char * errstrng,
+ int signable);
+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 int inzcont(char ** fields, int nfields);
+static int inzone(char ** fields, int nfields);
+static int inzsub(char ** fields, int nfields, int iscont);
+static int itsdir(const char * name);
+static int lowerit(int c);
+static int mkdirs(char * filename);
+static void newabbr(const char * abbr);
+static zic_t oadd(zic_t t1, zic_t t2);
+static void outzone(const struct zone * zp, int 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);
+static int yearistype(int year, const char * type);
+#ifdef ICU
+static void emit_icu_zone(FILE* f, const char* zoneName, int zoneOffset,
+ const struct rule* rule,
+ int ruleIndex, int startYear);
+static void emit_icu_link(FILE* f, const char* from, const char* to);
+static void emit_icu_rule(FILE* f, const struct rule* r, int ruleIndex);
+static int add_icu_final_rules(const struct rule* r1, const struct rule* r2);
+#endif
+
+static int charcnt;
+static int errors;
+static const char * filename;
+static int leapcnt;
+static int leapseen;
+static zic_t leapminyear;
+static zic_t leapmaxyear;
+static int linenum;
+static int max_abbrvar_len;
+static int max_format_len;
+static zic_t max_year;
+static zic_t min_year;
+static int noise;
+static const char * rfilename;
+static int rlinenum;
+static const char * progname;
+static int timecnt;
+static int timecnt_alloc;
+static int typecnt;
+
+/*
+** Line codes.
+*/
+
+#define LC_RULE 0
+#define LC_ZONE 1
+#define LC_LINK 2
+#define LC_LEAP 3
+
+/*
+** Which fields are which on a Zone line.
+*/
+
+#define ZF_NAME 1
+#define ZF_GMTOFF 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_GMTOFF 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_STDOFF 8
+#define RF_ABBRVAR 9
+#define RULE_FIELDS 10
+
+/*
+** Which fields are which on a Link line.
+*/
+
+#define LF_FROM 1
+#define LF_TO 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
+
+/*
+** Year synonyms.
+*/
+
+#define YR_MINIMUM 0
+#define YR_MAXIMUM 1
+#define YR_ONLY 2
+
+static struct rule * rules;
+static int nrules; /* number of rules */
+static int nrules_alloc;
+
+static struct zone * zones;
+static int nzones; /* number of zones */
+static int nzones_alloc;
+
+struct link {
+ const char * l_filename;
+ int l_linenum;
+ const char * l_from;
+ const char * l_to;
+};
+
+static struct link * links;
+static int nlinks;
+static int nlinks_alloc;
+
+struct lookup {
+ const char * l_word;
+ const int l_value;
+};
+
+#ifdef ICU
+/* Indices into rules[] for final rules. They will occur in pairs,
+ * with finalRules[i] occurring before finalRules[i+1] in the year.
+ * Each zone need only store a start year, a standard offset, and an
+ * index into finalRules[]. FinalRules[] are aliases into rules[]. */
+static const struct rule ** finalRules = NULL;
+static int finalRulesCount = 0;
+#endif
+
+static struct lookup const * byword(const char * string,
+ const struct lookup * lp);
+
+static struct lookup const line_codes[] = {
+ { "Rule", LC_RULE },
+ { "Zone", LC_ZONE },
+ { "Link", LC_LINK },
+ { "Leap", LC_LEAP },
+ { 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;
+ unsigned char type;
+} * attypes;
+static zic_t gmtoffs[TZ_MAX_TYPES];
+#ifdef ICU
+/* gmtoffs[i] = rawoffs[i] + dstoffs[i] */
+static zic_t rawoffs[TZ_MAX_TYPES];
+static zic_t dstoffs[TZ_MAX_TYPES];
+#endif
+static char isdsts[TZ_MAX_TYPES];
+static unsigned char abbrinds[TZ_MAX_TYPES];
+static char ttisstds[TZ_MAX_TYPES];
+static char ttisgmts[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 _Noreturn void
+memory_exhausted(const char *msg)
+{
+ fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
+ exit(EXIT_FAILURE);
+}
+
+static ATTRIBUTE_PURE size_t
+size_product(size_t nitems, size_t itemsize)
+{
+ if (SIZE_MAX / itemsize < nitems)
+ memory_exhausted("size overflow");
+ return nitems * itemsize;
+}
+
+static ATTRIBUTE_PURE void *
+memcheck(void *const ptr)
+{
+ if (ptr == NULL)
+ memory_exhausted(strerror(errno));
+ return ptr;
+}
+
+#define emalloc(size) memcheck(malloc(size))
+#define erealloc(ptr, size) memcheck(realloc(ptr, size))
+#define ecpyalloc(ptr) memcheck(icpyalloc(ptr))
+#define ecatalloc(oldp, newp) memcheck(icatalloc((oldp), (newp)))
+
+static void *
+growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc)
+{
+ if (nitems < *nitems_alloc)
+ return ptr;
+ else {
+ int amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX;
+ if ((amax - 1) / 3 * 2 < *nitems_alloc)
+ memory_exhausted("int overflow");
+ *nitems_alloc = *nitems_alloc + (*nitems_alloc >> 1) + 1;
+ return erealloc(ptr, size_product(*nitems_alloc, itemsize));
+ }
+}
+
+/*
+** Error handling.
+*/
+
+static void
+eats(const char *const name, const int num, const char *const rname,
+ const int rnum)
+{
+ filename = name;
+ linenum = num;
+ rfilename = rname;
+ rlinenum = rnum;
+}
+
+static void
+eat(const char *const name, const int num)
+{
+ eats(name, num, NULL, -1);
+}
+
+static void ATTRIBUTE_FORMAT((printf, 1, 0))
+verror(const char *const string, va_list args)
+{
+ /*
+ ** Match the format of "cc" to allow sh users to
+ ** zic ... 2>&1 | error -t "*" -v
+ ** on BSD systems.
+ */
+ fprintf(stderr, _("\"%s\", line %d: "), filename, linenum);
+ vfprintf(stderr, string, args);
+ if (rfilename != NULL)
+ (void) fprintf(stderr, _(" (rule from \"%s\", line %d)"),
+ rfilename, rlinenum);
+ (void) fprintf(stderr, "\n");
+ ++errors;
+}
+
+static void ATTRIBUTE_FORMAT((printf, 1, 2))
+error(const char *const string, ...)
+{
+ va_list args;
+ va_start(args, string);
+ verror(string, args);
+ va_end(args);
+}
+
+static void ATTRIBUTE_FORMAT((printf, 1, 2))
+warning(const char *const string, ...)
+{
+ va_list args;
+ fprintf(stderr, _("warning: "));
+ va_start(args, string);
+ verror(string, args);
+ va_end(args);
+ --errors;
+}
+
+static _Noreturn void
+usage(FILE *stream, int status)
+{
+ (void) fprintf(stream, _("%s: usage is %s \
+[ --version ] [ --help ] [ -v ] [ -l localtime ] [ -p posixrules ] \\\n\
+\t[ -d directory ] [ -L leapseconds ] [ -y yearistype ] [ filename ... ]\n\
+\n\
+Report bugs to %s.\n"),
+ progname, progname, REPORT_BUGS_TO);
+ exit(status);
+}
+
+#ifdef ICU
+/* File into which we will write supplemental ICU data. */
+static FILE * icuFile;
+
+static void
+emit_icu_zone(FILE* f, const char* zoneName, int zoneOffset,
+ const struct rule* rule,
+ int ruleIndex, int startYear) {
+ /* machine-readable section */
+ fprintf(f, "zone %s %d %d %s", zoneName, zoneOffset, startYear, rule->r_name);
+
+ /* human-readable section */
+ fprintf(f, " # zone %s, offset %d, year >= %d, rule %s (%d)\n",
+ zoneName, zoneOffset, startYear,
+ rule->r_name, ruleIndex);
+}
+
+static void
+emit_icu_link(FILE* f, const char* from, const char* to) {
+ /* machine-readable section */
+ fprintf(f, "link %s %s\n", from, to);
+}
+
+static const char* DYCODE[] = {"DOM", "DOWGEQ", "DOWLEQ"};
+
+static void
+emit_icu_rule(FILE* f, const struct rule* r, int ruleIndex) {
+ if (r->r_yrtype != NULL) {
+ warning("year types not supported by ICU");
+ fprintf(stderr, "rule %s, file %s, line %d\n",
+ r->r_name, r->r_filename, r->r_linenum);
+ }
+
+ /* machine-readable section */
+ fprintf(f, "rule %s %s %d %d %d %lld %d %d %lld",
+ r->r_name, DYCODE[r->r_dycode],
+ r->r_month, r->r_dayofmonth,
+ (r->r_dycode == DC_DOM ? -1 : r->r_wday),
+ r->r_tod, r->r_todisstd, r->r_todisgmt, r->r_stdoff
+ );
+
+ /* human-readable section */
+ fprintf(f, " # %d: %s, file %s, line %d",
+ ruleIndex, r->r_name, r->r_filename, r->r_linenum);
+ fprintf(f, ", mode %s", DYCODE[r->r_dycode]);
+ fprintf(f, ", %s, dom %d", mon_names[r->r_month].l_word, r->r_dayofmonth);
+ if (r->r_dycode != DC_DOM) {
+ fprintf(f, ", %s", wday_names[r->r_wday].l_word);
+ }
+ fprintf(f, ", time %lld", r->r_tod);
+ fprintf(f, ", isstd %d", r->r_todisstd);
+ fprintf(f, ", isgmt %d", r->r_todisgmt);
+ fprintf(f, ", offset %lld", r->r_stdoff);
+ fprintf(f, "\n");
+}
+
+static int
+add_icu_final_rules(const struct rule* r1, const struct rule* r2) {
+ int i;
+
+ for (i=0; i<finalRulesCount; ++i) { /* i+=2 should work too */
+ if (r1==finalRules[i]) return i; /* [sic] pointer comparison */
+ }
+
+ finalRules = (const struct rule**) (void*) erealloc((char *) finalRules,
+ (finalRulesCount + 2) * sizeof(*finalRules));
+ finalRules[finalRulesCount++] = r1;
+ finalRules[finalRulesCount++] = r2;
+ return finalRulesCount - 2;
+}
+#endif
+
+static const char * psxrules;
+static const char * lcltime;
+static const char * directory;
+static const char * leapsec;
+static const char * yitcommand;
+
+int
+main(int argc, char **argv)
+{
+ register int i;
+ register int j;
+ register int c;
+
+#ifdef S_IWGRP
+ (void) umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
+#endif
+#if HAVE_GETTEXT
+ (void) setlocale(LC_ALL, "");
+#ifdef TZ_DOMAINDIR
+ (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
+#endif /* defined TEXTDOMAINDIR */
+ (void) textdomain(TZ_DOMAIN);
+#endif /* HAVE_GETTEXT */
+ progname = argv[0];
+ if (TYPE_BIT(zic_t) < 64) {
+ (void) fprintf(stderr, "%s: %s\n", progname,
+ _("wild compilation-time specification of zic_t"));
+ exit(EXIT_FAILURE);
+ }
+ for (i = 1; i < argc; ++i)
+ if (strcmp(argv[i], "--version") == 0) {
+ (void) printf("zic %s%s\n", PKGVERSION, TZVERSION);
+ exit(EXIT_SUCCESS);
+ } else if (strcmp(argv[i], "--help") == 0) {
+ usage(stdout, EXIT_SUCCESS);
+ }
+ while ((c = getopt(argc, argv, "d:l:p:L:vsy:")) != EOF && c != -1)
+ switch (c) {
+ default:
+ usage(stderr, EXIT_FAILURE);
+ case 'd':
+ if (directory == NULL)
+ directory = optarg;
+ else {
+ (void) fprintf(stderr,
+_("%s: More than one -d option specified\n"),
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'l':
+ if (lcltime == NULL)
+ lcltime = optarg;
+ else {
+ (void) fprintf(stderr,
+_("%s: More than one -l option specified\n"),
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'p':
+ if (psxrules == NULL)
+ psxrules = optarg;
+ else {
+ (void) fprintf(stderr,
+_("%s: More than one -p option specified\n"),
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'y':
+ if (yitcommand == NULL)
+ yitcommand = optarg;
+ else {
+ (void) fprintf(stderr,
+_("%s: More than one -y option specified\n"),
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'L':
+ if (leapsec == NULL)
+ leapsec = optarg;
+ else {
+ (void) fprintf(stderr,
+_("%s: More than one -L option specified\n"),
+ progname);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'v':
+ noise = true;
+ break;
+ case 's':
+ (void) printf("%s: -s ignored\n", progname);
+ break;
+ }
+ if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
+ usage(stderr, EXIT_FAILURE); /* usage message by request */
+ if (directory == NULL)
+ directory = TZDIR;
+ if (yitcommand == NULL)
+ yitcommand = "yearistype";
+
+ if (optind < argc && leapsec != NULL) {
+ infile(leapsec);
+ adjleap();
+ }
+
+#ifdef ICU
+ if ((icuFile = fopen(ICU_ZONE_FILE, "w")) == NULL) {
+ const char *e = strerror(errno);
+ (void) fprintf(stderr, _("%s: Can't open %s: %s\n"),
+ progname, ICU_ZONE_FILE, e);
+ (void) exit(EXIT_FAILURE);
+ }
+#endif
+ for (i = optind; i < argc; ++i)
+ infile(argv[i]);
+ if (errors)
+ exit(EXIT_FAILURE);
+ associate();
+ 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_from, links[i].l_to);
+#ifdef ICU
+ emit_icu_link(icuFile, links[i].l_from, links[i].l_to);
+#endif
+ if (noise)
+ for (j = 0; j < nlinks; ++j)
+ if (strcmp(links[i].l_to,
+ links[j].l_from) == 0)
+ warning(_("link to link"));
+ }
+ if (lcltime != NULL) {
+ eat("command line", 1);
+ dolink(lcltime, TZDEFAULT);
+ }
+ if (psxrules != NULL) {
+ eat("command line", 1);
+ dolink(psxrules, TZDEFRULES);
+ }
+#ifdef ICU
+ for (i=0; i<finalRulesCount; ++i) {
+ emit_icu_rule(icuFile, finalRules[i], i);
+ }
+#endif /*ICU*/
+ return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+static void
+dolink(const char *const fromfield, const char *const tofield)
+{
+ register char * fromname;
+ register char * toname;
+
+ if (fromfield[0] == '/')
+ fromname = ecpyalloc(fromfield);
+ else {
+ fromname = ecpyalloc(directory);
+ fromname = ecatalloc(fromname, "/");
+ fromname = ecatalloc(fromname, fromfield);
+ }
+ if (tofield[0] == '/')
+ toname = ecpyalloc(tofield);
+ else {
+ toname = ecpyalloc(directory);
+ toname = ecatalloc(toname, "/");
+ toname = ecatalloc(toname, tofield);
+ }
+ /*
+ ** We get to be careful here since
+ ** there's a fair chance of root running us.
+ */
+ if (!itsdir(toname))
+ (void) remove(toname);
+ if (link(fromname, toname) != 0
+ && access(fromname, F_OK) == 0 && !itsdir(fromname)) {
+ int result;
+
+ if (mkdirs(toname) != 0)
+ exit(EXIT_FAILURE);
+
+ result = link(fromname, toname);
+ if (result != 0) {
+ const char *s = fromfield;
+ const char *t;
+ register char * symlinkcontents = NULL;
+
+ do
+ t = s;
+ while ((s = strchr(s, '/'))
+ && ! strncmp (fromfield, tofield,
+ ++s - fromfield));
+
+ for (s = tofield + (t - fromfield);
+ (s = strchr(s, '/'));
+ s++)
+ symlinkcontents =
+ ecatalloc(symlinkcontents,
+ "../");
+ symlinkcontents = ecatalloc(symlinkcontents, t);
+ result = symlink(symlinkcontents, toname);
+ if (result == 0)
+warning(_("hard link failed, symbolic link used"));
+ free(symlinkcontents);
+ }
+ if (result != 0) {
+ FILE *fp, *tp;
+ int c;
+ fp = fopen(fromname, "rb");
+ if (!fp) {
+ const char *e = strerror(errno);
+ (void) fprintf(stderr,
+ _("%s: Can't read %s: %s\n"),
+ progname, fromname, e);
+ exit(EXIT_FAILURE);
+ }
+ tp = fopen(toname, "wb");
+ if (!tp) {
+ const char *e = strerror(errno);
+ (void) fprintf(stderr,
+ _("%s: Can't create %s: %s\n"),
+ progname, toname, e);
+ exit(EXIT_FAILURE);
+ }
+ while ((c = getc(fp)) != EOF)
+ putc(c, tp);
+ if (ferror(fp) || fclose(fp)) {
+ (void) fprintf(stderr,
+ _("%s: Error reading %s\n"),
+ progname, fromname);
+ exit(EXIT_FAILURE);
+ }
+ if (ferror(tp) || fclose(tp)) {
+ (void) fprintf(stderr,
+ _("%s: Error writing %s\n"),
+ progname, toname);
+ exit(EXIT_FAILURE);
+ }
+ warning(_("link failed, copy used"));
+#ifndef ICU_LINKS
+ exit(EXIT_FAILURE);
+#endif
+ }
+ }
+ free(fromname);
+ free(toname);
+}
+
+#define TIME_T_BITS_IN_FILE 64
+
+static const zic_t min_time = (zic_t) -1 << (TIME_T_BITS_IN_FILE - 1);
+static const zic_t max_time = -1 - ((zic_t) -1 << (TIME_T_BITS_IN_FILE - 1));
+
+static int
+itsdir(const char *const name)
+{
+ register char * myname;
+ register int accres;
+
+ myname = ecpyalloc(name);
+ myname = ecatalloc(myname, "/.");
+ accres = access(myname, F_OK);
+ free(myname);
+ return accres == 0;
+}
+
+/*
+** 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)
+{
+ register struct zone * zp;
+ register struct rule * rp;
+ register int base, out;
+ register int i, j;
+
+ if (nrules != 0) {
+ (void) 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_stdoff = gethms(zp->z_rule, _("unruly zone"),
+ true);
+ /*
+ ** Note, though, that if there's no rule,
+ ** a '%s' in the format is a bad thing.
+ */
+ if (strchr(zp->z_format, '%') != 0)
+ error("%s", _("%s in ruleless zone"));
+ }
+ }
+ if (errors)
+ exit(EXIT_FAILURE);
+}
+
+static void
+infile(const char *name)
+{
+ register FILE * fp;
+ register char ** fields;
+ register char * cp;
+ register const struct lookup * lp;
+ register int nfields;
+ register int wantcont;
+ register int 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);
+
+ (void) fprintf(stderr, _("%s: Can't 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) {
+ /* nothing to do */
+ } else if (wantcont) {
+ wantcont = inzcont(fields, nfields);
+ } else {
+ lp = byword(fields[0], line_codes);
+ if (lp == NULL)
+ error(_("input line of unknown type"));
+ else switch ((int) (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:
+ if (name != leapsec)
+ (void) fprintf(stderr,
+_("%s: Leap line in non leap seconds file %s\n"),
+ progname, name);
+ else inleap(fields, nfields);
+ wantcont = false;
+ break;
+ default: /* "cannot happen" */
+ (void) fprintf(stderr,
+_("%s: panic: Invalid l_value %d\n"),
+ progname, lp->l_value);
+ exit(EXIT_FAILURE);
+ }
+ }
+ free(fields);
+ }
+ if (ferror(fp)) {
+ (void) fprintf(stderr, _("%s: Error reading %s\n"),
+ progname, filename);
+ exit(EXIT_FAILURE);
+ }
+ if (fp != stdin && fclose(fp)) {
+ const char *e = strerror(errno);
+
+ (void) fprintf(stderr, _("%s: Error closing %s: %s\n"),
+ progname, filename, e);
+ exit(EXIT_FAILURE);
+ }
+ 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(const char *string, const char *const errstring, const int signable)
+{
+ zic_t hh;
+ int mm, ss, sign;
+
+ if (string == NULL || *string == '\0')
+ return 0;
+ if (!signable)
+ sign = 1;
+ else if (*string == '-') {
+ sign = -1;
+ ++string;
+ } else sign = 1;
+ if (sscanf(string, scheck(string, "%"SCNdZIC), &hh) == 1)
+ mm = ss = 0;
+ else if (sscanf(string, scheck(string, "%"SCNdZIC":%d"), &hh, &mm) == 2)
+ ss = 0;
+ else if (sscanf(string, scheck(string, "%"SCNdZIC":%d:%d"),
+ &hh, &mm, &ss) != 3) {
+ error("%s", errstring);
+ return 0;
+ }
+ if (hh < 0 ||
+ mm < 0 || mm >= MINSPERHOUR ||
+ ss < 0 || ss > SECSPERMIN) {
+ error("%s", errstring);
+ return 0;
+ }
+ if (ZIC_MAX / SECSPERHOUR < hh) {
+ error(_("time overflow"));
+ return 0;
+ }
+ if (noise && hh == HOURSPERDAY && mm == 0 && ss == 0)
+ warning(_("24:00 not handled by pre-1998 versions of zic"));
+ 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 * hh * SECSPERHOUR,
+ sign * (mm * SECSPERMIN + ss));
+}
+
+static void
+inrule(register char **const fields, const int nfields)
+{
+ static struct rule r;
+
+ if (nfields != RULE_FIELDS) {
+ error(_("wrong number of fields on Rule line"));
+ return;
+ }
+ if (*fields[RF_NAME] == '\0') {
+ error(_("nameless rule"));
+ return;
+ }
+ r.r_filename = filename;
+ r.r_linenum = linenum;
+ r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), true);
+ 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 int
+inzone(register char **const fields, const int nfields)
+{
+ register int i;
+
+ if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) {
+ error(_("wrong number of fields on Zone line"));
+ return false;
+ }
+ if (strcmp(fields[ZF_NAME], TZDEFAULT) == 0 && lcltime != NULL) {
+ 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 int
+inzcont(register char **const fields, const 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 int
+inzsub(register char **const fields, const int nfields, const int iscont)
+{
+ register char * cp;
+ static struct zone z;
+ register int i_gmtoff, i_rule, i_format;
+ register int i_untilyear, i_untilmonth;
+ register int i_untilday, i_untiltime;
+ register int hasuntil;
+
+ if (iscont) {
+ i_gmtoff = ZFC_GMTOFF;
+ 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 {
+ i_gmtoff = ZF_GMTOFF;
+ 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_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"), true);
+ if ((cp = strchr(fields[i_format], '%')) != 0) {
+ if (*++cp != 's' || strchr(cp, '%') != 0) {
+ error(_("invalid abbreviation format"));
+ return false;
+ }
+ }
+ z.z_rule = ecpyalloc(fields[i_rule]);
+ z.z_format = ecpyalloc(fields[i_format]);
+ 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 void
+inleap(register char ** const fields, const int nfields)
+{
+ register const char * cp;
+ register const struct lookup * lp;
+ register int i, j;
+ zic_t year;
+ int month, day;
+ zic_t dayoff, tod;
+ zic_t t;
+
+ if (nfields != LEAP_FIELDS) {
+ error(_("wrong number of fields on Leap line"));
+ return;
+ }
+ dayoff = 0;
+ cp = fields[LP_YEAR];
+ if (sscanf(cp, scheck(cp, "%"SCNdZIC), &year) != 1) {
+ /*
+ ** Leapin' Lizards!
+ */
+ error(_("invalid leaping year"));
+ return;
+ }
+ 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;
+ }
+ 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, scheck(cp, "%d"), &day) != 1 ||
+ day <= 0 || day > len_months[isleap(year)][month]) {
+ error(_("invalid day of month"));
+ return;
+ }
+ dayoff = oadd(dayoff, day - 1);
+ if (dayoff < 0 && !TYPE_SIGNED(zic_t)) {
+ error(_("time before zero"));
+ return;
+ }
+ if (dayoff < min_time / SECSPERDAY) {
+ error(_("time too small"));
+ return;
+ }
+ if (dayoff > max_time / SECSPERDAY) {
+ error(_("time too large"));
+ return;
+ }
+ t = (zic_t) dayoff * SECSPERDAY;
+ tod = gethms(fields[LP_TIME], _("invalid time of day"), false);
+ cp = fields[LP_CORR];
+ {
+ register int positive;
+ int count;
+
+ if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */
+ positive = false;
+ count = 1;
+ } else if (strcmp(cp, "--") == 0) {
+ positive = false;
+ count = 2;
+ } else if (strcmp(cp, "+") == 0) {
+ positive = true;
+ count = 1;
+ } else if (strcmp(cp, "++") == 0) {
+ positive = true;
+ count = 2;
+ } else {
+ error(_("illegal CORRECTION field on Leap line"));
+ return;
+ }
+ if ((lp = byword(fields[LP_ROLL], leap_types)) == NULL) {
+ error(_(
+ "illegal Rolling/Stationary field on Leap line"
+ ));
+ return;
+ }
+ leapadd(tadd(t, tod), positive, lp->l_value, count);
+ }
+}
+
+static void
+inlink(register char **const fields, const int nfields)
+{
+ struct link l;
+
+ if (nfields != LINK_FIELDS) {
+ error(_("wrong number of fields on Link line"));
+ return;
+ }
+ if (*fields[LF_FROM] == '\0') {
+ error(_("blank FROM field on Link line"));
+ return;
+ }
+ if (*fields[LF_TO] == '\0') {
+ error(_("blank TO field on Link line"));
+ return;
+ }
+ l.l_filename = filename;
+ l.l_linenum = linenum;
+ l.l_from = ecpyalloc(fields[LF_FROM]);
+ l.l_to = ecpyalloc(fields[LF_TO]);
+ links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
+ links[nlinks++] = l;
+}
+
+static void
+rulesub(register struct rule *const rp,
+ const char *const loyearp,
+ const char *const hiyearp,
+ const char *const typep,
+ const char *const monthp,
+ const char *const dayp,
+ const char *const timep)
+{
+ register const struct lookup * lp;
+ register const char * cp;
+ register char * dp;
+ register char * ep;
+
+ if ((lp = byword(monthp, mon_names)) == NULL) {
+ error(_("invalid month name"));
+ return;
+ }
+ rp->r_month = lp->l_value;
+ rp->r_todisstd = false;
+ rp->r_todisgmt = false;
+ dp = ecpyalloc(timep);
+ if (*dp != '\0') {
+ ep = dp + strlen(dp) - 1;
+ switch (lowerit(*ep)) {
+ case 's': /* Standard */
+ rp->r_todisstd = true;
+ rp->r_todisgmt = false;
+ *ep = '\0';
+ break;
+ case 'w': /* Wall */
+ rp->r_todisstd = false;
+ rp->r_todisgmt = false;
+ *ep = '\0';
+ break;
+ case 'g': /* Greenwich */
+ case 'u': /* Universal */
+ case 'z': /* Zulu */
+ rp->r_todisstd = true;
+ rp->r_todisgmt = true;
+ *ep = '\0';
+ break;
+ }
+ }
+ rp->r_tod = gethms(dp, _("invalid time of day"), false);
+ free(dp);
+ /*
+ ** Year work.
+ */
+ cp = loyearp;
+ lp = byword(cp, begin_years);
+ rp->r_lowasnum = lp == NULL;
+ if (!rp->r_lowasnum) switch ((int) lp->l_value) {
+ case YR_MINIMUM:
+ rp->r_loyear = ZIC_MIN;
+ break;
+ case YR_MAXIMUM:
+ rp->r_loyear = ZIC_MAX;
+ break;
+ default: /* "cannot happen" */
+ (void) fprintf(stderr,
+ _("%s: panic: Invalid l_value %d\n"),
+ progname, lp->l_value);
+ exit(EXIT_FAILURE);
+ } else if (sscanf(cp, scheck(cp, "%"SCNdZIC), &rp->r_loyear) != 1) {
+ error(_("invalid starting year"));
+ return;
+ }
+ cp = hiyearp;
+ lp = byword(cp, end_years);
+ rp->r_hiwasnum = lp == NULL;
+ if (!rp->r_hiwasnum) switch ((int) 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" */
+ (void) fprintf(stderr,
+ _("%s: panic: Invalid l_value %d\n"),
+ progname, lp->l_value);
+ exit(EXIT_FAILURE);
+ } else if (sscanf(cp, scheck(cp, "%"SCNdZIC), &rp->r_hiyear) != 1) {
+ error(_("invalid ending year"));
+ return;
+ }
+ if (rp->r_loyear > rp->r_hiyear) {
+ error(_("starting year greater than ending year"));
+ return;
+ }
+ if (*typep == '\0')
+ rp->r_yrtype = NULL;
+ else {
+ if (rp->r_loyear == rp->r_hiyear) {
+ error(_("typed single year"));
+ return;
+ }
+ rp->r_yrtype = ecpyalloc(typep);
+ }
+ /*
+ ** Day work.
+ ** Accept things such as:
+ ** 1
+ ** last-Sunday
+ ** 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, '<')) != 0)
+ rp->r_dycode = DC_DOWLEQ;
+ else if ((ep = strchr(dp, '>')) != 0)
+ 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, scheck(ep, "%d"), &rp->r_dayofmonth) != 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 int_fast32_t val, char *const buf)
+{
+ register int i;
+ register 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)
+{
+ register int i;
+ register 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 int_fast32_t val, FILE *const fp)
+{
+ char buf[4];
+
+ convert(val, buf);
+ (void) fwrite(buf, sizeof buf, 1, fp);
+}
+
+static void
+puttzcode64(const zic_t val, FILE *const fp)
+{
+ char buf[8];
+
+ convert64(val, buf);
+ (void) 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);
+}
+
+static int
+is32(const zic_t x)
+{
+ return INT32_MIN <= x && x <= INT32_MAX;
+}
+
+static void
+writezone(const char *const name, const char *const string, char version)
+{
+ register FILE * fp;
+ register int i, j;
+ register int leapcnt32, leapi32;
+ register int timecnt32, timei32;
+ register int pass;
+ static char * fullname;
+ static const struct tzhead tzh0;
+ static struct tzhead tzh;
+ zic_t *ats = emalloc(size_product(timecnt, sizeof *ats + 1));
+ void *typesptr = ats + timecnt;
+ unsigned char *types = typesptr;
+
+ /*
+ ** Sort.
+ */
+ if (timecnt > 1)
+ (void) qsort(attypes, timecnt, sizeof *attypes, atcomp);
+ /*
+ ** Optimize.
+ */
+ {
+ int fromi;
+ int toi;
+
+ toi = 0;
+ fromi = 0;
+ while (fromi < timecnt && attypes[fromi].at < min_time)
+ ++fromi;
+ /*
+ ** Remember that type 0 is reserved.
+ */
+ if (isdsts[1] == 0)
+ while (fromi < timecnt && attypes[fromi].type == 1)
+ ++fromi; /* handled by default rule */
+ for ( ; fromi < timecnt; ++fromi) {
+ if (toi != 0 && ((attypes[fromi].at +
+ gmtoffs[attypes[toi - 1].type]) <=
+ (attypes[toi - 1].at + gmtoffs[toi == 1 ? 0
+ : attypes[toi - 2].type]))) {
+ attypes[toi - 1].type =
+ attypes[fromi].type;
+ continue;
+ }
+ if (toi == 0 ||
+ attypes[toi - 1].type != attypes[fromi].type)
+ attypes[toi++] = attypes[fromi];
+ }
+ timecnt = toi;
+ }
+ /*
+ ** 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;
+ }
+ }
+ /*
+ ** Figure out 32-bit-limited starts and counts.
+ */
+ timecnt32 = timecnt;
+ timei32 = 0;
+ leapcnt32 = leapcnt;
+ leapi32 = 0;
+ while (timecnt32 > 0 && !is32(ats[timecnt32 - 1]))
+ --timecnt32;
+ while (timecnt32 > 0 && !is32(ats[timei32])) {
+ --timecnt32;
+ ++timei32;
+ }
+ while (leapcnt32 > 0 && !is32(trans[leapcnt32 - 1]))
+ --leapcnt32;
+ while (leapcnt32 > 0 && !is32(trans[leapi32])) {
+ --leapcnt32;
+ ++leapi32;
+ }
+ fullname = erealloc(fullname,
+ strlen(directory) + 1 + strlen(name) + 1);
+ (void) sprintf(fullname, "%s/%s", directory, name);
+ /*
+ ** Remove old file, if any, to snap links.
+ */
+ if (!itsdir(fullname) && remove(fullname) != 0 && errno != ENOENT) {
+ const char *e = strerror(errno);
+
+ (void) fprintf(stderr, _("%s: Can't remove %s: %s\n"),
+ progname, fullname, e);
+ exit(EXIT_FAILURE);
+ }
+ if ((fp = fopen(fullname, "wb")) == NULL) {
+ if (mkdirs(fullname) != 0)
+ exit(EXIT_FAILURE);
+ if ((fp = fopen(fullname, "wb")) == NULL) {
+ const char *e = strerror(errno);
+
+ (void) fprintf(stderr, _("%s: Can't create %s: %s\n"),
+ progname, fullname, e);
+ exit(EXIT_FAILURE);
+ }
+ }
+ for (pass = 1; pass <= 2; ++pass) {
+ register int thistimei, thistimecnt;
+ register int thisleapi, thisleapcnt;
+ register int thistimelim, thisleaplim;
+ int writetype[TZ_MAX_TYPES];
+ int typemap[TZ_MAX_TYPES];
+ register int thistypecnt;
+ char thischars[TZ_MAX_CHARS];
+ char thischarcnt;
+ int indmap[TZ_MAX_CHARS];
+
+ if (pass == 1) {
+ thistimei = timei32;
+ thistimecnt = timecnt32;
+ thisleapi = leapi32;
+ thisleapcnt = leapcnt32;
+ } else {
+ thistimei = 0;
+ thistimecnt = timecnt;
+ thisleapi = 0;
+ thisleapcnt = leapcnt;
+ }
+ thistimelim = thistimei + thistimecnt;
+ thisleaplim = thisleapi + thisleapcnt;
+ /*
+ ** Remember that type 0 is reserved.
+ */
+ writetype[0] = false;
+ for (i = 1; i < typecnt; ++i)
+ writetype[i] = thistimecnt == timecnt;
+ if (thistimecnt == 0) {
+ /*
+ ** No transition times fall in the current
+ ** (32- or 64-bit) window.
+ */
+ if (typecnt != 0)
+ writetype[typecnt - 1] = true;
+ } else {
+ for (i = thistimei - 1; i < thistimelim; ++i)
+ if (i >= 0)
+ writetype[types[i]] = true;
+ /*
+ ** For America/Godthab and Antarctica/Palmer
+ */
+ /*
+ ** Remember that type 0 is reserved.
+ */
+ if (thistimei == 0)
+ writetype[1] = true;
+ }
+#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).
+ */
+ {
+ register 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 = 0; i < typecnt; ++i)
+ if (writetype[i]) {
+ if (isdsts[i])
+ hidst = i;
+ else histd = i;
+ }
+ if (hidst >= 0 && mrudst >= 0 && hidst != mrudst &&
+ gmtoffs[hidst] != gmtoffs[mrudst]) {
+ isdsts[mrudst] = -1;
+ type = addtype(gmtoffs[mrudst],
+#ifdef ICU
+ rawoffs[mrudst], dstoffs[mrudst],
+#endif
+ &chars[abbrinds[mrudst]],
+ true,
+ ttisstds[mrudst],
+ ttisgmts[mrudst]);
+ isdsts[mrudst] = true;
+ writetype[type] = true;
+ }
+ if (histd >= 0 && mrustd >= 0 && histd != mrustd &&
+ gmtoffs[histd] != gmtoffs[mrustd]) {
+ isdsts[mrustd] = -1;
+ type = addtype(gmtoffs[mrustd],
+#ifdef ICU
+ rawoffs[mrudst], dstoffs[mrudst],
+#endif
+ &chars[abbrinds[mrustd]],
+ false,
+ ttisstds[mrustd],
+ ttisgmts[mrustd]);
+ isdsts[mrustd] = false;
+ writetype[type] = true;
+ }
+ }
+#endif /* !defined LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */
+ thistypecnt = 0;
+ /*
+ ** Potentially, set type 0 to that of lowest-valued time.
+ */
+ if (thistimei > 0) {
+ for (i = 1; i < typecnt; ++i)
+ if (writetype[i] && !isdsts[i])
+ break;
+ if (i != types[thistimei - 1]) {
+ i = types[thistimei - 1];
+ gmtoffs[0] = gmtoffs[i];
+ isdsts[0] = isdsts[i];
+ ttisstds[0] = ttisstds[i];
+ ttisgmts[0] = ttisgmts[i];
+ abbrinds[0] = abbrinds[i];
+ writetype[0] = true;
+ writetype[i] = false;
+ }
+ }
+ for (i = 0; i < typecnt; ++i)
+ typemap[i] = writetype[i] ? thistypecnt++ : 0;
+ for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
+ indmap[i] = -1;
+ thischarcnt = 0;
+ for (i = 0; i < typecnt; ++i) {
+ register char * thisabbr;
+
+ if (!writetype[i])
+ continue;
+ if (indmap[abbrinds[i]] >= 0)
+ continue;
+ thisabbr = &chars[abbrinds[i]];
+ for (j = 0; j < thischarcnt; ++j)
+ if (strcmp(&thischars[j], thisabbr) == 0)
+ break;
+ if (j == thischarcnt) {
+ (void) strcpy(&thischars[(int) thischarcnt],
+ thisabbr);
+ thischarcnt += strlen(thisabbr) + 1;
+ }
+ indmap[abbrinds[i]] = j;
+ }
+#define DO(field) ((void) fwrite(tzh.field, sizeof tzh.field, 1, fp))
+ tzh = tzh0;
+#ifdef ICU
+ * (ICUZoneinfoVersion*) &tzh.tzh_reserved = TZ_ICU_VERSION;
+ (void) strncpy(tzh.tzh_magic, TZ_ICU_MAGIC, sizeof tzh.tzh_magic);
+#else
+ (void) strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
+#endif
+ tzh.tzh_version[0] = version;
+ convert(thistypecnt, tzh.tzh_ttisgmtcnt);
+ convert(thistypecnt, tzh.tzh_ttisstdcnt);
+ convert(thisleapcnt, tzh.tzh_leapcnt);
+ convert(thistimecnt, tzh.tzh_timecnt);
+ convert(thistypecnt, tzh.tzh_typecnt);
+ convert(thischarcnt, tzh.tzh_charcnt);
+ DO(tzh_magic);
+ DO(tzh_version);
+ DO(tzh_reserved);
+ DO(tzh_ttisgmtcnt);
+ DO(tzh_ttisstdcnt);
+ DO(tzh_leapcnt);
+ DO(tzh_timecnt);
+ DO(tzh_typecnt);
+ DO(tzh_charcnt);
+#undef DO
+ for (i = thistimei; i < thistimelim; ++i)
+ if (pass == 1)
+ puttzcode(ats[i], fp);
+ else puttzcode64(ats[i], fp);
+ for (i = thistimei; i < thistimelim; ++i) {
+ unsigned char uc;
+
+ uc = typemap[types[i]];
+ (void) fwrite(&uc, sizeof uc, 1, fp);
+ }
+ for (i = 0; i < typecnt; ++i)
+ if (writetype[i]) {
+#ifdef ICU
+ puttzcode(rawoffs[i], fp);
+ puttzcode(dstoffs[i], fp);
+#else
+ puttzcode(gmtoffs[i], fp);
+#endif
+ (void) putc(isdsts[i], fp);
+ (void) putc((unsigned char) indmap[abbrinds[i]], fp);
+ }
+ if (thischarcnt != 0)
+ (void) fwrite(thischars, sizeof thischars[0],
+ thischarcnt, fp);
+ for (i = thisleapi; i < thisleaplim; ++i) {
+ register 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], -gmtoffs[j]);
+ } else todo = trans[i];
+ if (pass == 1)
+ puttzcode(todo, fp);
+ else puttzcode64(todo, fp);
+ puttzcode(corr[i], fp);
+ }
+ for (i = 0; i < typecnt; ++i)
+ if (writetype[i])
+ (void) putc(ttisstds[i], fp);
+ for (i = 0; i < typecnt; ++i)
+ if (writetype[i])
+ (void) putc(ttisgmts[i], fp);
+ }
+ (void) fprintf(fp, "\n%s\n", string);
+ if (ferror(fp) || fclose(fp)) {
+ (void) fprintf(stderr, _("%s: Error writing %s\n"),
+ progname, fullname);
+ exit(EXIT_FAILURE);
+ }
+ free(ats);
+}
+
+static void
+doabbr(char *const abbr, const char *const format, const char *const letters,
+ const int isdst, const int doquotes)
+{
+ register char * cp;
+ register char * slashp;
+ register int len;
+
+ slashp = strchr(format, '/');
+ if (slashp == NULL) {
+ if (letters == NULL)
+ (void) strcpy(abbr, format);
+ else (void) sprintf(abbr, format, letters);
+ } else if (isdst) {
+ (void) strcpy(abbr, slashp + 1);
+ } else {
+ if (slashp > format)
+ (void) strncpy(abbr, format, slashp - format);
+ abbr[slashp - format] = '\0';
+ }
+ if (!doquotes)
+ return;
+ for (cp = abbr; *cp != '\0'; ++cp)
+ if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", *cp) == NULL &&
+ strchr("abcdefghijklmnopqrstuvwxyz", *cp) == NULL)
+ break;
+ len = strlen(abbr);
+ if (len > 0 && *cp == '\0')
+ return;
+ abbr[len + 2] = '\0';
+ abbr[len + 1] = '>';
+ for ( ; len > 0; --len)
+ abbr[len] = abbr[len - 1];
+ abbr[0] = '<';
+}
+
+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)
+{
+ register int hours;
+ register int minutes;
+ register int seconds;
+
+ result[0] = '\0';
+ if (offset < 0) {
+ (void) strcpy(result, "-");
+ offset = -offset;
+ }
+ seconds = offset % SECSPERMIN;
+ offset /= SECSPERMIN;
+ minutes = offset % MINSPERHOUR;
+ offset /= MINSPERHOUR;
+ hours = offset;
+ if (hours >= HOURSPERDAY * DAYSPERWEEK) {
+ result[0] = '\0';
+ return -1;
+ }
+ (void) sprintf(end(result), "%d", hours);
+ if (minutes != 0 || seconds != 0) {
+ (void) sprintf(end(result), ":%02d", minutes);
+ if (seconds != 0)
+ (void) sprintf(end(result), ":%02d", seconds);
+ }
+ return 0;
+}
+
+static int
+stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
+ const zic_t gmtoff)
+{
+ register zic_t tod = rp->r_tod;
+ register int compat = 0;
+
+ result = end(result);
+ if (rp->r_dycode == DC_DOM) {
+ register 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)
+ (void) sprintf(result, "%d", total + rp->r_dayofmonth - 1);
+ else
+ (void) sprintf(result, "J%d", total + rp->r_dayofmonth);
+ } else {
+ register int week;
+ register int wday = rp->r_wday;
+ register 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;
+ (void) sprintf(result, "M%d.%d.%d",
+ rp->r_month + 1, week, wday);
+ }
+ if (rp->r_todisgmt)
+ tod += gmtoff;
+ if (rp->r_todisstd && rp->r_stdoff == 0)
+ tod += dstoff;
+ if (tod != 2 * SECSPERMIN * MINSPERHOUR) {
+ (void) strcat(result, "/");
+ if (stringoffset(end(result), tod) != 0)
+ 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;
+}
+
+enum { YEAR_BY_YEAR_ZONE = 1 };
+
+static int
+stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
+{
+ register const struct zone * zp;
+ register struct rule * rp;
+ register struct rule * stdrp;
+ register struct rule * dstrp;
+ register int i;
+ register const char * abbrvar;
+ register int compat = 0;
+ register int c;
+ struct rule stdr, dstr;
+
+ result[0] = '\0';
+ 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_yrtype != NULL)
+ continue;
+ if (rp->r_stdoff == 0) {
+ 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.
+ */
+ register struct rule *stdabbrrp = NULL;
+ for (i = 0; i < zp->z_nrules; ++i) {
+ rp = &zp->z_rules[i];
+ if (rp->r_stdoff == 0 && rule_cmp(stdabbrrp, rp) < 0)
+ stdabbrrp = rp;
+ if (rule_cmp(stdrp, rp) < 0)
+ stdrp = rp;
+ }
+ /*
+ ** Horrid special case: if year is 2037,
+ ** presume this is a zone handled on a year-by-year basis;
+ ** do not try to apply a rule to the zone.
+ */
+ if (stdrp != NULL && stdrp->r_hiyear == 2037)
+ return YEAR_BY_YEAR_ZONE;
+
+ if (stdrp != NULL && stdrp->r_stdoff != 0) {
+ /* 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_todisgmt = false;
+ dstr.r_stdoff = stdrp->r_stdoff;
+ 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_stdoff;
+ stdr.r_todisstd = stdr.r_todisgmt = false;
+ stdr.r_stdoff = 0;
+ stdr.r_abbrvar
+ = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
+ dstrp = &dstr;
+ stdrp = &stdr;
+ }
+ }
+ if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0))
+ return -1;
+ abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
+ doabbr(result, zp->z_format, abbrvar, false, true);
+ if (stringoffset(end(result), -zp->z_gmtoff) != 0) {
+ result[0] = '\0';
+ return -1;
+ }
+ if (dstrp == NULL)
+ return compat;
+ doabbr(end(result), zp->z_format, dstrp->r_abbrvar, true, true);
+ if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR)
+ if (stringoffset(end(result),
+ -(zp->z_gmtoff + dstrp->r_stdoff)) != 0) {
+ result[0] = '\0';
+ return -1;
+ }
+ (void) strcat(result, ",");
+ c = stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff);
+ if (c < 0) {
+ result[0] = '\0';
+ return -1;
+ }
+ if (compat < c)
+ compat = c;
+ (void) strcat(result, ",");
+ c = stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff);
+ if (c < 0) {
+ result[0] = '\0';
+ return -1;
+ }
+ if (compat < c)
+ compat = c;
+ return compat;
+}
+
+static void
+outzone(const struct zone * const zpfirst, const int zonecount)
+{
+ register const struct zone * zp;
+ register struct rule * rp;
+ register int i, j;
+ register int usestart, useuntil;
+ register zic_t starttime, untiltime;
+ register zic_t gmtoff;
+ register zic_t stdoff;
+ register zic_t year;
+ register zic_t startoff;
+ register int startttisstd;
+ register int startttisgmt;
+ register int type;
+ register char * startbuf;
+ register char * ab;
+ register char * envvar;
+ register int max_abbr_len;
+ register int max_envvar_len;
+ register int prodstic; /* all rules are min to max */
+ register int compat;
+ register int do_extend;
+ register char version;
+#ifdef ICU
+ int finalRuleYear, finalRuleIndex;
+ const struct rule* finalRule1;
+ const struct rule* finalRule2;
+#endif
+
+ 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;
+ startttisgmt = false;
+ min_year = max_year = EPOCH_YEAR;
+ if (leapseen) {
+ updateminmax(leapminyear);
+ updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX));
+ }
+ /*
+ ** Reserve type 0.
+ */
+ gmtoffs[0] = isdsts[0] = ttisstds[0] = ttisgmts[0] = abbrinds[0] = -1;
+ typecnt = 1;
+ 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 || compat == YEAR_BY_YEAR_ZONE;
+#ifdef ICU
+ do_extend = 0;
+#endif
+ if (noise) {
+ if (!*envvar)
+ warning("%s %s",
+ _("no POSIX environment variable for zone"),
+ zpfirst->z_name);
+ else if (compat != 0 && compat != YEAR_BY_YEAR_ZONE) {
+ /* 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;
+ }
+ }
+ /*
+ ** For the benefit of older systems,
+ ** generate data from 1900 through 2037.
+ */
+ if (min_year > 1900)
+ min_year = 1900;
+ if (max_year < 2037)
+ max_year = 2037;
+ for (i = 0; i < zonecount; ++i) {
+ /*
+ ** A guess that may well be corrected later.
+ */
+ stdoff = 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;
+ gmtoff = zp->z_gmtoff;
+ eat(zp->z_filename, zp->z_linenum);
+ *startbuf = '\0';
+ startoff = zp->z_gmtoff;
+#ifdef ICU
+ finalRuleYear = finalRuleIndex = -1;
+ finalRule1 = finalRule2 = NULL;
+ if (i == (zonecount - 1)) { /* !useuntil */
+ /* Look for exactly 2 rules that end at 'max' and
+ * note them. Determine max(r_loyear) for the 2 of
+ * them. */
+ for (j=0; j<zp->z_nrules; ++j) {
+ rp = &zp->z_rules[j];
+ if (rp->r_hiyear == ZIC_MAX) {
+ if (rp->r_loyear > finalRuleYear) {
+ finalRuleYear = rp->r_loyear;
+ }
+ if (finalRule1 == NULL) {
+ finalRule1 = rp;
+ } else if (finalRule2 == NULL) {
+ finalRule2 = rp;
+ } else {
+ error("more than two max rules found (ICU)");
+ exit(EXIT_FAILURE);
+ }
+ } else if (rp->r_hiyear >= finalRuleYear) {
+ /* There might be an overriding non-max rule
+ * to be applied to a specific year after one of
+ * max rule's start year. For example,
+ *
+ * Rule Foo 2010 max ...
+ * Rule Foo 2015 only ...
+ *
+ * In this case, we need to change the start year of
+ * the final (max) rules to the next year. */
+ finalRuleYear = rp->r_hiyear + 1;
+
+ /* When above adjustment is done, max_year might need
+ * to be adjusted, so the final rule will be properly
+ * evaluated and emitted by the later code block.
+ *
+ * Note: This may push the start year of the final
+ * rules ahead by 1 year unnecessarily. For example,
+ * If there are two rules, non-max rule and max rule
+ * starting in the same year, such as
+ *
+ * Rule Foo 2010 only ....
+ * Rule Foo 2010 max ....
+ *
+ * In this case, the final (max) rule actually starts
+ * in 2010, instead of 2010. We could make this tool
+ * more intelligent to detect such situation. But pushing
+ * final rule start year to 1 year ahead (in the worst case)
+ * will just populate a few extra transitions, and it still
+ * works fine. So for now, we're not trying to put additional
+ * logic to optimize the case.
+ */
+ if (max_year < finalRuleYear) {
+ max_year = finalRuleYear;
+ }
+ }
+ }
+ if (finalRule1 != NULL) {
+ if (finalRule2 == NULL) {
+ warning("only one max rule found (ICU)");
+ finalRuleYear = finalRuleIndex = -1;
+ finalRule1 = NULL;
+ } else {
+ if (finalRule1->r_stdoff == finalRule2->r_stdoff) {
+ /* America/Resolute in 2009a uses a pair of rules
+ * which does not change the offset. ICU ignores
+ * such rules without actual time transitions. */
+ finalRuleYear = finalRuleIndex = -1;
+ finalRule1 = finalRule2 = NULL;
+ } else {
+ /* Swap if necessary so finalRule1 occurs before
+ * finalRule2 */
+ if (finalRule1->r_month > finalRule2->r_month) {
+ const struct rule* t = finalRule1;
+ finalRule1 = finalRule2;
+ finalRule2 = t;
+ }
+ /* Add final rule to our list */
+ finalRuleIndex = add_icu_final_rules(finalRule1, finalRule2);
+ }
+ }
+ }
+ }
+#endif
+
+ if (zp->z_nrules == 0) {
+ stdoff = zp->z_stdoff;
+ doabbr(startbuf, zp->z_format,
+ NULL, stdoff != 0, false);
+ type = addtype(oadd(zp->z_gmtoff, stdoff),
+#ifdef ICU
+ zp->z_gmtoff, stdoff,
+#endif
+ startbuf, stdoff != 0, startttisstd,
+ startttisgmt);
+ if (usestart) {
+ addtt(starttime, type);
+ usestart = false;
+ } else if (stdoff != 0)
+ addtt(min_time, 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);
+ */
+ 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 &&
+ yearistype(year, rp->r_yrtype);
+ if (rp->r_todo)
+ rp->r_temp = rpytime(rp, year);
+ }
+ for ( ; ; ) {
+ register int k;
+ register zic_t jtime, ktime;
+ register zic_t offset;
+
+ INITIALIZE(ktime);
+ if (useuntil) {
+ /*
+ ** Turn untiltime into UT
+ ** assuming the current gmtoff and
+ ** stdoff values.
+ */
+ untiltime = zp->z_untiltime;
+ if (!zp->z_untilrule.r_todisgmt)
+ untiltime = tadd(untiltime,
+ -gmtoff);
+ if (!zp->z_untilrule.r_todisstd)
+ untiltime = tadd(untiltime,
+ -stdoff);
+ }
+ /*
+ ** 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_todisgmt ? 0 : gmtoff;
+ if (!rp->r_todisstd)
+ offset = oadd(offset, stdoff);
+ 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;
+ }
+ }
+ if (k < 0)
+ break; /* go on to next year */
+ rp = &zp->z_rules[k];
+ rp->r_todo = false;
+ if (useuntil && ktime >= untiltime)
+ break;
+ stdoff = rp->r_stdoff;
+ if (usestart && ktime == starttime)
+ usestart = false;
+ if (usestart) {
+ if (ktime < starttime) {
+ startoff = oadd(zp->z_gmtoff,
+ stdoff);
+ doabbr(startbuf, zp->z_format,
+ rp->r_abbrvar,
+ rp->r_stdoff != 0,
+ false);
+ continue;
+ }
+ if (*startbuf == '\0' &&
+ startoff == oadd(zp->z_gmtoff,
+ stdoff)) {
+ doabbr(startbuf,
+ zp->z_format,
+ rp->r_abbrvar,
+ rp->r_stdoff !=
+ 0,
+ false);
+ }
+ }
+#ifdef ICU
+ if (year >= finalRuleYear && rp == finalRule1) {
+ /* We want to shift final year 1 year after
+ * the actual final rule takes effect (year + 1),
+ * because the previous type is valid until the first
+ * transition defined by the final rule. Otherwise
+ * we may see unexpected offset shift at the
+ * beginning of the year when the final rule takes
+ * effect.
+ *
+ * Note: This may results some 64bit second transitions
+ * at the very end (year 2038). ICU 4.2 or older releases
+ * cannot handle 64bit second transitions and they are
+ * dropped from zoneinfo.txt. */
+ emit_icu_zone(icuFile,
+ zpfirst->z_name, zp->z_gmtoff,
+ rp, finalRuleIndex, year + 1);
+ /* only emit this for the first year */
+ finalRule1 = NULL;
+ }
+#endif
+ eats(zp->z_filename, zp->z_linenum,
+ rp->r_filename, rp->r_linenum);
+ doabbr(ab, zp->z_format, rp->r_abbrvar,
+ rp->r_stdoff != 0, false);
+ offset = oadd(zp->z_gmtoff, rp->r_stdoff);
+#ifdef ICU
+ type = addtype(offset, zp->z_gmtoff, rp->r_stdoff,
+ ab, rp->r_stdoff != 0,
+ rp->r_todisstd, rp->r_todisgmt);
+#else
+ type = addtype(offset, ab, rp->r_stdoff != 0,
+ rp->r_todisstd, rp->r_todisgmt);
+#endif
+ addtt(ktime, type);
+ }
+ }
+ if (usestart) {
+ if (*startbuf == '\0' &&
+ zp->z_format != NULL &&
+ strchr(zp->z_format, '%') == NULL &&
+ strchr(zp->z_format, '/') == NULL)
+ (void) strcpy(startbuf, zp->z_format);
+ eat(zp->z_filename, zp->z_linenum);
+ if (*startbuf == '\0')
+error(_("can't determine time zone abbreviation to use just after until time"));
+ else addtt(starttime,
+#ifdef ICU
+ addtype(startoff,
+ zp->z_gmtoff, startoff - zp->z_gmtoff,
+ startbuf,
+ startoff != zp->z_gmtoff,
+ startttisstd,
+ startttisgmt));
+#else
+ addtype(startoff, startbuf,
+ startoff != zp->z_gmtoff,
+ startttisstd,
+ startttisgmt));
+#endif
+ }
+ /*
+ ** Now we may get to set starttime for the next zone line.
+ */
+ if (useuntil) {
+ startttisstd = zp->z_untilrule.r_todisstd;
+ startttisgmt = zp->z_untilrule.r_todisgmt;
+ starttime = zp->z_untiltime;
+ if (!startttisstd)
+ starttime = tadd(starttime, -stdoff);
+ if (!startttisgmt)
+ starttime = tadd(starttime, -gmtoff);
+ }
+ }
+ 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[0], i = 1; i < timecnt; i++)
+ if (attypes[i].at > lastat->at)
+ lastat = &attypes[i];
+ if (lastat->at < rpytime(&xr, max_year - 1)) {
+ /*
+ ** Create new type code for the redundant entry,
+ ** to prevent it being optimised away.
+ */
+ if (typecnt >= TZ_MAX_TYPES) {
+ error(_("too many local time types"));
+ exit(EXIT_FAILURE);
+ }
+ gmtoffs[typecnt] = gmtoffs[lastat->type];
+ isdsts[typecnt] = isdsts[lastat->type];
+ ttisstds[typecnt] = ttisstds[lastat->type];
+ ttisgmts[typecnt] = ttisgmts[lastat->type];
+ abbrinds[typecnt] = abbrinds[lastat->type];
+ ++typecnt;
+ addtt(rpytime(&xr, max_year + 1), typecnt-1);
+ }
+ }
+ writezone(zpfirst->z_name, envvar, version);
+ free(startbuf);
+ free(ab);
+ free(envvar);
+}
+
+static void
+addtt(const zic_t starttime, int type)
+{
+ if (starttime <= min_time ||
+ (timecnt == 1 && attypes[0].at < min_time)) {
+ gmtoffs[0] = gmtoffs[type];
+#ifdef ICU
+ rawoffs[0] = rawoffs[type];
+ dstoffs[0] = dstoffs[type];
+#endif
+ isdsts[0] = isdsts[type];
+ ttisstds[0] = ttisstds[type];
+ ttisgmts[0] = ttisgmts[type];
+ if (abbrinds[type] != 0)
+ (void) strcpy(chars, &chars[abbrinds[type]]);
+ abbrinds[0] = 0;
+ charcnt = strlen(chars) + 1;
+ typecnt = 1;
+ timecnt = 0;
+ type = 0;
+ }
+ attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
+ attypes[timecnt].at = starttime;
+ attypes[timecnt].type = type;
+ ++timecnt;
+}
+
+static int
+#ifdef ICU
+addtype(const zic_t gmtoff, const zic_t rawoff, const zic_t dstoff, char *const abbr, const int isdst,
+ const int ttisstd, const int ttisgmt)
+#else
+addtype(const zic_t gmtoff, const char *const abbr, const int isdst,
+ const int ttisstd, const int ttisgmt)
+#endif
+{
+ register int i, j;
+
+ if (isdst != true && isdst != false) {
+ error(_("internal error - addtype called with bad isdst"));
+ exit(EXIT_FAILURE);
+ }
+ if (ttisstd != true && ttisstd != false) {
+ error(_("internal error - addtype called with bad ttisstd"));
+ exit(EXIT_FAILURE);
+ }
+ if (ttisgmt != true && ttisgmt != false) {
+ error(_("internal error - addtype called with bad ttisgmt"));
+ exit(EXIT_FAILURE);
+ }
+#ifdef ICU
+ if (isdst != (dstoff != 0)) {
+ error(_("internal error - addtype called with bad isdst/dstoff"));
+ exit(EXIT_FAILURE);
+ }
+ if (gmtoff != (rawoff + dstoff)) {
+ error(_("internal error - addtype called with bad gmt/raw/dstoff"));
+ exit(EXIT_FAILURE);
+ }
+#endif
+ /*
+ ** See if there's already an entry for this zone type.
+ ** If so, just return its index.
+ */
+ for (i = 0; i < typecnt; ++i) {
+ if (gmtoff == gmtoffs[i] && isdst == isdsts[i] &&
+#ifdef ICU
+ rawoff == rawoffs[i] && dstoff == dstoffs[i] &&
+#endif
+ strcmp(abbr, &chars[abbrinds[i]]) == 0 &&
+ ttisstd == ttisstds[i] &&
+ ttisgmt == ttisgmts[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);
+ }
+ if (! (-1L - 2147483647L <= gmtoff && gmtoff <= 2147483647L)) {
+ error(_("UT offset out of range"));
+ exit(EXIT_FAILURE);
+ }
+ gmtoffs[i] = gmtoff;
+#ifdef ICU
+ rawoffs[i] = rawoff;
+ dstoffs[i] = dstoff;
+#endif
+ isdsts[i] = isdst;
+ ttisstds[i] = ttisstd;
+ ttisgmts[i] = ttisgmt;
+
+ for (j = 0; j < charcnt; ++j)
+ if (strcmp(&chars[j], abbr) == 0)
+ break;
+ if (j == charcnt)
+ newabbr(abbr);
+ abbrinds[i] = j;
+ ++typecnt;
+ return i;
+}
+
+static void
+leapadd(const zic_t t, const int positive, const int rolling, int count)
+{
+ register int i, j;
+
+ if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS) {
+ error(_("too many leap seconds"));
+ exit(EXIT_FAILURE);
+ }
+ for (i = 0; i < leapcnt; ++i)
+ if (t <= trans[i]) {
+ if (t == trans[i]) {
+ error(_("repeated leap second moment"));
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+ do {
+ for (j = leapcnt; j > i; --j) {
+ trans[j] = trans[j - 1];
+ corr[j] = corr[j - 1];
+ roll[j] = roll[j - 1];
+ }
+ trans[i] = t;
+ corr[i] = positive ? 1 : -count;
+ roll[i] = rolling;
+ ++leapcnt;
+ } while (positive && --count != 0);
+}
+
+static void
+adjleap(void)
+{
+ register int i;
+ register zic_t last = 0;
+
+ /*
+ ** propagate leap seconds forward
+ */
+ for (i = 0; i < leapcnt; ++i) {
+ trans[i] = tadd(trans[i], last);
+ last = corr[i] += last;
+ }
+}
+
+static int
+yearistype(const int year, const char *const type)
+{
+ static char * buf;
+ int result;
+
+ if (type == NULL || *type == '\0')
+ return true;
+ buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type));
+ (void) sprintf(buf, "%s %d %s", yitcommand, year, type);
+ result = system(buf);
+ if (WIFEXITED(result)) switch (WEXITSTATUS(result)) {
+ case 0:
+ return true;
+ case 1:
+ return false;
+ }
+ error(_("Wild result from command execution"));
+ (void) fprintf(stderr, _("%s: command was '%s', result was %d\n"),
+ progname, buf, result);
+ for ( ; ; )
+ exit(EXIT_FAILURE);
+}
+
+static int
+lowerit(int a)
+{
+ a = (unsigned char) a;
+ return (isascii(a) && isupper(a)) ? tolower(a) : a;
+}
+
+/* case-insensitive equality */
+static ATTRIBUTE_PURE int
+ciequal(register const char *ap, register const char *bp)
+{
+ while (lowerit(*ap) == lowerit(*bp++))
+ if (*ap++ == '\0')
+ return true;
+ return false;
+}
+
+static ATTRIBUTE_PURE int
+itsabbr(register const char *abbr, register 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;
+}
+
+static ATTRIBUTE_PURE const struct lookup *
+byword(register const char *const word,
+ register const struct lookup *const table)
+{
+ register const struct lookup * foundlp;
+ register const struct lookup * lp;
+
+ if (word == NULL || table == NULL)
+ return NULL;
+ /*
+ ** 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 (itsabbr(word, lp->l_word)) {
+ if (foundlp == NULL)
+ foundlp = lp;
+ else return NULL; /* multiple inexact matches */
+ }
+ return foundlp;
+}
+
+static char **
+getfields(register char *cp)
+{
+ register char * dp;
+ register char ** array;
+ register int nsubs;
+
+ if (cp == NULL)
+ return NULL;
+ array = emalloc(size_product(strlen(cp) + 1, sizeof *array));
+ nsubs = 0;
+ for ( ; ; ) {
+ while (isascii((unsigned char) *cp) &&
+ isspace((unsigned char) *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(1);
+ }
+ } while (*cp != '\0' && *cp != '#' &&
+ (!isascii(*cp) || !isspace((unsigned char) *cp)));
+ if (isascii(*cp) && isspace((unsigned char) *cp))
+ ++cp;
+ *dp = '\0';
+ }
+ array[nsubs] = NULL;
+ return array;
+}
+
+static ATTRIBUTE_PURE zic_t
+oadd(const zic_t t1, const zic_t t2)
+{
+ if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2) {
+ error(_("time overflow"));
+ exit(EXIT_FAILURE);
+ }
+ return t1 + t2;
+}
+
+static ATTRIBUTE_PURE zic_t
+tadd(const zic_t t1, const zic_t t2)
+{
+ if (t1 == max_time && t2 > 0)
+ return max_time;
+ if (t1 == min_time && t2 < 0)
+ return min_time;
+ if (t1 < 0 ? t2 < min_time - t1 : max_time - t1 < t2) {
+ error(_("time overflow"));
+ exit(EXIT_FAILURE);
+ }
+ 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(register const struct rule *const rp, register const zic_t wantedy)
+{
+ register int m, i;
+ register zic_t dayoff; /* with a nod to Margaret O. */
+ register 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;
+ 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) {
+ register 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 *const string)
+{
+ register int i;
+
+ if (strcmp(string, GRANDPARENTED) != 0) {
+ register const char * cp;
+ const char * mp;
+
+ /*
+ ** Want one to ZIC_MAX_ABBR_LEN_WO_WARN alphabetics
+ ** optionally followed by a + or - and a number from 1 to 14.
+ */
+ cp = string;
+ mp = NULL;
+ while (isascii((unsigned char) *cp) &&
+ isalpha((unsigned char) *cp))
+ ++cp;
+ if (cp - string == 0)
+mp = _("time zone abbreviation lacks alphabetic at start");
+ if (noise && cp - string < 3)
+mp = _("time zone abbreviation has fewer than 3 alphabetics");
+ if (cp - string > ZIC_MAX_ABBR_LEN_WO_WARN)
+mp = _("time zone abbreviation has too many alphabetics");
+ if (mp == NULL && (*cp == '+' || *cp == '-')) {
+ ++cp;
+ if (isascii((unsigned char) *cp) &&
+ isdigit((unsigned char) *cp))
+ if (*cp++ == '1' &&
+ *cp >= '0' && *cp <= '4')
+ ++cp;
+ }
+ 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);
+ }
+ (void) strcpy(&chars[charcnt], string);
+ charcnt += i;
+}
+
+static int
+mkdirs(char *argname)
+{
+ register char * name;
+ register char * cp;
+
+ if (argname == NULL || *argname == '\0')
+ return 0;
+ cp = name = ecpyalloc(argname);
+ while ((cp = strchr(cp + 1, '/')) != 0) {
+ *cp = '\0';
+#ifdef HAVE_DOS_FILE_NAMES
+ /*
+ ** DOS drive specifier?
+ */
+ if (isalpha((unsigned char) name[0]) &&
+ name[1] == ':' && name[2] == '\0') {
+ *cp = '/';
+ continue;
+ }
+#endif
+ if (!itsdir(name)) {
+ /*
+ ** It doesn't seem to exist, so we try to create it.
+ ** Creation may fail because of the directory being
+ ** created by some other multiprocessor, so we get
+ ** to do extra checking.
+ */
+ if (mkdir(name, MKDIR_UMASK) != 0) {
+ const char *e = strerror(errno);
+
+ if (errno != EEXIST || !itsdir(name)) {
+ (void) fprintf(stderr,
+_("%s: Can't create directory %s: %s\n"),
+ progname, name, e);
+ free(name);
+ return -1;
+ }
+ }
+ }
+ *cp = '/';
+ }
+ free(name);
+ return 0;
+}
+
+/*
+** UNIX was a registered trademark of The Open Group in 2003.
+*/