timezone: sync to TZDB 2024a

Sync tzselect, zdump, zic to TZDB 2024a.
This patch incorporates the following TZDB source code changes,
listed roughly in descending order of importance.

  zic now supports links to links, needed for future tzdata
  zic now defaults to '-b slim'
  zic now updates output files atomically
  zic has new options -R, -l -, -p -
  zic -r now uses -00 for unspecified timestamps
  zdump now uses [lo,hi) for both -c and -t
  Fix several integer overflow bugs
  zic now checks input bytes more carefully
  Simplify and fix new TZDIR setup
  Default time_t to 64 bits on glibc 2.34+ 32-bit
  zic now generates TZ strings that conform to POSIX when all-year DST
  zic -v now shows extreme-int tm_year transitions
  Fix zic bug in last time type of Asia/Gaza etc.
  Fix zic bug with Palestine after 2075
  Fix bug uncovered by recent change to Iran history
  Fix 'zic -b fat' bug with Port Moresby 32-bit data
  Fix zic bug with -r @X where X is deduced from TZ
  Fix bug with zic -r cutoff before 1st transition
  Fix leap second expiry and truncation
  Fix zic bug on Linux 2.6.16 and 2.6.17
  Fix bug with 'zic -d /a/b/c' if /a is unwriteable
  Don't mistruncate TZif files at leap seconds
  Fix zdump undefined behavior if !USE_LTZ
  zdump -v reports localtime+gmtime failures better
  Fix zdump diagnostic for missing timezone
  Don't assume nonempty argv
  Port better to C23
  Do not assume negative >> behavior
  I18nize zdump a bit better
  Port zdump to right_only installations
  New tzselect menu option 'now'
  tzselect can now use current time to help choose
  Improve tzselect behavior for Turkey etc.
  tzselect: do not create temporary files
  tzselect: work around mawk bug with {2,}
  tzselect: Port to POSIX awk, which prohibits -v newlines
  Do not use empty RE in tzselect
  Don't set TZ in tzselect
  Avoid sed, expr in tzselect
  tzselect: Fix problems with spaces in TZDIR
  Improve tzselect diagnostics
  Remove zic workaround for Qt bug 53071
  Remove zic support for "min" in Rule lines
  Remove zic support for zic -y, Rule TYPEs, pacificnew
  Remove tzselect workaround for Bash 1.14.7 bug

* SHARED-FILES: Update to match current sync.
* config.h.in (HAVE_STRERROR): Remove; no longer needed.
* timezone/Makefile ($(objpfx)zic.o): Depend on tzdir.h.
($(objpfx)tzdir.h): New rule to build a placeholder.
* timezone/private.h, timezone/tzfile.h, timezone/version:
* timezone/zdump.c, timezone/zic.c: Copy verbatim from TZDB 2024a.
This commit is contained in:
Paul Eggert 2024-04-06 23:39:53 -07:00
parent 57581acd95
commit 1f94147a79
9 changed files with 2686 additions and 1554 deletions

View File

@ -177,23 +177,25 @@ unicode:
# The following files are shared with the upstream tzcode project and must be
# updated regularly to stay in sync with the upstream releases.
#
# Update from tzcode 2017b.
# Latest is 2018g:
# https://mm.icann.org/pipermail/tz-announce/2018-October/000052.html
# Currently synced to TZDB 2024a, announced and distributed here:
# https://mm.icann.org/pipermail/tz-announce/2024-February/000081.html
# https://data.iana.org/time-zones/releases/tzdb-2024a.tar.lz
tzcode:
timezone/private.h
timezone/tzfile.h
timezone/tzselect.ksh
timezone/version
timezone/zdump.c
timezone/zic.c
timezone/tzselect.ksh
# The following files are shared with the upstream tzdata project but is not
# synchronized regularly. The data files themselves are used only for testing
# purposes and their data is never used to generate any output. We synchronize
# them only to stay on top of newer data that might help with testing.
#
# Currently synced to 2009i. Latest is 2018g.
# https://mm.icann.org/pipermail/tz-announce/2018-October/000052.html
# Currently synced to tzcode 2009i, announced and distributed here:
# https://mm.icann.org/pipermail/tz/2009-June/040697.html
# https://data.iana.org/time-zones/releases/tzdata2009i.tar.gz
tzdata:
timezone/africa
timezone/antarctica

View File

@ -240,10 +240,6 @@
#ifdef _LIBC
/* The zic and zdump programs need these definitions. */
#define HAVE_STRERROR 1
/* The locale code needs these definitions. */
#define HAVE_REGEX 1

View File

@ -49,6 +49,13 @@ endif
include ../Rules
$(objpfx)zic.o: $(objpfx)tzdir.h
# In glibc this file is an empty placeholder,
# as tz-cflags defines TZDEFAULT and TZDIR.
$(objpfx)tzdir.h:
> $@
$(objpfx)zic.o $(objpfx)zdump.o: $(objpfx)version.h
$(objpfx)version.h: $(common-objpfx)config.make

View File

