diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /intl/icu/source/tools/tzcode | |
parent | Initial commit. (diff) | |
download | firefox-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.in | 182 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/asctime.c | 132 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/ialloc.c | 32 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/icuregions | 25 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/icuzdump.cpp | 426 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/icuzdump.vcxproj | 110 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/icuzdump.vcxproj.filters | 22 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/icuzones | 96 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/localtime.c | 2058 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/private.h | 415 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/readme.txt | 95 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/scheck.c | 64 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/tz2icu.cpp | 1881 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/tz2icu.h | 46 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/tzfile.h | 169 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/tzselect.ksh | 308 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/zdump.c | 1089 | ||||
-rw-r--r-- | intl/icu/source/tools/tzcode/zic.c | 3156 |
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. +*/ |