diff options
author | Joseph Myers <joseph@codesourcery.com> | 2013-12-20 13:10:07 +0000 |
---|---|---|
committer | Joseph Myers <joseph@codesourcery.com> | 2013-12-20 13:10:07 +0000 |
commit | 85bff96ad652b463f83d4cf19239eff1535e186a (patch) | |
tree | d651b5e72fe828f47613e6813ac5d54b0b7aaf66 | |
parent | b7867a3bfb9d7e00204c088feb227abddfc7bb43 (diff) | |
download | glibc-85bff96ad652b463f83d4cf19239eff1535e186a.zip glibc-85bff96ad652b463f83d4cf19239eff1535e186a.tar.gz glibc-85bff96ad652b463f83d4cf19239eff1535e186a.tar.bz2 |
Update timezone code from tzcode 2013i.
Now we have Paul's support for version-3 tz files checked in, this
patch updates all the code we take (unmodified) from tzcode to version
2013i (which includes the support for generating version-3 tz files
where necessary).
Tested x86_64.
* timezone/checktab.awk: Update from tzcode 2013i.
* timezone/private.h: Likewise.
* timezone/scheck.c: Likewise.
* timezone/tzfile.h: Likewise.
* timezone/tzselect.ksh: Likewise.
* timezone/zdump.c: Likewise.
* timezone/zic.c: Likewise.
-rw-r--r-- | ChangeLog | 8 | ||||
-rw-r--r-- | timezone/checktab.awk | 29 | ||||
-rw-r--r-- | timezone/private.h | 148 | ||||
-rw-r--r-- | timezone/scheck.c | 29 | ||||
-rw-r--r-- | timezone/tzfile.h | 25 | ||||
-rw-r--r-- | timezone/tzselect.ksh | 366 | ||||
-rw-r--r-- | timezone/zdump.c | 327 | ||||
-rw-r--r-- | timezone/zic.c | 620 |
8 files changed, 1072 insertions, 480 deletions
@@ -1,5 +1,13 @@ 2013-12-20 Joseph Myers <joseph@codesourcery.com> + * timezone/checktab.awk: Update from tzcode 2013i. + * timezone/private.h: Likewise. + * timezone/scheck.c: Likewise. + * timezone/tzfile.h: Likewise. + * timezone/tzselect.ksh: Likewise. + * timezone/zdump.c: Likewise. + * timezone/zic.c: Likewise. + * math/auto-libm-test-in: Add tests of cpow. * math/auto-libm-test-out: Regenerated. * math/libm-test.inc (cpow_test_data): Use AUTO_TESTS_cc_c. diff --git a/timezone/checktab.awk b/timezone/checktab.awk index c88b12f..fec4f62 100644 --- a/timezone/checktab.awk +++ b/timezone/checktab.awk @@ -9,6 +9,9 @@ BEGIN { if (!zone_table) zone_table = "zone.tab" if (!want_warnings) want_warnings = -1 + # A special (and we hope temporary) case. + tztab["America/Montreal"] = 1 + while (getline <iso_table) { iso_NR++ if ($0 ~ /^#/) continue @@ -69,13 +72,10 @@ BEGIN { status = 1 } cc0 = cc - if (tz2cc[tz]) { - printf "%s:%d: %s: duplicate TZ column\n", \ - zone_table, zone_NR, tz >>"/dev/stderr" - status = 1 - } - tz2cc[tz] = cc - tz2comments[tz] = comments + cctz = cc tz + cctztab[cctz] = 1 + tztab[tz] = 1 + tz2comments[cctz] = comments tz2NR[tz] = zone_NR if (cc2name[cc]) { cc_used[cc]++ @@ -92,16 +92,19 @@ BEGIN { } } - for (tz in tz2cc) { - if (cc_used[tz2cc[tz]] == 1) { - if (tz2comments[tz]) { + for (cctz in cctztab) { + cc = substr (cctz, 1, 2) + tz = substr (cctz, 3) + if (cc_used[cc] == 1) { + if (tz2comments[cctz]) { printf "%s:%d: unnecessary comment `%s'\n", \ - zone_table, tz2NR[tz], tz2comments[tz] \ + zone_table, tz2NR[tz], \ + tz2comments[cctz] \ >>"/dev/stderr" status = 1 } } else { - if (!tz2comments[tz]) { + if (!tz2comments[cctz]) { printf "%s:%d: missing comment\n", \ zone_table, tz2NR[tz] >>"/dev/stderr" status = 1 @@ -125,7 +128,7 @@ BEGIN { if (src != dst) tz = $3 } if (tz && tz ~ /\//) { - if (!tz2cc[tz]) { + if (!tztab[tz]) { printf "%s: no data for `%s'\n", zone_table, tz \ >>"/dev/stderr" status = 1 diff --git a/timezone/private.h b/timezone/private.h index 1d1d391..4eb0ab6 100644 --- a/timezone/private.h +++ b/timezone/private.h @@ -34,6 +34,10 @@ #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 */ @@ -124,19 +128,76 @@ #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 */ @@ -144,10 +205,26 @@ typedef long int_fast64_t; #define INT32_MIN (-1 - INT32_MAX) #endif /* !defined INT32_MIN */ -#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__) +#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 /* @@ -165,6 +242,58 @@ 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. */ @@ -192,14 +321,15 @@ const char * scheck(const char * string, const char * format); #define TYPE_SIGNED(type) (((type) -1) < 0) #endif /* !defined TYPE_SIGNED */ -/* -** Since the definition of TYPE_INTEGRAL contains floating point numbers, -** it cannot be used in preprocessor directives. -*/ - -#ifndef TYPE_INTEGRAL -#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) -#endif /* !defined TYPE_INTEGRAL */ +/* 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 /* diff --git a/timezone/scheck.c b/timezone/scheck.c index ed60980..8bd01a8 100644 --- a/timezone/scheck.c +++ b/timezone/scheck.c @@ -25,26 +25,35 @@ scheck(const char *const string, const char *const format) 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 (*fp == '%') { - *tp++ = *fp++; - 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; - while (is_digit(*fp)) - *tp++ = *fp++; - if (*fp == 'l' || *fp == 'h') - *tp++ = *fp++; - else if (*fp == '[') - do *tp++ = *fp++; - while (*fp != '\0' && *fp != ']'); if ((*tp++ = *fp++) == '\0') break; } + *(tp - 1) = '%'; *tp++ = 'c'; *tp = '\0'; diff --git a/timezone/tzfile.h b/timezone/tzfile.h index 0f6c687..529650d 100644 --- a/timezone/tzfile.h +++ b/timezone/tzfile.h @@ -39,7 +39,7 @@ struct tzhead { char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2' as of 2005 */ + 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 */ @@ -55,7 +55,7 @@ struct tzhead { ** 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 UTC offset in seconds +** 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 @@ -68,7 +68,7 @@ struct tzhead { ** if absent, transition times are ** assumed to be wall clock time ** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition -** time is UTC, if FALSE, +** time is UT, if FALSE, ** transition time is local time ** if absent, transition times are ** assumed to be local time @@ -82,6 +82,13 @@ struct tzhead { ** 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. */ /* @@ -94,16 +101,8 @@ struct tzhead { #endif /* !defined TZ_MAX_TIMES */ #ifndef TZ_MAX_TYPES -#ifndef NOSOLAR +/* 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 NOSOLAR */ -#ifdef NOSOLAR -/* -** Must be at least 14 for Europe/Riga as of Jan 12 1995, -** as noted by Earl Chew. -*/ -#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ -#endif /* !defined NOSOLAR */ #endif /* !defined TZ_MAX_TYPES */ #ifndef TZ_MAX_CHARS @@ -122,7 +121,7 @@ struct tzhead { #define DAYSPERNYEAR 365 #define DAYSPERLYEAR 366 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) #define MONSPERYEAR 12 #define TM_SUNDAY 0 diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh index 8e66b44..9d70691 100644 --- a/timezone/tzselect.ksh +++ b/timezone/tzselect.ksh @@ -11,7 +11,7 @@ REPORT_BUGS_TO=tz@iana.org # Porting notes: # -# This script requires a Posix-like shell with the extension of a +# This script requires a Posix-like shell and prefers the extension of a # 'select' statement. The 'select' statement was introduced in the # Korn shell and is available in Bash and other shell implementations. # If your host lacks both Bash and the Korn shell, you can get their @@ -21,6 +21,10 @@ REPORT_BUGS_TO=tz@iana.org # Korn Shell <http://www.kornshell.com/> # Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/> # +# For portability to Solaris 9 /bin/sh this script avoids some POSIX +# features and common extensions, such as $(...) (which works sometimes +# but not others), $((...)), and $10. +# # 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, # you can use either of the following free programs instead: @@ -31,7 +35,7 @@ REPORT_BUGS_TO=tz@iana.org # Specify default values for environment variables if they are unset. : ${AWK=awk} -: ${TZDIR=$(pwd)} +: ${TZDIR=`pwd`} # Check for awk Posix compliance. ($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1 @@ -40,21 +44,125 @@ REPORT_BUGS_TO=tz@iana.org exit 1 } -if [ "$1" = "--help" ]; then - cat <<EOF -Usage: tzselect +coord= +location_limit=10 + +usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT] Select a time zone interactively. -Report bugs to $REPORT_BUGS_TO. -EOF - exit -elif [ "$1" = "--version" ]; then - cat <<EOF -tzselect $PKGVERSION$TZVERSION -EOF - exit +Options: + + -c COORD + Instead of asking for continent and then country and then city, + ask for selection from time zones whose largest cities + are closest to the location with geographical coordinates COORD. + COORD should use ISO 6709 notation, for example, '-c +4852+00220' + for Paris (in degrees and minutes, North and East), or + '-c -35-058' for Buenos Aires (in degrees, South and West). + + -n LIMIT + Display at most LIMIT locations when -c is used (default $location_limit). + + --version + Output version information. + + --help + Output this help. + +Report bugs to $REPORT_BUGS_TO." + +# Ask the user to select from the function's arguments, +# and assign the selected argument to the variable 'select_result'. +# Exit on EOF or I/O error. Use the shell's 'select' builtin if available, +# falling back on a less-nice but portable substitute otherwise. +if + case $BASH_VERSION in + ?*) : ;; + '') + # '; exit' should be redundant, but Dash doesn't properly fail without it. + (eval 'set --; select x; do break; done; exit') 2>/dev/null + esac +then + # Do this inside 'eval', as otherwise the shell might exit when parsing it + # even though it is never executed. + eval ' + doselect() { + select select_result + do + case $select_result in + "") echo >&2 "Please enter a number in range." ;; + ?*) break + esac + done || exit + } + + # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout. + case $BASH_VERSION in + [01].*) + case `echo 1 | (select x in x; do break; done) 2>/dev/null` in + ?*) PS3= + esac + esac + ' +else + doselect() { + # Field width of the prompt numbers. + select_width=`expr $# : '.*'` + + select_i= + + while : + do + case $select_i in + '') + select_i=0 + for select_word + do + select_i=`expr $select_i + 1` + printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word" + done ;; + *[!0-9]*) + echo >&2 'Please enter a number in range.' ;; + *) + if test 1 -le $select_i && test $select_i -le $#; then + shift `expr $select_i - 1` + select_result=$1 + break + fi + echo >&2 'Please enter a number in range.' + esac + + # Prompt and read input. + printf >&2 %s "${PS3-#? }" + read select_i || exit + done + } fi +while getopts c:n:-: opt +do + case $opt$OPTARG in + c*) + coord=$OPTARG ;; + n*) + location_limit=$OPTARG ;; + -help) + exec echo "$usage" ;; + -version) + exec echo "tzselect $PKGVERSION$TZVERSION" ;; + -*) + echo >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;; + *) + echo >&2 "$0: try '$0 --help'"; exit 1 ;; + esac +done + +shift `expr $OPTIND - 1` +case $# in +0) ;; +*) echo >&2 "$0: $1: unknown argument"; exit 1 ;; +esac + # Make sure the tables are readable. TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab TZ_ZONE_TABLE=$TZDIR/zone.tab @@ -71,11 +179,65 @@ 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 - +# Awk script to read a time zone table and output the same table, +# with each column preceded by its distance from 'here'. +output_distances=' + BEGIN { + FS = "\t" + while (getline <TZ_COUNTRY_TABLE) + if ($0 ~ /^[^#]/) + country[$1] = $2 + country["US"] = "US" # Otherwise the strings get too long. + } + function convert_coord(coord, deg, min, ilen, sign, sec) { + if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) { + degminsec = coord + intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000) + minsec = degminsec - intdeg * 10000 + intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100) + sec = minsec - intmin * 100 + deg = (intdeg * 3600 + intmin * 60 + sec) / 3600 + } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) { + degmin = coord + intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100) + min = degmin - intdeg * 100 + deg = (intdeg * 60 + min) / 60 + } else + deg = coord + return deg * 0.017453292519943296 + } + function convert_latitude(coord) { + match(coord, /..*[-+]/) + return convert_coord(substr(coord, 1, RLENGTH - 1)) + } + function convert_longitude(coord) { + match(coord, /..*[-+]/) + return convert_coord(substr(coord, RLENGTH)) + } + # Great-circle distance between points with given latitude and longitude. + # Inputs and output are in radians. This uses the great-circle special + # case of the Vicenty formula for distances on ellipsoids. + function dist(lat1, long1, lat2, long2, dlong, x, y, num, denom) { + dlong = long2 - long1 + x = cos (lat2) * sin (dlong) + y = cos (lat1) * sin (lat2) - sin (lat1) * cos (lat2) * cos (dlong) + num = sqrt (x * x + y * y) + denom = sin (lat1) * sin (lat2) + cos (lat1) * cos (lat2) * cos (dlong) + return atan2(num, denom) + } + BEGIN { + coord_lat = convert_latitude(coord) + coord_long = convert_longitude(coord) + } + /^[^#]/ { + here_lat = convert_latitude($2) + here_long = convert_longitude($2) + line = $1 "\t" $2 "\t" $3 "\t" country[$1] + if (NF == 4) + line = line " - " $4 + printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line + } +' # Begin the main loop. We come back here if the user wants to retry. while @@ -87,39 +249,46 @@ while country= region= + case $coord in + ?*) + continent=coord;; + '') # 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 + echo >&2 'Please select a continent, ocean, "coord", or "TZ".' + + quoted_continents=` + $AWK ' + BEGIN { FS = "\t" } + /^[^#]/ { + entry = substr($3, 1, index($3, "/") - 1) + if (entry == "America") + entry = entry "s" + if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) + entry = entry " Ocean" + printf "'\''%s'\''\n", entry + } + ' $TZ_ZONE_TABLE | + sort -u | + tr '\n' ' ' + echo '' + ` + + eval ' + doselect '"$quoted_continents"' \ + "coord - I want to use geographical coordinates." \ + "TZ - I want to specify the time zone using the Posix TZ format." + continent=$select_result case $continent in - '') - echo >&2 'Please enter a number in range.';; - ?*) - case $continent in - Americas) continent=America;; - *' '*) continent=$(expr "$continent" : '\([^ ]*\)') - esac - break + Americas) continent=America;; + *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''` esac - done + ' + esac + case $continent in - '') - exit 1;; - none) + TZ) # Ask the user for a Posix TZ string. Check that it conforms. while echo >&2 'Please enter the desired value' \ @@ -144,11 +313,46 @@ while done TZ_for_date=$TZ;; *) + case $continent in + coord) + case $coord in + '') + echo >&2 'Please enter coordinates' \ + 'in ISO 6709 notation.' + echo >&2 'For example, +4042-07403 stands for' + echo >&2 '40 degrees 42 minutes north,' \ + '74 degrees 3 minutes west.' + read coord;; + esac + distance_table=`$AWK \ + -v coord="$coord" \ + -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ + "$output_distances" <$TZ_ZONE_TABLE | + sort -n | + sed "${location_limit}q" + ` + regions=`echo "$distance_table" | $AWK ' + BEGIN { FS = "\t" } + { print $NF } + '` + echo >&2 'Please select one of the following' \ + 'time zone regions,' + echo >&2 'listed roughly in increasing order' \ + "of distance from $coord". + doselect $regions + region=$select_result + TZ=`echo "$distance_table" | $AWK -v region="$region" ' + BEGIN { FS="\t" } + $NF == region { print $4 } + '` + ;; + *) # Get list of names of countries in the continent or ocean. - countries=$($AWK -F'\t' \ + countries=`$AWK \ -v continent="$continent" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' + BEGIN { FS = "\t" } /^#/ { next } $3 ~ ("^" continent "/") { if (!cc_seen[$1]++) cc_list[++ccs] = $1 @@ -165,35 +369,28 @@ while print country } } - ' <$TZ_ZONE_TABLE | sort -f) + ' <$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;; + echo >&2 'Please select a country' \ + 'whose clocks agree with yours.' + doselect $countries + country=$select_result;; *) country=$countries esac # Get list of names of time zone rule regions in the country. - regions=$($AWK -F'\t' \ + regions=`$AWK \ -v country="$country" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { + FS = "\t" cc = country while (getline <TZ_COUNTRY_TABLE) { if ($0 !~ /^#/ && country == $2) { @@ -203,7 +400,7 @@ while } } $1 == cc { print $4 } - ' <$TZ_ZONE_TABLE) + ' <$TZ_ZONE_TABLE` # If there's more than one region, ask the user which one. @@ -211,27 +408,20 @@ while *"$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;; + doselect $regions + region=$select_result;; *) region=$regions esac # Determine TZ from country and region. - TZ=$($AWK -F'\t' \ + TZ=`$AWK \ -v country="$country" \ -v region="$region" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { + FS = "\t" cc = country while (getline <TZ_COUNTRY_TABLE) { if ($0 !~ /^#/ && country == $2) { @@ -241,7 +431,8 @@ while } } $1 == cc && $4 == region { print $3 } - ' <$TZ_ZONE_TABLE) + ' <$TZ_ZONE_TABLE` + esac # Make sure the corresponding zoneinfo file exists. TZ_for_date=$TZDIR/$TZ @@ -259,10 +450,10 @@ while 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]\)') + 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=" @@ -278,28 +469,23 @@ Universal Time is now: $UTdate." echo >&2 "" echo >&2 "The following information has been given:" echo >&2 "" - case $country+$region in - ?*+?*) echo >&2 " $country$newline $region";; - ?*+) echo >&2 " $country";; + case $country%$region%$coord in + ?*%?*%) echo >&2 " $country$newline $region";; + ?*%%) echo >&2 " $country";; + %?*%?*) echo >&2 " coord $coord$newline $region";; + %%?*) echo >&2 " coord $coord";; +) 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 + doselect Yes No + ok=$select_result case $ok in - '') exit 1;; Yes) break esac -do : +do coord= done case $SHELL in diff --git a/timezone/zdump.c b/timezone/zdump.c index 9255aff..209b79d 100644 --- a/timezone/zdump.c +++ b/timezone/zdump.c @@ -9,20 +9,72 @@ ** 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 "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 "float.h" /* for FLT_MAX and DBL_MAX */ #include "limits.h" /* for CHAR_BIT, LLONG_MAX */ #include "ctype.h" /* for isalpha et al. */ #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 */ @@ -90,9 +142,20 @@ #define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) #endif /* !defined isleap_sum */ -#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#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 @@ -112,14 +175,6 @@ #endif /* !defined lint */ #endif /* !defined GNUC_or_lint */ -#ifndef INITIALIZE -#ifdef GNUC_or_lint -#define INITIALIZE(x) ((x) = 0) -#else /* !defined GNUC_or_lint */ -#define INITIALIZE(x) -#endif /* !defined GNUC_or_lint */ -#endif /* !defined INITIALIZE */ - #if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__) # define ATTRIBUTE_PURE __attribute__ ((__pure__)) #else @@ -151,48 +206,27 @@ extern char * optarg; extern int optind; extern char * tzname[2]; -/* The minimum and maximum finite time values. Shift 'long long' or - 'long' instead of 'time_t'; this avoids compile-time errors when - time_t is floating-point. In practice, 'long long' is wide enough. */ +/* The minimum and maximum finite time values. */ static time_t const absolute_min_time = - ((time_t) 0.5 == 0.5 - ? (sizeof (time_t) == sizeof (float) ? (time_t) -FLT_MAX - : sizeof (time_t) == sizeof (double) ? (time_t) -DBL_MAX - : sizeof (time_t) == sizeof (long double) ? (time_t) -LDBL_MAX - : 0) - : (time_t) -1 < 0 -#ifdef LLONG_MAX - ? (time_t) ((long long) -1 << (CHAR_BIT * sizeof (time_t) - 1)) -#else - ? (time_t) ((long) -1 << (CHAR_BIT * sizeof (time_t) - 1)) -#endif + ((time_t) -1 < 0 + ? (time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1) : 0); static time_t const absolute_max_time = - ((time_t) 0.5 == 0.5 - ? (sizeof (time_t) == sizeof (float) ? (time_t) FLT_MAX - : sizeof (time_t) == sizeof (double) ? (time_t) DBL_MAX - : sizeof (time_t) == sizeof (long double) ? (time_t) LDBL_MAX - : -1) - : (time_t) -1 < 0 -#ifdef LLONG_MAX - ? (time_t) (- (~ 0 < 0) - ((long long) -1 << (CHAR_BIT * sizeof (time_t) - 1))) -#else - ? (time_t) (- (~ 0 < 0) - ((long) -1 << (CHAR_BIT * sizeof (time_t) - 1))) -#endif - : (time_t) -1); + ((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 long delta(struct tm * newp, struct tm * oldp) ATTRIBUTE_PURE; +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 checkabsolutes(void); static void show(char * zone, time_t t, int v); static const char * tformat(void); -static time_t yeartot(long y) ATTRIBUTE_PURE; +static time_t yeartot(intmax_t y) ATTRIBUTE_PURE; #ifndef TYPECHECK #define my_localtime localtime @@ -209,7 +243,7 @@ my_localtime(time_t *tp) tm = *tmp; t = mktime(&tm); - if (t - *tp >= 1 || *tp - t >= 1) { + if (t != *tp) { (void) fflush(stdout); (void) fprintf(stderr, "\n%s: ", progname); (void) fprintf(stderr, tformat(), *tp); @@ -270,9 +304,9 @@ static void usage(FILE * const stream, const int status) { (void) fprintf(stream, -_("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -c [loyear,]hiyear ] zonename ...\n\ -\n\ -Report bugs to %s.\n"), +_("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n" + "\n" + "Report bugs to %s.\n"), progname, progname, REPORT_BUGS_TO); exit(status); } @@ -281,11 +315,10 @@ int main(int argc, char *argv[]) { register int i; - register int c; register int vflag; + register int Vflag; register char * cutarg; - register long cutloyear = ZDUMP_LO_YEAR; - register long cuthiyear = ZDUMP_HI_YEAR; + register char * cuttimes; register time_t cutlotime; register time_t cuthitime; register char ** fakeenv; @@ -297,8 +330,8 @@ main(int argc, char *argv[]) register struct tm * tmp; register struct tm * newtmp; - INITIALIZE(cutlotime); - INITIALIZE(cuthitime); + cutlotime = absolute_min_time; + cuthitime = absolute_max_time; #if HAVE_GETTEXT (void) setlocale(LC_ALL, ""); #ifdef TZ_DOMAINDIR @@ -314,37 +347,78 @@ main(int argc, char *argv[]) } else if (strcmp(argv[i], "--help") == 0) { usage(stdout, EXIT_SUCCESS); } - vflag = 0; - cutarg = NULL; - while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v') - if (c == 'v') - vflag = 1; - else cutarg = optarg; - if ((c != EOF && c != -1) || - (optind == argc - 1 && strcmp(argv[optind], "=") == 0)) { - usage(stderr, EXIT_FAILURE); - } - if (vflag) { + vflag = Vflag = 0; + cutarg = cuttimes = NULL; + 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); + } + arg_processing_done:; + + 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) { - long lo; - long hi; - char dummy; - - if (sscanf(cutarg, "%ld%c", &hi, &dummy) == 1) { + 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 if (sscanf(cutarg, "%ld,%ld%c", - &lo, &hi, &dummy) == 2) { - cutloyear = lo; - cuthiyear = hi; } else { (void) fprintf(stderr, _("%s: wild -c argument %s\n"), progname, cutarg); exit(EXIT_FAILURE); } } - checkabsolutes(); - cutlotime = yeartot(cutloyear); - cuthitime = yeartot(cuthiyear); + 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); + } + } } (void) time(&now); longest = 0; @@ -375,15 +449,17 @@ main(int argc, char *argv[]) static char buf[MAX_STRING_LENGTH]; (void) strcpy(&fakeenv[0][3], argv[i]); - if (!vflag) { + if (! (vflag | Vflag)) { show(argv[i], now, FALSE); continue; } warned = FALSE; t = absolute_min_time; - show(argv[i], t, TRUE); - t += SECSPERHOUR * HOURSPERDAY; - show(argv[i], t, TRUE); + if (!Vflag) { + show(argv[i], t, TRUE); + t += SECSPERDAY; + show(argv[i], t, TRUE); + } if (t < cutlotime) t = cutlotime; tmp = my_localtime(&t); @@ -392,9 +468,11 @@ main(int argc, char *argv[]) (void) strncpy(buf, abbr(&tm), (sizeof buf) - 1); } for ( ; ; ) { - if (t >= cuthitime || t >= cuthitime - SECSPERHOUR * 12) + newt = (t < absolute_max_time - SECSPERDAY / 2 + ? t + SECSPERDAY / 2 + : absolute_max_time); + if (cuthitime <= newt) break; - newt = t + SECSPERHOUR * 12; newtmp = localtime(&newt); if (newtmp != NULL) newtm = *newtmp; @@ -415,11 +493,13 @@ main(int argc, char *argv[]) tm = newtm; tmp = newtmp; } - t = absolute_max_time; - t -= SECSPERHOUR * HOURSPERDAY; - show(argv[i], t, TRUE); - t += SECSPERHOUR * HOURSPERDAY; - show(argv[i], t, TRUE); + if (!Vflag) { + t = absolute_max_time; + t -= SECSPERDAY; + show(argv[i], t, TRUE); + t += SECSPERDAY; + show(argv[i], t, TRUE); + } } if (fflush(stdout) || ferror(stdout)) { (void) fprintf(stderr, "%s: ", progname); @@ -431,44 +511,45 @@ main(int argc, char *argv[]) return EXIT_FAILURE; } -static void -checkabsolutes(void) -{ - if (absolute_max_time < absolute_min_time) { - (void) fprintf(stderr, -_("%s: use of -v on system with floating time_t other than float or double\n"), - progname); - exit(EXIT_FAILURE); - } -} - static time_t -yeartot(const long y) +yeartot(const intmax_t y) { - register long myy; - register long seconds; - register time_t t; + register intmax_t myy, seconds, years; + register time_t t; myy = EPOCH_YEAR; t = 0; - while (myy != y) { - if (myy < y) { + 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; - ++myy; - if (t > absolute_max_time - seconds) { - t = absolute_max_time; - break; - } - t += seconds; + 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 { - --myy; - seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR; - if (t < absolute_min_time + seconds) { - t = absolute_min_time; - break; - } - t -= seconds; + seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR; + years = 1; } + myy -= years; + if (t < absolute_min_time + seconds) + return absolute_min_time; + t -= seconds; } return t; } @@ -477,7 +558,6 @@ static time_t hunt(char *name, time_t lot, time_t hit) { time_t t; - long diff; struct tm lotm; register struct tm * lotmp; struct tm tm; @@ -490,7 +570,7 @@ hunt(char *name, time_t lot, time_t hit) (void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1); } for ( ; ; ) { - diff = (long) (hit - lot); + time_t diff = hit - lot; if (diff < 2) break; t = lot; @@ -520,11 +600,11 @@ hunt(char *name, time_t lot, time_t hit) ** Thanks to Paul Eggert for logic used in delta. */ -static long +static intmax_t delta(struct tm * newp, struct tm *oldp) { - register long result; - register int tmy; + register intmax_t result; + register int tmy; if (newp->tm_year < oldp->tm_year) return -delta(oldp, newp); @@ -553,7 +633,7 @@ show(char *zone, time_t t, int v) (void) printf(tformat(), t); } else { dumptime(tmp); - (void) printf(" UTC"); + (void) printf(" UT"); } (void) printf(" = "); } @@ -594,18 +674,19 @@ abbr(struct tm *tmp) static const char * tformat(void) { - if (0.5 == (time_t) 0.5) { /* floating */ - if (sizeof (time_t) > sizeof (double)) - return "%Lg"; - return "%g"; - } 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)) diff --git a/timezone/zic.c b/timezone/zic.c index 91f0d20..e7b0081 100644 --- a/timezone/zic.c +++ b/timezone/zic.c @@ -8,9 +8,15 @@ #include "locale.h" #include "tzfile.h" -#define ZIC_VERSION '2' +#include <stdarg.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 @@ -38,9 +44,6 @@ typedef int_fast64_t zic_t; #define isascii(x) 1 #endif -#define OFFSET_STRLEN_MAXIMUM (7 + INT_STRLEN_MAXIMUM(long)) -#define RULE_STRLEN_MAXIMUM 8 /* "Mdd.dd.d" */ - #define end(cp) (strchr((cp), '\0')) struct rule { @@ -48,8 +51,8 @@ struct rule { int r_linenum; const char * r_name; - int r_loyear; /* for example, 1986 */ - int r_hiyear; /* for example, 1986 */ + zic_t r_loyear; /* for example, 1986 */ + zic_t r_hiyear; /* for example, 1986 */ const char * r_yrtype; int r_lowasnum; int r_hiwasnum; @@ -60,12 +63,12 @@ struct rule { int r_dayofmonth; int r_wday; - long r_tod; /* time from midnight */ + 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 */ - long r_stdoff; /* offset from standard time */ + 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) */ @@ -85,11 +88,11 @@ struct zone { int z_linenum; const char * z_name; - long z_gmtoff; + zic_t z_gmtoff; const char * z_rule; const char * z_format; - long z_stdoff; + zic_t z_stdoff; struct rule * z_rules; int z_nrules; @@ -104,17 +107,23 @@ 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); -static int addtype(long gmtoff, const char * abbr, int isdst, +static int addtype(zic_t gmtoff, const char * abbr, int isdst, int ttisstd, int ttisgmt); 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 long eitol(int i); static char ** getfields(char * buf); -static long gethms(const char * string, const char * errstrng, - int signable); +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); @@ -126,14 +135,14 @@ static int itsdir(const char * name); static int lowerit(int c); static int mkdirs(char * filename); static void newabbr(const char * abbr); -static long oadd(long t1, long t2); +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, int wantedy); +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, long t2); +static zic_t tadd(zic_t t1, zic_t t2); static int yearistype(int year, const char * type); static int charcnt; @@ -141,13 +150,13 @@ static int errors; static const char * filename; static int leapcnt; static int leapseen; -static int leapminyear; -static int leapmaxyear; +static zic_t leapminyear; +static zic_t leapmaxyear; static int linenum; static int max_abbrvar_len; static int max_format_len; -static int max_year; -static int min_year; +static zic_t max_year; +static zic_t min_year; static int noise; static const char * rfilename; static int rlinenum; @@ -338,14 +347,14 @@ static struct attype { zic_t at; unsigned char type; } attypes[TZ_MAX_TIMES]; -static long gmtoffs[TZ_MAX_TYPES]; +static zic_t gmtoffs[TZ_MAX_TYPES]; 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 long corr[TZ_MAX_LEAPS]; +static zic_t corr[TZ_MAX_LEAPS]; static char roll[TZ_MAX_LEAPS]; /* @@ -390,16 +399,16 @@ eat(const char *const name, const int num) eats(name, num, NULL, -1); } -static void -error(const char *const string) +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. */ - (void) fprintf(stderr, _("\"%s\", line %d: %s"), - filename, linenum, string); + fprintf(stderr, _("\"%s\", line %d: "), filename, linenum); + vfprintf(stderr, string, args); if (rfilename != NULL) (void) fprintf(stderr, _(" (rule from \"%s\", line %d)"), rfilename, rlinenum); @@ -407,19 +416,27 @@ error(const char *const string) ++errors; } -static void -warning(const char *const string) +static void ATTRIBUTE_FORMAT((printf, 1, 2)) +error(const char *const string, ...) { - char * cp; + va_list args; + va_start(args, string); + verror(string, args); + va_end(args); +} - cp = ecpyalloc(_("warning: ")); - cp = ecatalloc(cp, string); - error(cp); - free(cp); +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 void +static _Noreturn void usage(FILE *stream, int status) { (void) fprintf(stream, _("%s: usage is %s \ @@ -444,9 +461,9 @@ main(int argc, char **argv) register int j; register int c; -#ifdef unix +#ifdef S_IWGRP (void) umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); -#endif /* defined unix */ +#endif #if HAVE_GETTEXT (void) setlocale(LC_ALL, ""); #ifdef TZ_DOMAINDIR @@ -602,41 +619,71 @@ dolink(const char *const fromfield, const char *const tofield) */ if (!itsdir(toname)) (void) remove(toname); - if (link(fromname, toname) != 0) { + 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 HAVE_SYMLINK - if (result != 0 && - access(fromname, F_OK) == 0 && - !itsdir(fromname)) { - const char *s = tofield; + if (result != 0) { + const char *s = fromfield; + const char *t; register char * symlinkcontents = NULL; - while ((s = strchr(s+1, '/')) != 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, - fromname); - result = symlink(symlinkcontents, - toname); + symlinkcontents = ecatalloc(symlinkcontents, t); + result = symlink(symlinkcontents, toname); if (result == 0) warning(_("hard link failed, symbolic link used")); free(symlinkcontents); } -#endif /* HAVE_SYMLINK */ if (result != 0) { - const char *e = strerror(errno); - - (void) fprintf(stderr, - _("%s: Can't link from %s to %s: %s\n"), - progname, fromname, toname, e); - exit(EXIT_FAILURE); + 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")); } } free(fromname); @@ -744,7 +791,7 @@ associate(void) ** a '%s' in the format is a bad thing. */ if (strchr(zp->z_format, '%') != 0) - error(_("%s in ruleless zone")); + error("%s", _("%s in ruleless zone")); } } if (errors) @@ -854,10 +901,10 @@ _("%s: panic: Invalid l_value %d\n"), ** Call error with errstring and return zero on errors. */ -static long +static zic_t gethms(const char *string, const char *const errstring, const int signable) { - long hh; + zic_t hh; int mm, ss, sign; if (string == NULL || *string == '\0') @@ -868,22 +915,22 @@ gethms(const char *string, const char *const errstring, const int signable) sign = -1; ++string; } else sign = 1; - if (sscanf(string, scheck(string, "%ld"), &hh) == 1) + if (sscanf(string, scheck(string, "%"SCNdZIC), &hh) == 1) mm = ss = 0; - else if (sscanf(string, scheck(string, "%ld:%d"), &hh, &mm) == 2) + else if (sscanf(string, scheck(string, "%"SCNdZIC":%d"), &hh, &mm) == 2) ss = 0; - else if (sscanf(string, scheck(string, "%ld:%d:%d"), + else if (sscanf(string, scheck(string, "%"SCNdZIC":%d:%d"), &hh, &mm, &ss) != 3) { - error(errstring); + error("%s", errstring); return 0; } if (hh < 0 || mm < 0 || mm >= MINSPERHOUR || ss < 0 || ss > SECSPERMIN) { - error(errstring); + error("%s", errstring); return 0; } - if (LONG_MAX / SECSPERHOUR < hh) { + if (ZIC_MAX / SECSPERHOUR < hh) { error(_("time overflow")); return 0; } @@ -892,8 +939,8 @@ gethms(const char *string, const char *const errstring, const int signable) 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(eitol(sign) * hh * eitol(SECSPERHOUR), - eitol(sign) * (eitol(mm) * eitol(SECSPERMIN) + eitol(ss))); + return oadd(sign * hh * SECSPERHOUR, + sign * (mm * SECSPERMIN + ss)); } static void @@ -926,40 +973,31 @@ static int inzone(register char **const fields, const int nfields) { register int i; - static char * buf; 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) { - buf = erealloc(buf, 132 + strlen(TZDEFAULT)); - (void) sprintf(buf, + error( _("\"Zone %s\" line and -l option are mutually exclusive"), TZDEFAULT); - error(buf); return FALSE; } if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) { - buf = erealloc(buf, 132 + strlen(TZDEFRULES)); - (void) sprintf(buf, + error( _("\"Zone %s\" line and -p option are mutually exclusive"), TZDEFRULES); - error(buf); return FALSE; } for (i = 0; i < nzones; ++i) if (zones[i].z_name != NULL && strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) { - buf = erealloc(buf, - (132 + strlen(fields[ZF_NAME]) - + strlen(zones[i].z_filename))); - (void) sprintf(buf, + error( _("duplicate zone name %s (file \"%s\", line %d)"), fields[ZF_NAME], zones[i].z_filename, zones[i].z_linenum); - error(buf); return FALSE; } return inzsub(fields, nfields, FALSE); @@ -1006,7 +1044,7 @@ inzsub(register char **const fields, const int nfields, const int iscont) } z.z_filename = filename; z.z_linenum = linenum; - z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UTC offset"), TRUE); + 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")); @@ -1058,8 +1096,9 @@ inleap(register char ** const fields, const int nfields) register const char * cp; register const struct lookup * lp; register int i, j; - int year, month, day; - long dayoff, tod; + zic_t year; + int month, day; + zic_t dayoff, tod; zic_t t; if (nfields != LEAP_FIELDS) { @@ -1068,7 +1107,7 @@ inleap(register char ** const fields, const int nfields) } dayoff = 0; cp = fields[LP_YEAR]; - if (sscanf(cp, scheck(cp, "%d"), &year) != 1) { + if (sscanf(cp, scheck(cp, "%"SCNdZIC), &year) != 1) { /* ** Leapin' Lizards! */ @@ -1089,7 +1128,7 @@ inleap(register char ** const fields, const int nfields) --j; i = -len_years[isleap(j)]; } - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); } if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) { error(_("invalid month name")); @@ -1099,7 +1138,7 @@ inleap(register char ** const fields, const int nfields) j = TM_JANUARY; while (j != month) { i = len_months[isleap(year)][j]; - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); ++j; } cp = fields[LP_DAY]; @@ -1108,7 +1147,7 @@ inleap(register char ** const fields, const int nfields) error(_("invalid day of month")); return; } - dayoff = oadd(dayoff, eitol(day - 1)); + dayoff = oadd(dayoff, day - 1); if (dayoff < 0 && !TYPE_SIGNED(zic_t)) { error(_("time before zero")); return; @@ -1233,17 +1272,17 @@ rulesub(register struct rule *const rp, rp->r_lowasnum = lp == NULL; if (!rp->r_lowasnum) switch ((int) lp->l_value) { case YR_MINIMUM: - rp->r_loyear = INT_MIN; + rp->r_loyear = ZIC_MIN; break; case YR_MAXIMUM: - rp->r_loyear = INT_MAX; + 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, "%d"), &rp->r_loyear) != 1) { + } else if (sscanf(cp, scheck(cp, "%"SCNdZIC), &rp->r_loyear) != 1) { error(_("invalid starting year")); return; } @@ -1252,10 +1291,10 @@ rulesub(register struct rule *const rp, rp->r_hiwasnum = lp == NULL; if (!rp->r_hiwasnum) switch ((int) lp->l_value) { case YR_MINIMUM: - rp->r_hiyear = INT_MIN; + rp->r_hiyear = ZIC_MIN; break; case YR_MAXIMUM: - rp->r_hiyear = INT_MAX; + rp->r_hiyear = ZIC_MAX; break; case YR_ONLY: rp->r_hiyear = rp->r_loyear; @@ -1265,7 +1304,7 @@ rulesub(register struct rule *const rp, _("%s: panic: Invalid l_value %d\n"), progname, lp->l_value); exit(EXIT_FAILURE); - } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_hiyear) != 1) { + } else if (sscanf(cp, scheck(cp, "%"SCNdZIC), &rp->r_hiyear) != 1) { error(_("invalid ending year")); return; } @@ -1330,7 +1369,7 @@ rulesub(register struct rule *const rp, } static void -convert(const long val, char *const buf) +convert(const int_fast32_t val, char *const buf) { register int i; register int shift; @@ -1352,7 +1391,7 @@ convert64(const zic_t val, char *const buf) } static void -puttzcode(const long val, FILE *const fp) +puttzcode(const int_fast32_t val, FILE *const fp) { char buf[4]; @@ -1385,7 +1424,7 @@ is32(const zic_t x) } static void -writezone(const char *const name, const char *const string) +writezone(const char *const name, const char *const string, char version) { register FILE * fp; register int i, j; @@ -1414,8 +1453,11 @@ writezone(const char *const name, const char *const string) fromi = 0; while (fromi < timecnt && attypes[fromi].at < min_time) ++fromi; - if (isdsts[0] == 0) - while (fromi < timecnt && attypes[fromi].type == 0) + /* + ** 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 + @@ -1517,7 +1559,11 @@ writezone(const char *const name, const char *const string) } thistimelim = thistimei + thistimecnt; thisleaplim = thisleapi + thisleapcnt; - for (i = 0; i < typecnt; ++i) + /* + ** Remember that type 0 is reserved. + */ + writetype[0] = FALSE; + for (i = 1; i < typecnt; ++i) writetype[i] = thistimecnt == timecnt; if (thistimecnt == 0) { /* @@ -1533,8 +1579,11 @@ writezone(const char *const name, const char *const string) /* ** For America/Godthab and Antarctica/Palmer */ + /* + ** Remember that type 0 is reserved. + */ if (thistimei == 0) - writetype[0] = TRUE; + writetype[1] = TRUE; } #ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH /* @@ -1584,8 +1633,26 @@ writezone(const char *const name, const char *const string) } #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++ : -1; + typemap[i] = writetype[i] ? thistypecnt++ : 0; for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i) indmap[i] = -1; thischarcnt = 0; @@ -1610,13 +1677,13 @@ writezone(const char *const name, const char *const string) #define DO(field) ((void) fwrite(tzh.field, sizeof tzh.field, 1, fp)) tzh = tzh0; (void) strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); - tzh.tzh_version[0] = ZIC_VERSION; - convert(eitol(thistypecnt), tzh.tzh_ttisgmtcnt); - convert(eitol(thistypecnt), tzh.tzh_ttisstdcnt); - convert(eitol(thisleapcnt), tzh.tzh_leapcnt); - convert(eitol(thistimecnt), tzh.tzh_timecnt); - convert(eitol(thistypecnt), tzh.tzh_typecnt); - convert(eitol(thischarcnt), tzh.tzh_charcnt); + 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); @@ -1629,7 +1696,7 @@ writezone(const char *const name, const char *const string) #undef DO for (i = thistimei; i < thistimelim; ++i) if (pass == 1) - puttzcode((long) ats[i], fp); + puttzcode(ats[i], fp); else puttzcode64(ats[i], fp); for (i = thistimei; i < thistimelim; ++i) { unsigned char uc; @@ -1723,7 +1790,7 @@ doabbr(char *const abbr, const char *const format, const char *const letters, } static void -updateminmax(const int x) +updateminmax(const zic_t x) { if (min_year > x) min_year = x; @@ -1732,7 +1799,7 @@ updateminmax(const int x) } static int -stringoffset(char *result, long offset) +stringoffset(char *result, zic_t offset) { register int hours; register int minutes; @@ -1748,7 +1815,7 @@ stringoffset(char *result, long offset) minutes = offset % MINSPERHOUR; offset /= MINSPERHOUR; hours = offset; - if (hours > HOURSPERDAY) { + if (hours >= HOURSPERDAY * DAYSPERWEEK) { result[0] = '\0'; return -1; } @@ -1762,10 +1829,11 @@ stringoffset(char *result, long offset) } static int -stringrule(char *result, const struct rule *const rp, const long dstoff, - const long gmtoff) +stringrule(char *result, const struct rule *const rp, const zic_t dstoff, + const zic_t gmtoff) { - register long tod; + register zic_t tod = rp->r_tod; + register int compat = 0; result = end(result); if (rp->r_dycode == DC_DOM) { @@ -1776,44 +1844,76 @@ stringrule(char *result, const struct rule *const rp, const long dstoff, total = 0; for (month = 0; month < rp->r_month; ++month) total += len_months[0][month]; - (void) sprintf(result, "J%d", total + rp->r_dayofmonth); + /* 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) { - if ((rp->r_dayofmonth % DAYSPERWEEK) != 1) - return -1; - week = 1 + rp->r_dayofmonth / DAYSPERWEEK; + 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 { - if ((rp->r_dayofmonth % DAYSPERWEEK) != 0) - return -1; + 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, rp->r_wday); + rp->r_month + 1, week, wday); } - tod = rp->r_tod; if (rp->r_todisgmt) tod += gmtoff; if (rp->r_todisstd && rp->r_stdoff == 0) tod += dstoff; - if (tod < 0) { - result[0] = '\0'; - return -1; - } 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 0; + return compat; } -static void +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; @@ -1822,77 +1922,106 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount) 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 != INT_MAX) + 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; + else return -1; } else { if (dstrp == NULL) dstrp = rp; - else return; + else return -1; } } if (stdrp == NULL && dstrp == NULL) { /* ** There are no rules running through "max". - ** Let's find the latest rule. + ** 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 (stdrp == NULL || rp->r_hiyear > stdrp->r_hiyear || - (rp->r_hiyear == stdrp->r_hiyear && - (rp->r_month > stdrp->r_month || - (rp->r_month == stdrp->r_month && - rp->r_dayofmonth > stdrp->r_dayofmonth)))) - stdrp = rp; + if (rp->r_stdoff == 0 && rule_cmp(stdabbrrp, rp) < 0) + stdabbrrp = rp; + if (rule_cmp(stdrp, rp) < 0) + stdrp = rp; } - if (stdrp != NULL && stdrp->r_stdoff != 0) - return; /* We end up in DST (a POSIX no-no). */ /* ** 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; + 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; + 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; + return -1; } if (dstrp == NULL) - return; + 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; + return -1; } (void) strcat(result, ","); - if (stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) { + c = stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff); + if (c < 0) { result[0] = '\0'; - return; + return -1; } + if (compat < c) + compat = c; (void) strcat(result, ","); - if (stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) { + c = stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff); + if (c < 0) { result[0] = '\0'; - return; + return -1; } + if (compat < c) + compat = c; + return compat; } static void @@ -1903,10 +2032,10 @@ outzone(const struct zone * const zpfirst, const int zonecount) register int i, j; register int usestart, useuntil; register zic_t starttime, untiltime; - register long gmtoff; - register long stdoff; - register int year; - register long startoff; + 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; @@ -1916,6 +2045,9 @@ outzone(const struct zone * const zpfirst, const int zonecount) 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; max_abbr_len = 2 + max_format_len + max_abbrvar_len; max_envvar_len = 2 * max_abbr_len + 5 * 9; @@ -1940,8 +2072,13 @@ outzone(const struct zone * const zpfirst, const int zonecount) min_year = max_year = EPOCH_YEAR; if (leapseen) { updateminmax(leapminyear); - updateminmax(leapmaxyear + (leapmaxyear < INT_MAX)); + 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) @@ -1959,23 +2096,45 @@ outzone(const struct zone * const zpfirst, const int zonecount) /* ** Generate lots of data if a rule can't cover all future times. */ - stringzone(envvar, zpfirst, zonecount); - if (noise && envvar[0] == '\0') { - register char * wp; - -wp = ecpyalloc(_("no POSIX environment variable for zone")); - wp = ecatalloc(wp, " "); - wp = ecatalloc(wp, zpfirst->z_name); - warning(wp); - free(wp); - } - if (envvar[0] == '\0') { - if (min_year >= INT_MIN + YEARSPERREPEAT) - min_year -= YEARSPERREPEAT; - else min_year = INT_MIN; - if (max_year <= INT_MAX - YEARSPERREPEAT) - max_year += YEARSPERREPEAT; - else max_year = INT_MAX; + compat = stringzone(envvar, zpfirst, zonecount); + version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION; + do_extend = compat < 0 || compat == YEAR_BY_YEAR_ZONE; + 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 @@ -1984,7 +2143,7 @@ wp = ecpyalloc(_("no POSIX environment variable for zone")); */ if (prodstic) { min_year = 1900; - max_year = min_year + YEARSPERREPEAT; + max_year = min_year + years_of_observations; } } /* @@ -2041,12 +2200,12 @@ wp = ecpyalloc(_("no POSIX environment variable for zone")); for ( ; ; ) { register int k; register zic_t jtime, ktime; - register long offset; + register zic_t offset; INITIALIZE(ktime); if (useuntil) { /* - ** Turn untiltime into UTC + ** Turn untiltime into UT ** assuming the current gmtoff and ** stdoff values. */ @@ -2150,7 +2309,45 @@ error(_("can't determine time zone abbreviation to use just after until time")); starttime = tadd(starttime, -gmtoff); } } - writezone(zpfirst->z_name, envvar); + 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); @@ -2183,7 +2380,7 @@ addtt(const zic_t starttime, int type) } static int -addtype(const long gmtoff, const char *const abbr, const int isdst, +addtype(const zic_t gmtoff, const char *const abbr, const int isdst, const int ttisstd, const int ttisgmt) { register int i, j; @@ -2220,7 +2417,7 @@ addtype(const long gmtoff, const char *const abbr, const int isdst, exit(EXIT_FAILURE); } if (! (-1L - 2147483647L <= gmtoff && gmtoff <= 2147483647L)) { - error(_("UTC offset out of range")); + error(_("UT offset out of range")); exit(EXIT_FAILURE); } gmtoffs[i] = gmtoff; @@ -2262,7 +2459,7 @@ leapadd(const zic_t t, const int positive, const int rolling, int count) roll[j] = roll[j - 1]; } trans[i] = t; - corr[i] = positive ? 1L : eitol(-count); + corr[i] = positive ? 1 : -count; roll[i] = rolling; ++leapcnt; } while (positive && --count != 0); @@ -2272,7 +2469,7 @@ static void adjleap(void) { register int i; - register long last = 0; + register zic_t last = 0; /* ** propagate leap seconds forward @@ -2406,10 +2603,10 @@ getfields(register char *cp) return array; } -static ATTRIBUTE_PURE long -oadd(const long t1, const long t2) +static ATTRIBUTE_PURE zic_t +oadd(const zic_t t1, const zic_t t2) { - if (t1 < 0 ? t2 < LONG_MIN - t1 : LONG_MAX - t1 < t2) { + if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2) { error(_("time overflow")); exit(EXIT_FAILURE); } @@ -2417,7 +2614,7 @@ oadd(const long t1, const long t2) } static ATTRIBUTE_PURE zic_t -tadd(const zic_t t1, const long t2) +tadd(const zic_t t1, const zic_t t2) { if (t1 == max_time && t2 > 0) return max_time; @@ -2436,15 +2633,15 @@ tadd(const zic_t t1, const long t2) */ static zic_t -rpytime(register const struct rule *const rp, register const int wantedy) +rpytime(register const struct rule *const rp, register const zic_t wantedy) { - register int y, m, i; - register long dayoff; /* with a nod to Margaret O. */ - register zic_t t; + register int m, i; + register zic_t dayoff; /* with a nod to Margaret O. */ + register zic_t t, y; - if (wantedy == INT_MIN) + if (wantedy == ZIC_MIN) return min_time; - if (wantedy == INT_MAX) + if (wantedy == ZIC_MAX) return max_time; dayoff = 0; m = TM_JANUARY; @@ -2457,11 +2654,11 @@ rpytime(register const struct rule *const rp, register const int wantedy) --y; i = -len_years[isleap(y)]; } - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); } while (m != rp->r_month) { i = len_months[isleap(y)][m]; - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); ++m; } i = rp->r_dayofmonth; @@ -2474,12 +2671,12 @@ rpytime(register const struct rule *const rp, register const int wantedy) } } --i; - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) { - register long wday; + register zic_t wday; -#define LDAYSPERWEEK ((long) DAYSPERWEEK) - wday = eitol(EPOCH_WDAY); +#define LDAYSPERWEEK ((zic_t) DAYSPERWEEK) + wday = EPOCH_WDAY; /* ** Don't trust mod of negative numbers. */ @@ -2490,7 +2687,7 @@ rpytime(register const struct rule *const rp, register const int wantedy) if (wday < 0) wday += LDAYSPERWEEK; } - while (wday != eitol(rp->r_wday)) + while (wday != rp->r_wday) if (rp->r_dycode == DC_DOWGEQ) { dayoff = oadd(dayoff, 1); if (++wday >= LDAYSPERWEEK) @@ -2550,14 +2747,8 @@ mp = _("time zone abbreviation has too many alphabetics"); } if (*cp != '\0') mp = _("time zone abbreviation differs from POSIX standard"); - if (mp != NULL) { - char *wp = ecpyalloc(mp); - wp = ecatalloc(wp, " ("); - wp = ecatalloc(wp, string); - wp = ecatalloc(wp, ")"); - warning(wp); - free(wp); - } + if (mp != NULL) + warning("%s (%s)", mp, string); } i = strlen(string) + 1; if (charcnt + i > TZ_MAX_CHARS) { @@ -2565,7 +2756,7 @@ mp = _("time zone abbreviation differs from POSIX standard"); exit(EXIT_FAILURE); } (void) strcpy(&chars[charcnt], string); - charcnt += eitol(i); + charcnt += i; } static int @@ -2579,7 +2770,7 @@ mkdirs(char *argname) cp = name = ecpyalloc(argname); while ((cp = strchr(cp + 1, '/')) != 0) { *cp = '\0'; -#ifndef unix +#ifdef HAVE_DOS_FILE_NAMES /* ** DOS drive specifier? */ @@ -2588,7 +2779,7 @@ mkdirs(char *argname) *cp = '/'; continue; } -#endif /* !defined unix */ +#endif if (!itsdir(name)) { /* ** It doesn't seem to exist, so we try to create it. @@ -2614,21 +2805,6 @@ _("%s: Can't create directory %s: %s\n"), return 0; } -static ATTRIBUTE_PURE long -eitol(const int i) -{ - long l; - - l = i; - if ((i < 0 && l >= 0) || (i == 0 && l != 0) || (i > 0 && l <= 0)) { - (void) fprintf(stderr, - _("%s: %d did not sign extend correctly\n"), - progname, i); - exit(EXIT_FAILURE); - } - return l; -} - /* ** UNIX was a registered trademark of The Open Group in 2003. */ |