@ -17,6 +17,40 @@
** Thank you!
*/
/* PORT_TO_C89 means the code should work even if the underlying
compiler and library support only C89 plus C99's 'long long'
and perhaps a few other extensions to C89. SUPPORT_C89 means the
tzcode library should support C89 callers in addition to the usual
support for C99-and-later callers; however, C89 support can trigger
latent bugs in C99-and-later callers. These macros are obsolescent,
and the plan is to remove them along with any code needed only when
they are nonzero. A good time to do that might be in the year 2029
because RHEL 7 (whose GCC defaults to C89) extended life cycle
support (ELS) is scheduled to end on 2028-06-30. */
#ifndef PORT_TO_C89
# define PORT_TO_C89 0
#endif
#ifndef SUPPORT_C89
# define SUPPORT_C89 0
#endif
#ifndef __STDC_VERSION__
# define __STDC_VERSION__ 0
#endif
/* Define true, false and bool if they don't work out of the box. */
#if PORT_TO_C89 && __STDC_VERSION__ < 199901
# define true 1
# define false 0
# define bool int
#elif __STDC_VERSION__ < 202311
# include <stdbool.h>
#endif
#if __STDC_VERSION__ < 202311
# define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1]
#endif
/*
** zdump has been made independent of the rest of the time
** conversion package to increase confidence in the verification it provides.
@ -39,24 +73,27 @@
# define HAVE_DECL_ASCTIME_R 1
#endif
#if !defined HAVE_GENERIC && defined __has_extension
# if __has_extension(c_generic_selections)
# define HAVE_GENERIC 1
# else
# define HAVE_GENERIC 0
#if !defined HAVE__GENERIC && defined __has_extension
# if !__has_extension(c_generic_selections)
# define HAVE__GENERIC 0
# endif
#endif
/* _Generic is buggy in pre-4.9 GCC. */
#if !defined HAVE_GENERIC && defined __GNUC__
# define HAVE_GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__))
#if !defined HAVE__GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__
# define HAVE__GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__))
#endif
#ifndef HAVE_GENERIC
# define HAVE_GENERIC (201112 <= __STDC_VERSION__)
#ifndef HAVE__GENERIC
# define HAVE__GENERIC (201112 <= __STDC_VERSION__)
#endif
#if !defined HAVE_GETTEXT && defined __has_include
# if __has_include(<libintl.h>)
# define HAVE_GETTEXT true
# endif
#endif
#ifndef HAVE_GETTEXT
#define HAVE_GETTEXT 0
#endif /* !defined HAVE_GETTEXT */
# define HAVE_GETTEXT false
#endif
#ifndef HAVE_INCOMPATIBLE_CTIME_R
# define HAVE_INCOMPATIBLE_CTIME_R 0
@ -66,41 +103,43 @@
# define HAVE_LINK 1
#endif /* !defined HAVE_LINK */
#ifndef HAVE_MALLOC_ERRNO
# define HAVE_MALLOC_ERRNO 1
#endif
#ifndef HAVE_POSIX_DECLS
# define HAVE_POSIX_DECLS 1
#endif
#ifndef HAVE_STDBOOL_H
#define HAVE_STDBOOL_H (199901 <= __STDC_VERSION__)
#ifndef HAVE_SETENV
# define HAVE_SETENV 1
#endif
#ifndef HAVE_STRDUP
# define HAVE_STRDUP 1
#endif
#ifndef HAVE_STRTOLL
#define HAVE_STRTOLL 1
#endif
#ifndef HAVE_SYMLINK
# define HAVE_SYMLINK 1
#endif /* !defined HAVE_SYMLINK */
#if !defined HAVE_SYS_STAT_H && defined __has_include
# if !__has_include(<sys/stat.h>)
# define HAVE_SYS_STAT_H false
# endif
#endif
#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 */
# define HAVE_SYS_STAT_H true
#endif
#if !defined HAVE_UNISTD_H && defined __has_include
# if !__has_include(<unistd.h>)
# define HAVE_UNISTD_H false
# endif
#endif
#ifndef HAVE_UNISTD_H
#define HAVE_UNISTD_H 1
#endif /* !defined HAVE_UNISTD_H */
#ifndef HAVE_UTMPX_H
#define HAVE_UTMPX_H 1
#endif /* !defined HAVE_UTMPX_H */
# define HAVE_UNISTD_H true
#endif
#ifndef NETBSD_INSPIRED
# define NETBSD_INSPIRED 1
@ -118,15 +157,17 @@
/* Enable strtoimax on pre-C99 Solaris 11. */
#define __EXTENSIONS__ 1
/* To avoid having 'stat' fail unnecessarily with errno == EOVERFLOW,
enable large files on GNUish systems ... */
/* On GNUish systems where time_t might be 32 or 64 bits, use 64.
On these platforms _FILE_OFFSET_BITS must also be 64; otherwise
setting _TIME_BITS to 64 does not work. The code does not
otherwise rely on _FILE_OFFSET_BITS being 64, since it does not
use off_t or functions like 'stat' that depend on off_t. */
#ifndef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 64
#endif
/* ... and on AIX ... */
#define _LARGE_FILES 1
/* ... and enable large inode numbers on Mac OS X 10.5 and later. */
#define _DARWIN_USE_64_BIT_INODE 1
#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64
# define _TIME_BITS 64
#endif
/*
** Nested includes
@ -157,16 +198,29 @@
#undef tzalloc
#undef tzfree
#include <sys/types.h> /* for time_t */
#include <stddef.h>
#include <string.h>
#if !PORT_TO_C89
# include <inttypes.h>
#endif
#include <limits.h> /* for CHAR_BIT et al. */
#include <stdlib.h>
#include <errno.h>
#ifndef EINVAL
# define EINVAL ERANGE
#endif
#ifndef ELOOP
# define ELOOP EINVAL
#endif
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
#ifndef ENOMEM
# define ENOMEM EINVAL
#endif
#ifndef ENOTSUP
# define ENOTSUP EINVAL
#endif
@ -218,15 +272,17 @@
# 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)
#if PORT_TO_C89
/*
** Define HAVE_STDINT_H's default value here, rather than at the
** start, since __GLIBC__ and INTMAX_MAX's values depend on
** previously-included files. glibc 2.1 and Solaris 10 and later have
** previously included files. glibc 2.1 and Solaris 10 and later have
** stdint.h, even with pre-C99 compilers.
*/
#if !defined HAVE_STDINT_H && defined __has_include
# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */
#endif
#ifndef HAVE_STDINT_H
# define HAVE_STDINT_H \
(199901 <= __STDC_VERSION__ \
@ -246,36 +302,36 @@
#endif
/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */
#ifdef __LONG_LONG_MAX__
#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__
# ifndef LLONG_MAX
# define LLONG_MAX __LONG_LONG_MAX__
# endif
# ifndef LLONG_MIN
# define LLONG_MIN (-1 - LLONG_MAX)
# endif
# ifndef ULLONG_MAX
# define ULLONG_MAX (LLONG_MAX * 2ull + 1)
# endif
#endif
#ifndef INT_FAST64_MAX
# ifdef LLONG_MAX
typedef long long int_fast64_t;
# define INT_FAST64_MIN LLONG_MIN
# define INT_FAST64_MAX LLONG_MAX
# else
# 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
# if 1 <= LONG_MAX >> 31 >> 31
typedef long int_fast64_t;
# define INT_FAST64_MIN LONG_MIN
# define INT_FAST64_MAX LONG_MAX
# else
/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */
typedef long long int_fast64_t;
# define INT_FAST64_MIN LLONG_MIN
# define INT_FAST64_MAX LLONG_MAX
# endif
#endif
#ifndef PRIdFAST64
# if INT_FAST64_MAX == LLONG_MAX
# define PRIdFAST64 "lld"
# else
# if INT_FAST64_MAX == LONG_MAX
# define PRIdFAST64 "ld"
# else
# define PRIdFAST64 "lld"
# endif
#endif
@ -298,6 +354,9 @@ typedef int int_fast32_t;
#ifndef INTMAX_MAX
# ifdef LLONG_MAX
typedef long long intmax_t;
# ifndef HAVE_STRTOLL
# define HAVE_STRTOLL true
# endif
# if HAVE_STRTOLL
# define strtoimax strtoll
# endif
@ -321,66 +380,183 @@ typedef long intmax_t;
# endif
#endif
#ifndef UINT_FAST64_MAX
# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
typedef unsigned long long uint_fast64_t;
# else
# if ULONG_MAX >> 31 >> 1 < 0xffffffff
Please use a compiler that supports a 64-bit integer type (or wider);
you may need to compile with "-DHAVE_STDINT_H".
#ifndef PTRDIFF_MAX
# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
#endif
#ifndef UINT_FAST32_MAX
typedef unsigned long uint_fast32_t;
#endif
#ifndef UINT_FAST64_MAX
# if 3 <= ULONG_MAX >> 31 >> 31
typedef unsigned long uint_fast64_t;
# define UINT_FAST64_MAX ULONG_MAX
# else
/* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */
typedef unsigned long long uint_fast64_t;
# define UINT_FAST64_MAX ULLONG_MAX
# endif
#endif
#ifndef UINTMAX_MAX
# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
# ifdef ULLONG_MAX
typedef unsigned long long uintmax_t;
# define UINTMAX_MAX ULLONG_MAX
# else
typedef unsigned long uintmax_t;
# define UINTMAX_MAX ULONG_MAX
# endif
#endif
#ifndef PRIuMAX
# if defined ULLONG_MAX || defined __LONG_LONG_MAX__
# ifdef ULLONG_MAX
# define PRIuMAX "llu"
# else
# 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
#endif /* PORT_TO_C89 */
/* The maximum size of any created object, as a signed integer.
Although the C standard does not outright prohibit larger objects,
behavior is undefined if the result of pointer subtraction does not
fit into ptrdiff_t, and the code assumes in several places that
pointer subtraction works. As a practical matter it's OK to not
support objects larger than this. */
#define INDEX_MAX ((ptrdiff_t) min(PTRDIFF_MAX, SIZE_MAX))
/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like
hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */
#if !defined HAVE_STDCKDINT_H && defined __has_include
# if __has_include(<stdckdint.h>)
# define HAVE_STDCKDINT_H true
# endif
#endif
#ifdef HAVE_STDCKDINT_H
# if HAVE_STDCKDINT_H
# include <stdckdint.h>
# endif
#elif defined __EDG__
/* Do nothing, to work around EDG bug <https://bugs.gnu.org/53256>. */
#elif defined __has_builtin
# if __has_builtin(__builtin_add_overflow)
# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
# endif
# if __has_builtin(__builtin_sub_overflow)
# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
# endif
# if __has_builtin(__builtin_mul_overflow)
# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
# endif
#elif 7 <= __GNUC__
# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
#endif
#if 3 <= __GNUC__
# define ATTRIBUTE_CONST __attribute__ ((const))
# define ATTRIBUTE_MALLOC __attribute__ ((__malloc__))
# define ATTRIBUTE_PURE __attribute__ ((__pure__))
# define ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
# define ATTRIBUTE_MALLOC __attribute__((malloc))
# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec))
#else
# define ATTRIBUTE_CONST /* empty */
# define ATTRIBUTE_MALLOC /* 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__))
#if (defined __has_c_attribute \
&& (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__))
# define HAVE___HAS_C_ATTRIBUTE true
#else
# define _Noreturn
# define HAVE___HAS_C_ATTRIBUTE false
#endif
#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(deprecated)
# define ATTRIBUTE_DEPRECATED [[deprecated]]
# endif
#endif
#ifndef ATTRIBUTE_DEPRECATED
# if 3 < __GNUC__ + (2 <= __GNUC_MINOR__)
# define ATTRIBUTE_DEPRECATED __attribute__((deprecated))
# else
# define ATTRIBUTE_DEPRECATED /* empty */
# endif
#endif
#if __STDC_VERSION__ < 199901 && !defined restrict
#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(fallthrough)
# define ATTRIBUTE_FALLTHROUGH [[fallthrough]]
# endif
#endif
#ifndef ATTRIBUTE_FALLTHROUGH
# if 7 <= __GNUC__
# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough))
# else
# define ATTRIBUTE_FALLTHROUGH ((void) 0)
# endif
#endif
#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(maybe_unused)
# define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]]
# endif
#endif
#ifndef ATTRIBUTE_MAYBE_UNUSED
# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
# define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused))
# else
# define ATTRIBUTE_MAYBE_UNUSED /* empty */
# endif
#endif
#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(noreturn)
# define ATTRIBUTE_NORETURN [[noreturn]]
# endif
#endif
#ifndef ATTRIBUTE_NORETURN
# if 201112 <= __STDC_VERSION__
# define ATTRIBUTE_NORETURN _Noreturn
# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__)
# define ATTRIBUTE_NORETURN __attribute__((noreturn))
# else
# define ATTRIBUTE_NORETURN /* empty */
# endif
#endif
#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(reproducible)
# define ATTRIBUTE_REPRODUCIBLE [[reproducible]]
# endif
#endif
#ifndef ATTRIBUTE_REPRODUCIBLE
# if 3 <= __GNUC__
# define ATTRIBUTE_REPRODUCIBLE __attribute__((pure))
# else
# define ATTRIBUTE_REPRODUCIBLE /* empty */
# endif
#endif
#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(unsequenced)
# define ATTRIBUTE_UNSEQUENCED [[unsequenced]]
# endif
#endif
#ifndef ATTRIBUTE_UNSEQUENCED
# if 3 <= __GNUC__
# define ATTRIBUTE_UNSEQUENCED __attribute__((const))
# else
# define ATTRIBUTE_UNSEQUENCED /* empty */
# endif
#endif
#if (__STDC_VERSION__ < 199901 && !defined restrict \
&& (PORT_TO_C89 || defined _MSC_VER))
# define restrict /* empty */
#endif
@ -418,11 +594,12 @@ typedef unsigned long uintmax_t;
# define TZ_TIME_T 0
#endif
#if TZ_TIME_T
# ifdef LOCALTIME_IMPLEMENTATION
#if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T
static time_t sys_time(time_t *x) { return time(x); }
#endif
#if TZ_TIME_T
typedef time_tz tz_time_t;
# undef asctime
@ -477,8 +654,6 @@ typedef time_tz tz_time_t;
# define tzfree tz_tzfree
# undef tzset
# define tzset tz_tzset
# undef tzsetwall
# define tzsetwall tz_tzsetwall
# if HAVE_STRFTIME_L
# undef strftime_l
# define strftime_l tz_strftime_l
@ -498,11 +673,16 @@ typedef time_tz tz_time_t;
# define altzone tz_altzone
# endif
char *asctime(struct tm const *);
# if __STDC_VERSION__ < 202311
# define DEPRECATED_IN_C23 /* empty */
# else
# define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED
# endif
DEPRECATED_IN_C23 char *asctime(struct tm const *);
char *asctime_r(struct tm const *restrict, char *restrict);
char *ctime(time_t const *);
DEPRECATED_IN_C23 char *ctime(time_t const *);
char *ctime_r(time_t const *, char *);
double difftime(time_t, time_t) ATTRIBUTE_CONST;
ATTRIBUTE_UNSEQUENCED double difftime(time_t, time_t);
size_t strftime(char *restrict, size_t, char const *restrict,
struct tm const *restrict);
# if HAVE_STRFTIME_L
@ -515,9 +695,24 @@ struct tm *localtime(time_t const *);
struct tm *localtime_r(time_t const *restrict, struct tm *restrict);
time_t mktime(struct tm *);
time_t time(time_t *);
time_t timegm(struct tm *);
void tzset(void);
#endif
#ifndef HAVE_DECL_TIMEGM
# if (202311 <= __STDC_VERSION__ \
|| defined __GLIBC__ || defined __tm_zone /* musl */ \
|| defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
|| (defined __APPLE__ && defined __MACH__))
# define HAVE_DECL_TIMEGM true
# else
# define HAVE_DECL_TIMEGM false
# endif
#endif
#if !HAVE_DECL_TIMEGM && !defined timegm
time_t timegm(struct tm *);
#endif
#if !HAVE_DECL_ASCTIME_R && !defined asctime_r
extern char *asctime_r(struct tm const *restrict, char *restrict);
#endif
@ -550,21 +745,18 @@ extern long altzone;
** declarations if time_tz is defined.
*/
#ifdef STD_INSPIRED
# if TZ_TIME_T || !defined tzsetwall
void tzsetwall(void);
#ifndef STD_INSPIRED
# define STD_INSPIRED 0
#endif
#if STD_INSPIRED
# if TZ_TIME_T || !defined offtime
struct tm *offtime(time_t const *, long);
# endif
# if TZ_TIME_T || !defined timegm
time_t timegm(struct tm *);
# endif
# if TZ_TIME_T || !defined timelocal
time_t timelocal(struct tm *);
# endif
# if TZ_TIME_T || !defined timeoff
time_t timeoff(struct tm *, long);
# define EXTERN_TIMEOFF
# endif
# if TZ_TIME_T || !defined time2posix
time_t time2posix(time_t);
@ -576,7 +768,9 @@ time_t posix2time(time_t);
/* Infer TM_ZONE on systems where this information is known, but suppress
guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */
#if (defined __GLIBC__ \
#if (200809 < _POSIX_VERSION \
|| defined __GLIBC__ \
|| defined __tm_zone /* musl */ \
|| defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
|| (defined __APPLE__ && defined __MACH__))
# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF
@ -602,12 +796,12 @@ struct tm *localtime_rz(timezone_t restrict, time_t const *restrict,
time_t mktime_z(timezone_t restrict, struct tm *restrict);
timezone_t tzalloc(char const *);
void tzfree(timezone_t);
# ifdef STD_INSPIRED
# if STD_INSPIRED
# if TZ_TIME_T || !defined posix2time_z
time_t posix2time_z(timezone_t, time_t) ATTRIBUTE_PURE;
ATTRIBUTE_REPRODUCIBLE time_t posix2time_z(timezone_t, time_t);
# endif
# if TZ_TIME_T || !defined time2posix_z
time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
ATTRIBUTE_REPRODUCIBLE time_t time2posix_z(timezone_t, time_t);
# endif
# endif
#endif
@ -616,18 +810,15 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
** Finally, some convenience items.
*/
#if HAVE_STDBOOL_H
# include <stdbool.h>
#else
# define true 1
# define false 0
# define bool int
#endif
#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
#define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type))
#define TYPE_SIGNED(type) (((type) -1) < 0)
#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0)
/* Minimum and maximum of two values. Use lower case to avoid
naming clashes with standard include files. */
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
/* Max and min values of the integer type T, of which only the bottom
B bits are used, and where the highest-order used bit is considered
to be a sign bit if T is signed. */
@ -642,12 +833,12 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t))
/* The extreme time values. These are macros, not constants, so that
any portability problem occur only when compiling .c files that use
any portability problems occur only when compiling .c files that use
the macros, which is safer for applications that need only zdump and zic.
This implementation assumes no padding if time_t is signed and
either the compiler lacks support for _Generic or time_t is not one
of the standard signed integer types. */
#if HAVE_GENERIC
#if HAVE__GENERIC
# define TIME_T_MIN \
_Generic((time_t) 0, \
signed char: SCHAR_MIN, short: SHRT_MIN, \
@ -660,10 +851,23 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
default: TIME_T_MAX_NO_PADDING) \
: (time_t) -1)
enum { SIGNED_PADDING_CHECK_NEEDED
= _Generic((time_t) 0,
signed char: false, short: false,
int: false, long: false, long long: false,
default: true) };
#else
# define TIME_T_MIN TIME_T_MIN_NO_PADDING
# define TIME_T_MAX TIME_T_MAX_NO_PADDING
enum { SIGNED_PADDING_CHECK_NEEDED = true };
#endif
/* Try to check the padding assumptions. Although TIME_T_MAX and the
following check can both have undefined behavior on oddball
platforms due to shifts exceeding widths of signed integers, these
platforms' compilers are likely to diagnose these issues in integer
constant expressions, so it shouldn't hurt to check statically. */
static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED
|| TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1);
/*
** 302 / 1000 is log10(2.0) rounded up.
@ -685,10 +889,45 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
# define INITIALIZE(x)
#endif
/* Whether memory access must strictly follow the C standard.
If 0, it's OK to read uninitialized storage so long as the value is
not relied upon. Defining it to 0 lets mktime access parts of
struct tm that might be uninitialized, as a heuristic when the
standard doesn't say what to return and when tm_gmtoff can help
mktime likely infer a better value. */
#ifndef UNINIT_TRAP
# define UNINIT_TRAP 0
#endif
/* localtime.c sometimes needs access to timeoff if it is not already public.
tz_private_timeoff should be used only by localtime.c. */
#if (!defined EXTERN_TIMEOFF \
&& defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP))
# ifndef timeoff
# define timeoff tz_private_timeoff
# endif
# define EXTERN_TIMEOFF
#endif
#ifdef EXTERN_TIMEOFF
time_t timeoff(struct tm *, long);
#endif
#ifdef DEBUG
# undef unreachable
# define unreachable() abort()
#elif !defined unreachable
# ifdef __has_builtin
# if __has_builtin(__builtin_unreachable)
# define unreachable() __builtin_unreachable()
# endif
# elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__)
# define unreachable() __builtin_unreachable()
# endif
# ifndef unreachable
# define unreachable() ((void) 0)
# endif
#endif
/*
** For the benefit of GNU folk...
** '_(MSGID)' uses the current locale's message library string for MSGID.
@ -708,49 +947,73 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
#if HAVE_INCOMPATIBLE_CTIME_R
#undef asctime_r
#undef ctime_r
char *asctime_r(struct tm const *, char *);
char *asctime_r(struct tm const *restrict, char *restrict);
char *ctime_r(time_t const *, char *);
#endif /* HAVE_INCOMPATIBLE_CTIME_R */
/* Handy macros that are independent of tzfile implementation. */
#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */
enum {
SECSPERMIN = 60,
MINSPERHOUR = 60,
SECSPERHOUR = SECSPERMIN * MINSPERHOUR,
HOURSPERDAY = 24,
DAYSPERWEEK = 7,
DAYSPERNYEAR = 365,
DAYSPERLYEAR = DAYSPERNYEAR + 1,
MONSPERYEAR = 12,
YEARSPERREPEAT = 400 /* years before a Gregorian repeat */
};
#define SECSPERMIN 60
#define MINSPERHOUR 60
#define HOURSPERDAY 24
#define DAYSPERWEEK 7
#define DAYSPERNYEAR 365
#define DAYSPERLYEAR 366
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY ((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 DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1)
#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY)
#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT)
#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
/* How many years to generate (in zic.c) or search through (in localtime.c).
This is two years larger than the obvious 400, to avoid edge cases.
E.g., suppose a non-POSIX.1-2017 rule applies from 2012 on with transitions
in March and September, plus 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 is not overkill for localtime.c, as a one-year bump
would mishandle 2023d's America/Ciudad_Juarez for November 2422. */
enum { years_of_observations = YEARSPERREPEAT + 2 };
#define TM_YEAR_BASE 1900
enum {
TM_SUNDAY,
TM_MONDAY,
TM_TUESDAY,
TM_WEDNESDAY,
TM_THURSDAY,
TM_FRIDAY,
TM_SATURDAY
};
#define EPOCH_YEAR 1970
#define EPOCH_WDAY TM_THURSDAY
enum {
TM_JANUARY,
TM_FEBRUARY,
TM_MARCH,
TM_APRIL,
TM_MAY,
TM_JUNE,
TM_JULY,
TM_AUGUST,
TM_SEPTEMBER,
TM_OCTOBER,
TM_NOVEMBER,
TM_DECEMBER
};
enum {
TM_YEAR_BASE = 1900,
TM_WDAY_BASE = TM_MONDAY,
EPOCH_YEAR = 1970,
EPOCH_WDAY = TM_THURSDAY
};
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
@ -768,14 +1031,4 @@ char *ctime_r(time_t const *, char *);
#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
/*
** The Gregorian year averages 365.2425 days, which is 31556952 seconds.
*/
#define AVGSECSPERYEAR 31556952L
#define SECSPERREPEAT \
((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR)
#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */
#endif /* !defined PRIVATE_H */

View File

@ -21,14 +21,6 @@
** Information about time zone files.
*/
#ifndef TZDIR
#define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */
#endif /* !defined TZDIR */
#ifndef TZDEFAULT
#define TZDEFAULT "/etc/localtime"
#endif /* !defined TZDEFAULT */
#ifndef TZDEFRULES
# define TZDEFRULES "posixrules"
#endif /* !defined TZDEFRULES */
@ -44,7 +36,7 @@
struct tzhead {
char tzh_magic[4]; /* TZ_MAGIC */
char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */
char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */
char tzh_reserved[15]; /* reserved; must be zero */
char tzh_ttisutcnt[4]; /* coded number of trans. time flags */
char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
@ -86,11 +78,11 @@ struct tzhead {
** 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).
** (with nothing between the newlines if there is no POSIX.1-2017
** 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
** First, the 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
@ -103,20 +95,24 @@ struct tzhead {
*/
#ifndef TZ_MAX_TIMES
/* This must be at least 242 for Europe/London with 'zic -b fat'. */
# 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. */
/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */
# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
#endif /* !defined TZ_MAX_TYPES */
#ifndef TZ_MAX_CHARS
/* This must be at least 40 for America/Anchorage. */
# 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
/* This must be at least 27 for leap seconds from 1972 through mid-2023.
There's a plan to discontinue leap seconds by 2035. */
# define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
#endif /* !defined TZ_MAX_LEAPS */

View File

@ -10,7 +10,7 @@ REPORT_BUGS_TO=tz@iana.org
# Porting notes:
#
# This script requires a Posix-like shell and prefers 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
@ -18,34 +18,46 @@ REPORT_BUGS_TO=tz@iana.org
#
# Bash <https://www.gnu.org/software/bash/>
# Korn Shell <http://www.kornshell.com/>
# MirBSD Korn Shell <https://www.mirbsd.org/mksh.htm>
# MirBSD Korn Shell <http://www.mirbsd.org/mksh.htm>
#
# 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.
# For portability to Solaris 10 /bin/sh (supported by Oracle through
# January 2027) this script avoids some POSIX features and common
# extensions, such as $(...), $((...)), ! CMD, unquoted ^, ${#ID},
# ${ID##PAT}, ${ID%%PAT}, and $10. Although some of these constructs
# work sometimes, it's simpler to avoid them entirely.
#
# 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:
# This script also uses several features of POSIX awk.
# If your host lacks awk, or has an old awk that does not conform to POSIX,
# you can use any of the following free programs instead:
#
# Gawk (GNU awk) <https://www.gnu.org/software/gawk/>
# mawk <https://invisible-island.net/mawk/>
# nawk <https://github.com/onetrueawk/awk>
#
# Because 'awk "VAR=VALUE" ...' and 'awk -v "VAR=VALUE" ...' are not portable
# if VALUE contains \, ", or newline, awk scripts in this file use:
# awk 'BEGIN { VAR = substr(ARGV[1], 2); ARGV[1] = "" } ...' ="VALUE"
# The substr avoids problems when VALUE is of the form X=Y and would be
# misinterpreted as an assignment.
# This script does not want path expansion.
set -f
# Specify default values for environment variables if they are unset.
: ${AWK=awk}
: ${TZDIR=`pwd`}
: ${PWD=`pwd`}
: ${TZDIR=$PWD}
# Output one argument as-is to standard output.
# Output one argument as-is to standard output, with trailing newline.
# Safer than 'echo', which can mishandle '\' or leading '-'.
say() {
printf '%s\n' "$1"
}
# Check for awk Posix compliance.
($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
# Check for awk POSIX compliance.
($AWK -v x=y 'BEGIN { exit 123 }') <>/dev/null >&0 2>&0
[ $? = 123 ] || {
say >&2 "$0: Sorry, your '$AWK' program is not Posix compatible."
say >&2 "$0: Sorry, your '$AWK' program is not POSIX compatible."
exit 1
}
@ -79,14 +91,14 @@ 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.
# Exit on EOF or I/O error. Use the shell's nicer 'select' builtin if
# available, falling back on a 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') </dev/null 2>/dev/null
(eval 'set --; select x; do break; done; exit') <>/dev/null 2>&0
esac
then
# Do this inside 'eval', as otherwise the shell might exit when parsing it
@ -101,19 +113,12 @@ then
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 $# : '.*'`
print_nargs_length="BEGIN {print length(\"$#\");}"
select_width=`$AWK "$print_nargs_length"`
select_i=
@ -124,14 +129,14 @@ else
select_i=0
for select_word
do
select_i=`expr $select_i + 1`
select_i=`$AWK "BEGIN { print $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`
shift `$AWK "BEGIN { print $select_i - 1 }"`
select_result=$1
break
fi
@ -161,57 +166,124 @@ do
-*)
say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1;;
*)
say >&2 "$0: try '$0 --help'"; exit 1 ;;
say >&2 "$0: try '$0 --help'"; exit 1
esac
done
shift `expr $OPTIND - 1`
shift `$AWK "BEGIN { print $OPTIND - 1 }"`
case $# in
0) ;;
*) say >&2 "$0: $1: unknown argument"; exit 1 ;;
*) say >&2 "$0: $1: unknown argument"; exit 1
esac
# Make sure the tables are readable.
TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab
for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
do
<"$f" || {
# translit=true to try transliteration.
# This is false if U+12345 CUNEIFORM SIGN URU TIMES KI has length 1
# which means awk (and presumably the shell) do not need transliteration.
if $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) == 1 }'; then
translit=true
else
translit=false
fi
# Read into shell variable $1 the contents of file $2.
# Convert to the current locale's encoding if possible,
# as the shell aligns columns better that way.
# If GNU iconv's //TRANSLIT does not work, fall back on POSIXish iconv;
# if that does not work, fall back on 'cat'.
read_file() {
{ $translit && {
eval "$1=\`(iconv -f UTF-8 -t //TRANSLIT) 2>/dev/null <\"\$2\"\`" ||
eval "$1=\`(iconv -f UTF-8) 2>/dev/null <\"\$2\"\`"
}; } ||
eval "$1=\`cat <\"\$2\"\`" || {
say >&2 "$0: time zone files are not set up correctly"
exit 1
}
done
# If the current locale does not support UTF-8, convert data to current
# locale's format if possible, as the shell aligns columns better that way.
# Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI.
! $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) != 1 }' &&
{ tmp=`(mktemp -d) 2>/dev/null` || {
tmp=${TMPDIR-/tmp}/tzselect.$$ &&
(umask 77 && mkdir -- "$tmp")
};} &&
trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM &&
(iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \
2>/dev/null &&
TZ_COUNTRY_TABLE=$tmp/iso3166.tab &&
iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab &&
TZ_ZONE_TABLE=$tmp/$zonetabtype.tab
}
read_file TZ_COUNTRY_TABLE "$TZDIR/iso3166.tab"
read_file TZ_ZONETABTYPE_TABLE "$TZDIR/$zonetabtype.tab"
TZ_ZONENOW_TABLE=
newline='
'
IFS=$newline
# Awk script to read a time zone table and output the same table,
# with each column preceded by its distance from 'here'.
output_distances='
# Awk script to output a country list.
output_country_list='
BEGIN {
continent_re = substr(ARGV[1], 2)
TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
TZ_ZONE_TABLE = substr(ARGV[3], 2)
ARGV[1] = ARGV[2] = ARGV[3] = ""
FS = "\t"
while (getline <TZ_COUNTRY_TABLE)
if ($0 ~ /^[^#]/)
nlines = split(TZ_ZONE_TABLE, line, /\n/)
for (iline = 1; iline <= nlines; iline++) {
$0 = line[iline]
commentary = $0 ~ /^#@/
if (commentary) {
if ($0 !~ /^#@/)
continue
col1ccs = substr($1, 3)
conts = $2
} else {
col1ccs = $1
conts = $3
}
ncc = split(col1ccs, cc, /,/)
ncont = split(conts, cont, /,/)
for (i = 1; i <= ncc; i++) {
elsewhere = commentary
for (ci = 1; ci <= ncont; ci++) {
if (cont[ci] ~ continent_re) {
if (!cc_seen[cc[i]]++)
cc_list[++ccs] = cc[i]
elsewhere = 0
}
}
if (elsewhere)
for (i = 1; i <= ncc; i++)
cc_elsewhere[cc[i]] = 1
}
}
nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 !~ /^#/)
cc_name[$1] = $2
}
for (i = 1; i <= ccs; i++) {
country = cc_list[i]
if (cc_elsewhere[country])
continue
if (cc_name[country])
country = cc_name[country]
print country
}
}
'
# Awk script to process a time zone table and output the same table,
# with each row preceded by its distance from 'here'.
# If output_times is set, each row is instead preceded by its local time
# and any apostrophes are escaped for the shell.
output_distances_or_times='
BEGIN {
coord = substr(ARGV[1], 2)
TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
TZ_ZONE_TABLE = substr(ARGV[3], 2)
ARGV[1] = ARGV[2] = ARGV[3] = ""
FS = "\t"
if (!output_times) {
nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 ~ /^#/)
continue
country[$1] = $2
}
country["US"] = "US" # Otherwise the strings get too long.
}
}
function abs(x) {
return x < 0 ? -x : x;
}
@ -270,20 +342,41 @@ output_distances='
BEGIN {
coord_lat = convert_latitude(coord)
coord_long = convert_longitude(coord)
nlines = split(TZ_ZONE_TABLE, line, /\n/)
for (h = 1; h <= nlines; h++) {
$0 = line[h]
if ($0 ~ /^#/)
continue
inline[inlines++] = $0
ncc = split($1, cc, /,/)
for (i = 1; i <= ncc; i++)
cc_used[cc[i]]++
}
/^[^#]/ {
here_lat = convert_latitude($2)
here_long = convert_longitude($2)
line = $1 "\t" $2 "\t" $3
for (h = 0; h < inlines; h++) {
$0 = inline[h]
outline = $1 "\t" $2 "\t" $3
sep = "\t"
ncc = split($1, cc, /,/)
split("", item_seen)
item_seen[""] = 1
for (i = 1; i <= ncc; i++) {
line = line sep country[cc[i]]
sep = ", "
item = cc_used[cc[i]] <= 1 ? country[cc[i]] : $4
if (item_seen[item]++)
continue
outline = outline sep item
sep = "; "
}
if (output_times) {
fmt = "TZ='\''%s'\'' date +'\''%d %%Y %%m %%d %%H:%%M %%a %%b\t%s'\''\n"
gsub(/'\''/, "&\\\\&&", outline)
printf fmt, $3, h, outline
} else {
here_lat = convert_latitude($2)
here_long = convert_longitude($2)
printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), \
outline
}
}
if (NF == 4)
line = line " - " $4
printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line
}
'
@ -295,7 +388,10 @@ while
continent=
country=
country_result=
region=
time=
TZ_ZONE_TABLE=$TZ_ZONETABTYPE_TABLE
case $coord in
?*)
@ -304,20 +400,36 @@ while
# Ask the user for continent or ocean.
echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
echo >&2 \
'Please select a continent, ocean, "coord", "TZ", "time", or "now".'
quoted_continents=`
$AWK '
BEGIN { FS = "\t" }
/^[^#]/ {
entry = substr($3, 1, index($3, "/") - 1)
function handle_entry(entry) {
entry = substr(entry, 1, index(entry, "/") - 1)
if (entry == "America")
entry = entry "s"
if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
entry = entry " Ocean"
printf "'\''%s'\''\n", entry
}
' <"$TZ_ZONE_TABLE" |
BEGIN {
TZ_ZONETABTYPE_TABLE = substr(ARGV[1], 2)
ARGV[1] = ""
FS = "\t"
nlines = split(TZ_ZONETABTYPE_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 ~ /^[^#]/)
handle_entry($3)
else if ($0 ~ /^#@/) {
ncont = split($2, cont, /,/)
for (ci = 1; ci <= ncont; ci++)
handle_entry(cont[ci])
}
}
}
' ="$TZ_ZONETABTYPE_TABLE" |
sort -u |
tr '\n' ' '
echo ''
@ -326,18 +438,50 @@ while
eval '
doselect '"$quoted_continents"' \
"coord - I want to use geographical coordinates." \
"TZ - I want to specify the timezone using the Posix TZ format."
"TZ - I want to specify the timezone using a POSIX.1-2017 TZ string." \
"time - I know local time already." \
"now - Like \"time\", but configure only for timestamps from now on."
continent=$select_result
case $continent in
Americas) continent=America;;
*" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
*)
# Get the first word of $continent. Path expansion is disabled
# so this works even with "*", which should not happen.
IFS=" "
for continent in $continent ""; do break; done
IFS=$newline;;
esac
case $zonetabtype,$continent in
zonenow,*) ;;
*,now)
${TZ_ZONENOW_TABLE:+:} read_file TZ_ZONENOW_TABLE "$TZDIR/zonenow.tab"
TZ_ZONE_TABLE=$TZ_ZONENOW_TABLE
esac
'
esac
case $continent in
TZ)
# Ask the user for a Posix TZ string. Check that it conforms.
# Ask the user for a POSIX.1-2017 TZ string. Check that it conforms.
check_POSIX_TZ_string='
BEGIN {
tz = substr(ARGV[1], 2)
ARGV[1] = ""
tzname = ("(<[[:alnum:]+-][[:alnum:]+-][[:alnum:]+-]+>" \
"|[[:alpha:]][[:alpha:]][[:alpha:]]+)")
time = ("(2[0-4]|[0-1]?[0-9])" \
"(:[0-5][0-9](:[0-5][0-9])?)?")
offset = "[-+]?" time
mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
jdate = ("((J[1-9]|[0-9]|J?[1-9][0-9]" \
"|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])")
datetime = ",(" mdate "|" jdate ")(/" time ")?"
tzpattern = ("^(:.*|" tzname offset "(" tzname \
"(" offset ")?(" datetime datetime ")?)?)$")
exit tz ~ tzpattern
}
'
while
echo >&2 'Please enter the desired value' \
'of the TZ environment variable.'
@ -345,25 +489,12 @@ while
'AEST and is 10 hours'
echo >&2 'ahead (east) of Greenwich,' \
'with no daylight saving time.'
read TZ
$AWK -v TZ="$TZ" 'BEGIN {
tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})"
time = "(2[0-4]|[0-1]?[0-9])" \
"(:[0-5][0-9](:[0-5][0-9])?)?"
offset = "[-+]?" time
mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \
"|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])"
datetime = ",(" mdate "|" jdate ")(/" time ")?"
tzpattern = "^(:.*|" tzname offset "(" tzname \
"(" offset ")?(" datetime datetime ")?)?)$"
if (TZ ~ tzpattern) exit 1
exit 0
}'
read tz
$AWK "$check_POSIX_TZ_string" ="$tz"
do
say >&2 "'$TZ' is not a conforming Posix timezone string."
say >&2 "'$tz' is not a conforming POSIX.1-2017 timezone string."
done
TZ_for_date=$TZ;;
TZ_for_date=$tz;;
*)
case $continent in
coord)
@ -374,56 +505,155 @@ while
echo >&2 'For example, +4042-07403 stands for'
echo >&2 '40 degrees 42 minutes north,' \
'74 degrees 3 minutes west.'
read coord;;
read coord
esac
distance_table=`$AWK \
-v coord="$coord" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
"$output_distances" <"$TZ_ZONE_TABLE" |
distance_table=`
$AWK \
"$output_distances_or_times" \
="$coord" ="$TZ_COUNTRY_TABLE" ="$TZ_ZONE_TABLE" |
sort -n |
sed "${location_limit}q"
$AWK "{print} NR == $location_limit { exit }"
`
regions=`say "$distance_table" | $AWK '
BEGIN { FS = "\t" }
{ print $NF }
'`
echo >&2 'Please select one of the following timezones,' \
regions=`
$AWK '
BEGIN {
distance_table = substr(ARGV[1], 2)
ARGV[1] = ""
nlines = split(distance_table, line, /\n/)
for (nr = 1; nr <= nlines; nr++) {
nf = split(line[nr], f, /\t/)
print f[nf]
}
}
' ="$distance_table"
`
echo >&2 'Please select one of the following timezones,'
echo >&2 'listed roughly in increasing order' \
"of distance from $coord".
doselect $regions
region=$select_result
TZ=`say "$distance_table" | $AWK -v region="$region" '
BEGIN { FS="\t" }
$NF == region { print $4 }
'`
tz=`
$AWK '
BEGIN {
distance_table = substr(ARGV[1], 2)
region = substr(ARGV[2], 2)
ARGV[1] = ARGV[2] = ""
nlines = split(distance_table, line, /\n/)
for (nr = 1; nr <= nlines; nr++) {
nf = split(line[nr], f, /\t/)
if (f[nf] == region)
print f[4]
}
}
' ="$distance_table" ="$region"
`;;
*)
case $continent in
now|time)
minute_format='%a %b %d %H:%M'
old_minute=`TZ=UTC0 date +"$minute_format"`
for i in 1 2 3
do
time_table_command=`
$AWK \
-v output_times=1 \
"$output_distances_or_times" \
= = ="$TZ_ZONE_TABLE"
`
time_table=`eval "$time_table_command"`
new_minute=`TZ=UTC0 date +"$minute_format"`
case $old_minute in
"$new_minute") break
esac
old_minute=$new_minute
done
echo >&2 "The system says Universal Time is $new_minute."
echo >&2 "Assuming that's correct, what is the local time?"
sorted_table=`say "$time_table" | sort -k2n -k2,5 -k1n` || {
say >&2 "$0: cannot sort time table"
exit 1
}
eval doselect `
$AWK '
BEGIN {
sorted_table = substr(ARGV[1], 2)
ARGV[1] = ""
nlines = split(sorted_table, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
outline = $6 " " $7 " " $4 " " $5
if (outline == oldline)
continue
oldline = outline
gsub(/'\''/, "&\\\\&&", outline)
printf "'\''%s'\''\n", outline
}
}
' ="$sorted_table"
`
time=$select_result
continent_re='^'
zone_table=`
$AWK '
BEGIN {
time = substr(ARGV[1], 2)
time_table = substr(ARGV[2], 2)
ARGV[1] = ARGV[2] = ""
nlines = split(time_table, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($6 " " $7 " " $4 " " $5 == time) {
sub(/[^\t]*\t/, "")
print
}
}
}
' ="$time" ="$time_table"
`
countries=`
$AWK \
"$output_country_list" \
="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
sort -f
`
;;
*)
# Get list of names of countries in the continent or ocean.
countries=`$AWK \
-v continent="$continent" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
'
BEGIN { FS = "\t" }
/^#/ { next }
$3 ~ ("^" continent "/") {
ncc = split($1, cc, /,/)
for (i = 1; i <= ncc; i++)
if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i]
}
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`
continent_re="^$continent/"
zone_table=$TZ_ZONE_TABLE
esac
# Get list of names of countries in the continent or ocean.
countries=`
$AWK \
"$output_country_list" \
="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
sort -f
`
# If all zone table entries have comments, and there are
# at most 22 entries, asked based on those comments.
# This fits the prompt onto old-fashioned 24-line screens.
regions=`
$AWK '
BEGIN {
TZ_ZONE_TABLE = substr(ARGV[1], 2)
ARGV[1] = ""
FS = "\t"
nlines = split(TZ_ZONE_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 ~ /^[^#]/ && !missing_comment) {
if ($4)
comment[++inlines] = $4
else
missing_comment = 1
}
}
if (!missing_comment && inlines <= 22)
for (i = 1; i <= inlines; i++)
print comment[i]
}
' ="$zone_table"
`
# If there's more than one country, ask the user which one.
case $countries in
@ -431,6 +661,7 @@ while
echo >&2 'Please select a country' \
'whose clocks agree with yours.'
doselect $countries
country_result=$select_result
country=$select_result;;
*)
country=$countries
@ -438,58 +669,77 @@ while
# Get list of timezones in the country.
regions=`$AWK \
-v country="$country" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
'
regions=`
$AWK '
BEGIN {
country = substr(ARGV[1], 2)
TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
TZ_ZONE_TABLE = substr(ARGV[3], 2)
ARGV[1] = ARGV[2] = ARGV[3] = ""
FS = "\t"
cc = country
while (getline <TZ_COUNTRY_TABLE) {
nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 !~ /^#/ && country == $2) {
cc = $1
break
}
}
nlines = split(TZ_ZONE_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 ~ /^#/)
continue
if ($1 ~ cc)
print $4
}
/^#/ { next }
$1 ~ cc { print $4 }
' <"$TZ_ZONE_TABLE"`
}
' ="$country" ="$TZ_COUNTRY_TABLE" ="$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 timezones.'
doselect $regions
region=$select_result;;
*)
region=$regions
region=$select_result
esac
# Determine TZ from country and region.
TZ=`$AWK \
-v country="$country" \
-v region="$region" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
'
# Determine tz from country and region.
tz=`
$AWK '
BEGIN {
country = substr(ARGV[1], 2)
region = substr(ARGV[2], 2)
TZ_COUNTRY_TABLE = substr(ARGV[3], 2)
TZ_ZONE_TABLE = substr(ARGV[4], 2)
ARGV[1] = ARGV[2] = ARGV[3] = ARGV[4] = ""
FS = "\t"
cc = country
while (getline <TZ_COUNTRY_TABLE) {
nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 !~ /^#/ && country == $2) {
cc = $1
break
}
}
nlines = split(TZ_ZONE_TABLE, line, /\n/)
for (i = 1; i <= nlines; i++) {
$0 = line[i]
if ($0 ~ /^#/)
continue
if ($1 ~ cc && ($4 == region || !region))
print $3
}
/^#/ { next }
$1 ~ cc && $4 == region { print $3 }
' <"$TZ_ZONE_TABLE"`
}
' ="$country" ="$region" ="$TZ_COUNTRY_TABLE" ="$zone_table"
`
esac
# Make sure the corresponding zoneinfo file exists.
TZ_for_date=$TZDIR/$TZ
TZ_for_date=$TZDIR/$tz
<"$TZ_for_date" || {
say >&2 "$0: time zone files are not set up correctly"
exit 1
@ -506,32 +756,39 @@ while
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)
if $AWK '
function getsecs(d) {
return match(d, /.*:[0-5][0-9]/) ? substr(d, RLENGTH - 1, 2) : ""
}
BEGIN { exit getsecs(ARGV[1]) != getsecs(ARGV[2]) }
' ="$TZdate" ="$UTdate"
then
extra_info="
Selected time is now: $TZdate.
Universal Time is now: $UTdate."
break
esac
fi
done
# Output TZ info and ask the user to confirm.
echo >&2 ""
echo >&2 "The following information has been given:"
echo >&2 "Based on the following information:"
echo >&2 ""
case $country%$region%$coord in
?*%?*%) say >&2 " $country$newline $region";;
?*%%) say >&2 " $country";;
%?*%?*) say >&2 " coord $coord$newline $region";;
%%?*) say >&2 " coord $coord";;
*) say >&2 " TZ='$TZ'"
case $time%$country_result%$region%$coord in
?*%?*%?*%)
say >&2 " $time$newline $country_result$newline $region";;
?*%?*%%|?*%%?*%) say >&2 " $time$newline $country_result$region";;
?*%%%) say >&2 " $time";;
%?*%?*%) say >&2 " $country_result$newline $region";;
%?*%%) say >&2 " $country_result";;
%%?*%?*) say >&2 " coord $coord$newline $region";;
%%%?*) say >&2 " coord $coord";;
*) say >&2 " TZ='$tz'"
esac
say >&2 ""
say >&2 "Therefore TZ='$TZ' will be used.$extra_info"
say >&2 "TZ='$tz' will be used.$extra_info"
say >&2 "Is the above information OK?"
doselect Yes No
@ -543,8 +800,8 @@ do coord=
done
case $SHELL in
*csh) file=.login line="setenv TZ '$TZ'";;
*) file=.profile line="TZ='$TZ'; export TZ"
*csh) file=.login line="setenv TZ '$tz'";;
*) file=.profile line="TZ='$tz'; export TZ"
esac
test -t 1 && say >&2 "
@ -555,4 +812,4 @@ 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:"
say "$TZ"
say "$tz"

View File

@ -1 +1 @@
2020a
2024a

View File

@ -15,7 +15,7 @@
#include <stdio.h>
#ifndef HAVE_SNPRINTF
# define HAVE_SNPRINTF (199901 <= __STDC_VERSION__)
# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__)
#endif
#ifndef HAVE_LOCALTIME_R
@ -42,10 +42,6 @@
# define ZDUMP_HI_YEAR 2500
#endif /* !defined ZDUMP_HI_YEAR */
#ifndef MAX_STRING_LENGTH
#define MAX_STRING_LENGTH 1024
#endif /* !defined MAX_STRING_LENGTH */
#define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
#define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
#define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
@ -88,22 +84,27 @@ static time_t const absolute_max_time =
? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
: -1);
static int longest;
static char * progname;
static char const *progname;
static bool warned;
static bool errout;
static char const *abbr(struct tm const *);
static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE;
ATTRIBUTE_REPRODUCIBLE static intmax_t delta(struct tm *, struct tm *);
static void dumptime(struct tm const *);
static time_t hunt(timezone_t, char *, time_t, time_t);
static time_t hunt(timezone_t, time_t, time_t, bool);
static void show(timezone_t, char *, time_t, bool);
static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
static void showtrans(char const *, struct tm const *, time_t, char const *,
char const *);
static const char *tformat(void);
static time_t yeartot(intmax_t) ATTRIBUTE_PURE;
ATTRIBUTE_REPRODUCIBLE static time_t yeartot(intmax_t);
/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
#define is_digit(c) ((unsigned)(c) - '0' <= 9)
/* Is C an ASCII digit? */
static bool
is_digit(char c)
{
return '0' <= c && c <= '9';
}
/* Is A an alphabetic character in the C locale? */
static bool
@ -124,26 +125,49 @@ is_alpha(char a)
}
}
/* Return A + B, exiting if the result would overflow. */
static size_t
sumsize(size_t a, size_t b)
ATTRIBUTE_NORETURN static void
size_overflow(void)
{
size_t sum = a + b;
if (sum < a) {
fprintf(stderr, "%s: size overflow\n", progname);
fprintf(stderr, _("%s: size overflow\n"), progname);
exit(EXIT_FAILURE);
}
/* Return A + B, exiting if the result would overflow either ptrdiff_t
or size_t. A and B are both nonnegative. */
ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
sumsize(ptrdiff_t a, ptrdiff_t b)
{
#ifdef ckd_add
ptrdiff_t sum;
if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
return sum;
#else
if (a <= INDEX_MAX && b <= INDEX_MAX - a)
return a + b;
#endif
size_overflow();
}
/* Return the size of of the string STR, including its trailing NUL.
Report an error and exit if this would exceed INDEX_MAX which means
pointer subtraction wouldn't work. */
static ptrdiff_t
xstrsize(char const *str)
{
size_t len = strlen(str);
if (len < INDEX_MAX)
return len + 1;
size_overflow();
}
/* Return a pointer to a newly allocated buffer of size SIZE, exiting
on failure. SIZE should be nonzero. */
static void * ATTRIBUTE_MALLOC
xmalloc(size_t size)
on failure. SIZE should be positive. */
ATTRIBUTE_MALLOC static void *
xmalloc(ptrdiff_t size)
{
void *p = malloc(size);
if (!p) {
perror(progname);
fprintf(stderr, _("%s: Memory exhausted\n"), progname);
exit(EXIT_FAILURE);
}
return p;
@ -204,7 +228,7 @@ localtime_r(time_t *tp, struct tm *tmp)
# undef localtime_rz
# define localtime_rz zdump_localtime_rz
static struct tm *
localtime_rz(timezone_t rz, time_t *tp, struct tm *tmp)
localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz, time_t *tp, struct tm *tmp)
{
return localtime_r(tp, tmp);
}
@ -227,33 +251,65 @@ mktime_z(timezone_t tz, struct tm *tmp)
static timezone_t
tzalloc(char const *val)
{
static char **fakeenv;
char **env = fakeenv;
char *env0;
if (! env) {
char **e = environ;
int to;
while (*e++)
continue;
env = xmalloc(sumsize(sizeof *environ,
(e - environ) * sizeof *environ));
to = 1;
for (e = environ; (env[to] = *e); e++)
to += strncmp(*e, "TZ=", 3) != 0;
# if HAVE_SETENV
if (setenv("TZ", val, 1) != 0) {
char const *e = strerror(errno);
fprintf(stderr, _("%s: setenv: %s\n"), progname, e);
exit(EXIT_FAILURE);
}
env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val)));
env[0] = strcat(strcpy(env0, "TZ="), val);
environ = fakeenv = env;
tzset();
return env;
return &optarg; /* Any valid non-null char ** will do. */
# else
enum { TZeqlen = 3 };
static char const TZeq[TZeqlen] = "TZ=";
static char **fakeenv;
static ptrdiff_t fakeenv0size;
void *freeable = NULL;
char **env = fakeenv, **initial_environ;
ptrdiff_t valsize = xstrsize(val);
if (fakeenv0size < valsize) {
char **e = environ, **to;
ptrdiff_t initial_nenvptrs = 1; /* Counting the trailing NULL pointer. */
while (*e++) {
# ifdef ckd_add
if (ckd_add(&initial_nenvptrs, initial_nenvptrs, 1)
|| INDEX_MAX < initial_nenvptrs)
size_overflow();
# else
if (initial_nenvptrs == INDEX_MAX / sizeof *environ)
size_overflow();
initial_nenvptrs++;
# endif
}
fakeenv0size = sumsize(valsize, valsize);
fakeenv0size = max(fakeenv0size, 64);
freeable = env;
fakeenv = env =
xmalloc(sumsize(sumsize(sizeof *environ,
initial_nenvptrs * sizeof *environ),
sumsize(TZeqlen, fakeenv0size)));
to = env + 1;
for (e = environ; (*to = *e); e++)
to += strncmp(*e, TZeq, TZeqlen) != 0;
env[0] = memcpy(to + 1, TZeq, TZeqlen);
}
memcpy(env[0] + TZeqlen, val, valsize);
initial_environ = environ;
environ = env;
tzset();
free(freeable);
return initial_environ;
# endif
}
static void
tzfree(timezone_t env)
tzfree(ATTRIBUTE_MAYBE_UNUSED timezone_t initial_environ)
{
environ = env + 1;
free(env[0]);
# if !HAVE_SETENV
environ = initial_environ;
tzset();
# endif
}
#endif /* ! USE_LOCALTIME_RZ */
@ -263,14 +319,28 @@ static void
gmtzinit(void)
{
if (USE_LOCALTIME_RZ) {
static char const utc[] = "UTC0";
gmtz = tzalloc(utc);
/* Try "GMT" first to find out whether this is one of the rare
platforms where time_t counts leap seconds; this works due to
the "Zone GMT 0 - GMT" line in the "etcetera" file. If "GMT"
fails, fall back on "GMT0" which might be similar due to the
"Link GMT GMT0" line in the "backward" file, and which
should work on all POSIX platforms. The rest of zdump does not
use the "GMT" abbreviation that comes from this setting, so it
is OK to use "GMT" here rather than the modern "UTC" which
would not work on platforms that omit the "backward" file. */
gmtz = tzalloc("GMT");
if (!gmtz) {
perror(utc);
static char const gmt0[] = "GMT0";
gmtz = tzalloc(gmt0);
if (!gmtz) {
char const *e = strerror(errno);
fprintf(stderr, _("%s: unknown timezone '%s': %s\n"),
progname, gmt0, e);
exit(EXIT_FAILURE);
}
}
}
}
/* Convert *TP to UT, storing the broken-down time into *TMP.
Return TMP if successful, NULL otherwise. This is like gmtime_r(TP, TMP),
@ -345,23 +415,23 @@ abbrok(const char *const abbrp, const char *const zone)
/* Return a time zone abbreviation. If the abbreviation needs to be
saved, use *BUF (of size *BUFALLOC) to save it, and return the
abbreviation in the possibly-reallocated *BUF. Otherwise, just
abbreviation in the possibly reallocated *BUF. Otherwise, just
return the abbreviation. Get the abbreviation from TMP.
Exit on memory allocation failure. */
static char const *
saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp)
saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
{
char const *ab = abbr(tmp);
if (HAVE_LOCALTIME_RZ)
return ab;
else {
size_t ablen = strlen(ab);
if (*bufalloc <= ablen) {
ptrdiff_t absize = xstrsize(ab);
if (*bufalloc < absize) {
free(*buf);
/* Make the new buffer at least twice as long as the old,
to avoid O(N**2) behavior on repeated calls. */
*bufalloc = sumsize(*bufalloc, ablen + 1);
*bufalloc = sumsize(*bufalloc, absize);
*buf = xmalloc(*bufalloc);
}
@ -406,7 +476,7 @@ main(int argc, char *argv[])
{
/* These are static so that they're initially zero. */
static char * abbrev;
static size_t abbrevsize;
static ptrdiff_t abbrevsize;
register int i;
register bool vflag;
@ -427,7 +497,7 @@ main(int argc, char *argv[])
# endif /* defined TEXTDOMAINDIR */
textdomain(TZ_DOMAIN);
#endif /* HAVE_GETTEXT */
progname = argv[0];
progname = argv[0] ? argv[0] : "zdump";
for (i = 1; i < argc; ++i)
if (strcmp(argv[i], "--version") == 0) {
printf("zdump %s%s\n", PKGVERSION, TZVERSION);
@ -447,7 +517,7 @@ main(int argc, char *argv[])
case -1:
if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
goto arg_processing_done;
/* Fall through. */
ATTRIBUTE_FALLTHROUGH;
default:
usage(stderr, EXIT_FAILURE);
}
@ -484,8 +554,8 @@ main(int argc, char *argv[])
if (cuttimes != loend && !*loend) {
hi = lo;
if (hi < cuthitime) {
if (hi < absolute_min_time)
hi = absolute_min_time;
if (hi < absolute_min_time + 1)
hi = absolute_min_time + 1;
cuthitime = hi;
}
} else if (cuttimes != loend && *loend == ','
@ -497,8 +567,8 @@ main(int argc, char *argv[])
cutlotime = lo;
}
if (hi < cuthitime) {
if (hi < absolute_min_time)
hi = absolute_min_time;
if (hi < absolute_min_time + 1)
hi = absolute_min_time + 1;
cuthitime = hi;
}
} else {
@ -510,14 +580,17 @@ main(int argc, char *argv[])
}
}
gmtzinit();
INITIALIZE (now);
if (! (iflag | vflag | Vflag))
if (iflag | vflag | Vflag)
now = 0;
else {
now = time(NULL);
now |= !now;
}
longest = 0;
for (i = optind; i < argc; i++) {
size_t arglen = strlen(argv[i]);
if (longest < arglen)
longest = arglen < INT_MAX ? arglen : INT_MAX;
longest = min(arglen, INT_MAX);
}
for (i = optind; i < argc; ++i) {
@ -527,10 +600,12 @@ main(int argc, char *argv[])
struct tm tm, newtm;
bool tm_ok;
if (!tz) {
perror(argv[i]);
char const *e = strerror(errno);
fprintf(stderr, _("%s: unknown timezone '%s': %s\n"),
progname, argv[i], e);
return EXIT_FAILURE;
}
if (! (iflag | vflag | Vflag)) {
if (now) {
show(tz, argv[i], now, false);
tzfree(tz);
continue;
@ -539,12 +614,15 @@ main(int argc, char *argv[])
t = absolute_min_time;
if (! (iflag | Vflag)) {
show(tz, argv[i], t, true);
t += SECSPERDAY;
show(tz, argv[i], t, true);
if (my_localtime_rz(tz, &t, &tm) == NULL
&& t < cutlotime) {
time_t newt = cutlotime;
if (my_localtime_rz(tz, &newt, &newtm) != NULL)
showextrema(tz, argv[i], t, NULL, newt);
}
if (t < cutlotime)
t = cutlotime;
INITIALIZE (ab);
}
if (t + 1 < cutlotime)
t = cutlotime - 1;
tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
if (tm_ok) {
ab = saveabbr(&abbrev, &abbrevsize, &tm);
@ -552,19 +630,20 @@ main(int argc, char *argv[])
showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
}
}
while (t < cuthitime) {
} else
ab = NULL;
while (t < cuthitime - 1) {
time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
&& t + SECSPERDAY / 2 < cuthitime)
&& t + SECSPERDAY / 2 < cuthitime - 1)
? t + SECSPERDAY / 2
: cuthitime);
: cuthitime - 1);
struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
bool newtm_ok = newtmp != NULL;
if (tm_ok != newtm_ok
|| (tm_ok && (delta(&newtm, &tm) != newt - t
|| (ab && (delta(&newtm, &tm) != newt - t
|| newtm.tm_isdst != tm.tm_isdst
|| strcmp(abbr(&newtm), ab) != 0))) {
newt = hunt(tz, argv[i], t, newt);
newt = hunt(tz, t, newt, false);
newtmp = localtime_rz(tz, &newt, &newtm);
newtm_ok = newtmp != NULL;
if (iflag)
@ -583,11 +662,15 @@ main(int argc, char *argv[])
}
}
if (! (iflag | Vflag)) {
t = absolute_max_time;
t -= SECSPERDAY;
show(tz, argv[i], t, true);
t += SECSPERDAY;
show(tz, argv[i], t, true);
time_t newt = absolute_max_time;
t = cuthitime;
if (t < newt) {
struct tm *tmp = my_localtime_rz(tz, &t, &tm);
if (tmp != NULL
&& my_localtime_rz(tz, &newt, &newtm) == NULL)
showextrema(tz, argv[i], t, tmp, newt);
}
show(tz, argv[i], absolute_max_time, true);
}
tzfree(tz);
}
@ -640,36 +723,42 @@ yeartot(intmax_t y)
return t;
}
/* Search for a discontinuity in timezone TZ, in the
timestamps ranging from LOT through HIT. LOT and HIT disagree
about some aspect of timezone. If ONLY_OK, search only for
definedness changes, i.e., localtime succeeds on one side of the
transition but fails on the other side. Return the timestamp just
before the transition from LOT's settings. */
static time_t
hunt(timezone_t tz, char *name, time_t lot, time_t hit)
hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
{
static char * loab;
static size_t loabsize;
char const * ab;
time_t t;
static ptrdiff_t loabsize;
struct tm lotm;
struct tm tm;
/* Convert LOT into a broken-down time here, even though our
caller already did that. On platforms without TM_ZONE,
tzname may have been altered since our caller broke down
LOT, and tzname needs to be changed back. */
bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
bool tm_ok;
char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL;
if (lotm_ok)
ab = saveabbr(&loab, &loabsize, &lotm);
for ( ; ; ) {
time_t diff = hit - lot;
if (diff < 2)
/* T = average of LOT and HIT, rounding down.
Avoid overflow. */
int rem_sum = lot % 2 + hit % 2;
time_t t = (rem_sum == 2) - (rem_sum < 0) + lot / 2 + hit / 2;
if (t == lot)
break;
t = lot;
t += diff / 2;
if (t <= lot)
++t;
else if (t >= hit)
--t;
tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
if (lotm_ok & tm_ok
? (delta(&tm, &lotm) == t - lot
&& tm.tm_isdst == lotm.tm_isdst
&& strcmp(abbr(&tm), ab) == 0)
: lotm_ok == tm_ok) {
if (lotm_ok == tm_ok
&& (only_ok
|| (ab && tm.tm_isdst == lotm.tm_isdst
&& delta(&tm, &lotm) == t - lot
&& strcmp(abbr(&tm), ab) == 0))) {
lot = t;
if (tm_ok)
lotm = tm;
@ -685,11 +774,11 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit)
static intmax_t
delta_nonneg(struct tm *newp, struct tm *oldp)
{
register intmax_t result;
register int tmy;
result = 0;
for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
intmax_t oldy = oldp->tm_year;
int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT;
intmax_t sec = SECSPERREPEAT, result = cycles * sec;
int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
for ( ; tmy < newp->tm_year; ++tmy)
result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
result += newp->tm_yday - oldp->tm_yday;
result *= HOURSPERDAY;
@ -730,7 +819,8 @@ adjusted_yday(struct tm const *a, struct tm const *b)
my_gmtime_r and use its result instead of B. Otherwise, B is the
possibly nonnull result of an earlier call to my_gmtime_r. */
static long
gmtoff(struct tm const *a, time_t *t, struct tm const *b)
gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
{
#ifdef TM_GMTOFF
return a->TM_GMTOFF;
@ -764,6 +854,7 @@ show(timezone_t tz, char *zone, time_t t, bool v)
gmtmp = my_gmtime_r(&t, &gmtm);
if (gmtmp == NULL) {
printf(tformat(), t);
printf(_(" (gmtime failed)"));
} else {
dumptime(gmtmp);
printf(" UT");
@ -771,8 +862,11 @@ show(timezone_t tz, char *zone, time_t t, bool v)
printf(" = ");
}
tmp = my_localtime_rz(tz, &t, &tm);
if (tmp == NULL) {
printf(tformat(), t);
printf(_(" (localtime failed)"));
} else {
dumptime(tmp);
if (tmp != NULL) {
if (*abbr(tmp) != '\0')
printf(" %s", abbr(tmp));
if (v) {
@ -787,13 +881,58 @@ show(timezone_t tz, char *zone, time_t t, bool v)
abbrok(abbr(tmp), zone);
}
/* Show timestamps just before and just after a transition between
defined and undefined (or vice versa) in either localtime or
gmtime. These transitions are for timezone TZ with name ZONE, in
the range from LO (with broken-down time LOTMP if that is nonnull)
through HI. LO and HI disagree on definedness. */
static void
showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
{
struct tm localtm[2], gmtm[2];
time_t t, boundary = hunt(tz, lo, hi, true);
bool old = false;
hi = (SECSPERDAY < hi - boundary
? boundary + SECSPERDAY
: hi + (hi < TIME_T_MAX));
if (SECSPERDAY < boundary - lo) {
lo = boundary - SECSPERDAY;
lotmp = my_localtime_rz(tz, &lo, &localtm[old]);
}
if (lotmp)
localtm[old] = *lotmp;
else
localtm[old].tm_sec = -1;
if (! my_gmtime_r(&lo, &gmtm[old]))
gmtm[old].tm_sec = -1;
/* Search sequentially for definedness transitions. Although this
could be sped up by refining 'hunt' to search for either
localtime or gmtime definedness transitions, it hardly seems
worth the trouble. */
for (t = lo + 1; t < hi; t++) {
bool new = !old;
if (! my_localtime_rz(tz, &t, &localtm[new]))
localtm[new].tm_sec = -1;
if (! my_gmtime_r(&t, &gmtm[new]))
gmtm[new].tm_sec = -1;
if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))
| ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {
show(tz, zone, t - 1, true);
show(tz, zone, t, true);
}
old = new;
}
}
#if HAVE_SNPRINTF
# define my_snprintf snprintf
#else
# include <stdarg.h>
/* A substitute for snprintf that is good enough for zdump. */
static int ATTRIBUTE_FORMAT((printf, 3, 4))
ATTRIBUTE_FORMAT((printf, 3, 4)) static int
my_snprintf(char *s, size_t size, char const *format, ...)
{
int n;
@ -831,7 +970,7 @@ my_snprintf(char *s, size_t size, char const *format, ...)
fit, return the length that the string would have been if it had
fit; do not overrun the output buffer. */
static int
format_local_time(char *buf, size_t size, struct tm const *tm)
format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
{
int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
return (ss
@ -854,7 +993,7 @@ format_local_time(char *buf, size_t size, struct tm const *tm)
the length that the string would have been if it had fit; do not
overrun the output buffer. */
static int
format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
{
long off = gmtoff(tm, &t, NULL);
char sign = ((off < 0
@ -883,11 +1022,11 @@ format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
If the representation's length is less than SIZE, return the
length; the representation is not null terminated. Otherwise
return SIZE, to indicate that BUF is too small. */
static size_t
format_quoted_string(char *buf, size_t size, char const *p)
static ptrdiff_t
format_quoted_string(char *buf, ptrdiff_t size, char const *p)
{
char *b = buf;
size_t s = size;
ptrdiff_t s = size;
if (!s)
return size;
*b++ = '"', s--;
@ -925,11 +1064,11 @@ format_quoted_string(char *buf, size_t size, char const *p)
and omit any trailing tabs. */
static bool
istrftime(char *buf, size_t size, char const *time_fmt,
istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
struct tm const *tm, time_t t, char const *ab, char const *zone_name)
{
char *b = buf;
size_t s = size;
ptrdiff_t s = size;
char const *f = time_fmt, *p;
for (p = f; ; p++)
@ -938,9 +1077,9 @@ istrftime(char *buf, size_t size, char const *time_fmt,
else if (!*p
|| (*p == '%'
&& (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
size_t formatted_len;
size_t f_prefix_len = p - f;
size_t f_prefix_copy_size = p - f + 2;
ptrdiff_t formatted_len;
ptrdiff_t f_prefix_len = p - f;
ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2);
char fbuf[100];
bool oversized = sizeof fbuf <= f_prefix_copy_size;
char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
@ -972,7 +1111,7 @@ istrftime(char *buf, size_t size, char const *time_fmt,
b += offlen, s -= offlen;
if (show_abbr) {
char const *abp;
size_t len;
ptrdiff_t len;
if (s <= 1)
return false;
*b++ = '\t', s--;
@ -1011,7 +1150,7 @@ showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
putchar('\n');
} else {
char stackbuf[1000];
size_t size = sizeof stackbuf;
ptrdiff_t size = sizeof stackbuf;
char *buf = stackbuf;
char *bufalloc = NULL;
while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
@ -1040,12 +1179,29 @@ abbr(struct tm const *tmp)
/*
** The code below can fail on certain theoretical systems;
** it works on all known real-world systems as of 2004-12-30.
** it works on all known real-world systems as of 2022-01-25.
*/
static const char *
tformat(void)
{
#if HAVE__GENERIC
/* C11-style _Generic is more likely to return the correct
format when distinct types have the same size. */
char const *fmt =
_Generic(+ (time_t) 0,
int: "%d", long: "%ld", long long: "%lld",
unsigned: "%u", unsigned long: "%lu",
unsigned long long: "%llu",
default: NULL);
if (fmt)
return fmt;
fmt = _Generic((time_t) 0,
intmax_t: "%"PRIdMAX, uintmax_t: "%"PRIuMAX,
default: NULL);
if (fmt)
return fmt;
#endif
if (0 > (time_t) -1) { /* signed */
if (sizeof(time_t) == sizeof(intmax_t))
return "%"PRIdMAX;
@ -1076,33 +1232,24 @@ dumptime(register const struct tm *timeptr)
"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;
int DIVISOR = 10;
if (timeptr == NULL) {
printf("NULL");
return;
}
/*
** The packaged localtime_rz and gmtime_r 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];
printf("%s %s%3d %.2d:%.2d:%.2d ",
wn, mn,
((0 <= timeptr->tm_wday
&& timeptr->tm_wday < sizeof wday_name / sizeof wday_name[0])
? wday_name[timeptr->tm_wday] : "???"),
((0 <= timeptr->tm_mon
&& timeptr->tm_mon < sizeof mon_name / sizeof mon_name[0])
? mon_name[timeptr->tm_mon] : "???"),
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;

File diff suppressed because it is too large Load Diff