diff mbox series

[v3] timezone: sync to TZDB 2024b

Message ID 20240905123315.1720957-1-adhemerval.zanella@linaro.org
State Accepted
Commit 1b171c942ea066c08a1e5aefb977e35da08ed291
Headers show
Series [v3] timezone: sync to TZDB 2024b | expand

Commit Message

Adhemerval Zanella Sept. 5, 2024, 12:33 p.m. UTC
Sync tzselect, zdump, zic to TZDB 2024b. This patch incorporates the
following TZDB source code changes:

6903dde3 Release 2024b
812aff32 Improve historical transitions in Mexico 1921-1997
52662566 Adjust to mailing list software change
7748036b Mention Internet RFC 9557
339e81d1 Mention Levine’s proposal to replace leap seconds
b4e6ad2d No leap second on 2024-12-31
7eb5bf88 Asia/Choibalsan is now an alias for Asia/Ulaanbaatar
43450cbf Improve historical data for Portugal and former possessions.
13d7348b Typo and validation fixes.
3c39cde8 Fix typo for “removed” in a comment
03fd9e45 More documentation updates for POSIX.1-2024
eb3bcceb POSIX.1-2014 is now published
913b0410 tzselect: support POSIX.1-2024 offset range
b5318b55 Document POSIX.1-2024 better
837609b7 Fix typo when making .txt man pages
d56ae6ee SUPPORT_C89 now defaults to 1, not 0
b1fe113d Port ! to Solaris make
8f1fd321 Avoid crash in Solaris 10 /usr/xpg4/bin/make
e0fcfdd6 Use ‘export VAR=VAL’ syntax
eba43166 Avoid an awk invocation via $'...'
36479a80 Avoid some subshells in tzselect
7f6cf054 * tzselect.ksh: Assume POSIX.2 awk.
a1cf1daf * tzselect.ksh: Assume POSIX.2 $PWD.
a9b8e536 Assume POSIX.2 command substitution
eaa4ef16 Avoid subshells when possible
9dac9eb7 Prefer $PWD to $(pwd) in Makefile
fada6a4c Prefer $(CMD) to `CMD` in Makefile
3e871b9a Assume POSIX.2 and eschew ‘expr’
c5d67805 difftime isn’t pure either
5857c056 * CONTRIBUTING: Document build assumptions.
6822cc82 ‘make check’ no longer depends on curl+Internet
cc6eb255 Document GCC bug 114833 and workaround
bcbc86bf Scale back on function attribute use
c0789e46 C23 [[reproducible]] and [[unsequenced]] fixups
bbd88154 More updates to GCC_DEBUG_FLAGS for GCC 14
1a35b7c8 Spelling fixes
f71085f2 POSIX.1-2024 removes asctime_r, ctime_r
70856f8e Adjust to refactored location of ctime, ctime_r
aacd151d Update GCC_DEBUG_FLAGS for GCC 14
967dcf3b Sub-second history for Maputo and Zurich
782d0826 Make EET, MET and WET links
a0b09c02 Mark CET, CST6CDT etc. as obsolescent
db7fb40d Document SMPTE timecodes and rolling leaps
97232e18 Don’t be so sure about leap seconds going away
5b6a74fb Update some URLs
a75a6251 * zic.8: Tweak for consistency.
1e75b31f Document what %s means before any rule applies
00c96cbb Conform to RFC 8536 section 3.2 for default type
3e944959 Document problems with stripped-down TZif readers
20fc91cf Shanks is likely wrong about Maputo switch to CAT
d99589b6 * zic.8: Add missing tab character.
94e6b3b0 Switch to %z in main dataform
2cd57b93 Treat W-Eur like Port when reguarding
ad6f6d94 Check that main.zi agrees with sources
a43b030f .gitignore: Add .pdf, .ps, .s. Remove obsolete ‘yearistype’.
253ca020 * theory.html: ‘CLT’ → ‘LTC’ (per Michael H Deckers)
a3dee8c8 * NEWS: ‘how’ → ‘now’ (thanks to Paul Goyette).
ea6341c5 * theory.html: Mention NASA and CLT (per Arthur David Olson).
0dcebe37 America/Scoresbysund matches America/Nuuk from now on
b1e07fb0 Update Vzic link (thanks to Allen Winter)
a4b05030 Fix wday/mday typo in previous patch
732a4803 Document how to detect mktime failure reliably
a64067e9 ziguard.awk: generalize for proposed Portugal patch
59c861fd Line up zdump examples
66c106c9 tzfile.5: srcfix
e5553001 Fix .RS/.RE problem in tzfile.5
d647eb01 Add Doctorow book
59d4a1ba Asia/Almaty matches Asia/Tashkent from now on
d4d3c3ba * asia: Update Philippine URLs (thanks to Guy Harris).
9fc11a27 Port unlikely overflow check to C23
b52a2969 Fix 2023d NEWS typo
e48c5b53 Cite "The NTP Leap Second File"
b1dc2122 Update Israel tz-link
6cf4e912 Extrapolate less from the 2022 CGPM resolution.

It fixes glibc build with gcc master [1].

Checked on x86_64-linux-gnu and on i686-linux-gnu.

[1] https://sourceware.org/pipermail/libc-alpha/2024-September/159571.html
---
 SHARED-FILES          |   5 +-
 timezone/private.h    | 114 +++++++++++++++++++++++---------------
 timezone/tzfile.h     |   8 ++-
 timezone/tzselect.ksh | 125 +++++++++++++++++++-----------------------
 timezone/zdump.c      |  10 ++--
 timezone/zic.c        |  31 +++++------
 6 files changed, 151 insertions(+), 142 deletions(-)

Comments

Paul Eggert Sept. 5, 2024, 3:11 p.m. UTC | #1
On 2024-09-05 05:33, Adhemerval Zanella wrote:
> Sync tzselect, zdump, zic to TZDB 2024b. This patch incorporates the
> following TZDB source code changes:

It's strange: that still doesn't sync to 2024b. I'm attaching the patch 
that I expected; can you please investigate the discrepancies? Thanks.
Adhemerval Zanella Sept. 5, 2024, 3:31 p.m. UTC | #2
On 05/09/24 12:11, Paul Eggert wrote:
> On 2024-09-05 05:33, Adhemerval Zanella wrote:
>> Sync tzselect, zdump, zic to TZDB 2024b. This patch incorporates the
>> following TZDB source code changes:
> 
> It's strange: that still doesn't sync to 2024b. I'm attaching the patch that I expected; can you please investigate the discrepancies? Thanks.

Taking the project reference repository is https://github.com/eggert/tz.git,
so:

  tz $ git checkout -b 2024b 2024b

  glibc $ diff -u timezone/private.h /path/to/tz/private.h ; echo $?
  0

For instance, on zdump I see a lot of ATTRIBUTE_PURE_114833 being used
where with your patch it just use ATTRIBUTE_PURE (for instance, on
sumsize).

What am I missing here? Am I using the wrong tag?
Paul Eggert Sept. 5, 2024, 3:46 p.m. UTC | #3
On 2024-09-05 08:31, Adhemerval Zanella Netto wrote:
> What am I missing here? Am I using the wrong tag?

Ouch, no, the mistake was mine. I was comparing v3 to what I thought was 
TZDB 2024b, but was actually v2.

So v3 looks good; please install. Sorry about the confusion.

Reviewed-by: Paul Eggert <eggert@cs.ucla.edu>
diff mbox series

Patch

diff --git a/SHARED-FILES b/SHARED-FILES
index cb0356ce32..d70c7adecf 100644
--- a/SHARED-FILES
+++ b/SHARED-FILES
@@ -180,9 +180,8 @@  unicode:
 # The following files are shared with the upstream tzcode project and must be
 # updated regularly to stay in sync with the upstream releases.
 #
-# 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
+# Currently synced to TZDB 2024b, announced and distributed here:
+#	https://github.com/eggert/tz/releases/tag/2024b
 tzcode:
   timezone/private.h
   timezone/tzfile.h
diff --git a/timezone/private.h b/timezone/private.h
index 0dac6af4e3..c33041049f 100644
--- a/timezone/private.h
+++ b/timezone/private.h
@@ -19,19 +19,22 @@ 
 
 /* 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
+   and perhaps a few other extensions to C89.
+
+   This macro is obsolescent, and the plan is to remove it along with
+   associated code.  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
+
+/* SUPPORT_C89 means the tzcode library should support C89 callers
+   in addition to the usual support for C99-and-later callers.
+   This defaults to 1 as POSIX requires, even though that can trigger
+   latent bugs in callers.  */
 #ifndef SUPPORT_C89
-# define SUPPORT_C89 0
+# define SUPPORT_C89 1
 #endif
 
 #ifndef __STDC_VERSION__
@@ -69,10 +72,6 @@ 
 ** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'.
 */
 
-#ifndef HAVE_DECL_ASCTIME_R
-# define HAVE_DECL_ASCTIME_R 1
-#endif
-
 #if !defined HAVE__GENERIC && defined __has_extension
 # if !__has_extension(c_generic_selections)
 #  define HAVE__GENERIC 0
@@ -236,6 +235,31 @@ 
 # include <unistd.h> /* for R_OK, and other POSIX goodness */
 #endif /* HAVE_UNISTD_H */
 
+/* SUPPORT_POSIX2008 means the tzcode library should support
+   POSIX.1-2017-and-earlier callers in addition to the usual support for
+   POSIX.1-2024-and-later callers; however, this can be
+   incompatible with POSIX.1-2024-and-later callers.
+   This macro is obsolescent, and the plan is to remove it
+   along with any code needed only when it is nonzero.
+   A good time to do that might be in the year 2034.
+   This macro's name is SUPPORT_POSIX2008 because _POSIX_VERSION == 200809
+   in POSIX.1-2017, a minor revision of POSIX.1-2008.  */
+#ifndef SUPPORT_POSIX2008
+# if defined _POSIX_VERSION && _POSIX_VERSION <= 200809
+#  define SUPPORT_POSIX2008 1
+# else
+#  define SUPPORT_POSIX2008 0
+# endif
+#endif
+
+#ifndef HAVE_DECL_ASCTIME_R
+# if SUPPORT_POSIX2008
+#  define HAVE_DECL_ASCTIME_R 1
+# else
+#  define HAVE_DECL_ASCTIME_R 0
+# endif
+#endif
+
 #ifndef HAVE_STRFTIME_L
 # if _POSIX_VERSION < 200809
 #  define HAVE_STRFTIME_L 0
@@ -460,14 +484,6 @@  typedef unsigned long uintmax_t;
 # define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
 #endif
 
-#if 3 <= __GNUC__
-# define ATTRIBUTE_MALLOC __attribute__((malloc))
-# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec))
-#else
-# define ATTRIBUTE_MALLOC /* empty */
-# define ATTRIBUTE_FORMAT(spec) /* empty */
-#endif
-
 #if (defined __has_c_attribute \
      && (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__))
 # define HAVE___HAS_C_ATTRIBUTE true
@@ -535,24 +551,27 @@  typedef unsigned long uintmax_t;
 # endif
 #endif
 #ifndef ATTRIBUTE_REPRODUCIBLE
-# if 3 <= __GNUC__
-#  define ATTRIBUTE_REPRODUCIBLE __attribute__((pure))
-# else
-#  define ATTRIBUTE_REPRODUCIBLE /* empty */
-# endif
+# define ATTRIBUTE_REPRODUCIBLE /* empty */
 #endif
 
-#if HAVE___HAS_C_ATTRIBUTE
-# if __has_c_attribute(unsequenced)
-#  define ATTRIBUTE_UNSEQUENCED [[unsequenced]]
-# endif
+/* GCC attributes that are useful in tzcode.
+   __attribute__((pure)) is stricter than [[reproducible]],
+   so the latter is an adequate substitute in non-GCC C23 platforms.  */
+#if __GNUC__ < 3
+# define ATTRIBUTE_FORMAT(spec) /* empty */
+# define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE
+#else
+# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec))
+# define ATTRIBUTE_PURE __attribute__((pure))
 #endif
-#ifndef ATTRIBUTE_UNSEQUENCED
-# if 3 <= __GNUC__
-#  define ATTRIBUTE_UNSEQUENCED __attribute__((const))
-# else
-#  define ATTRIBUTE_UNSEQUENCED /* empty */
-# endif
+
+/* Avoid GCC bug 114833 <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114833>.
+   Remove this macro and its uses when the bug is fixed in a GCC release,
+   because only the latest GCC matters for $(GCC_DEBUG_FLAGS).  */
+#ifdef GCC_LINT
+# define ATTRIBUTE_PURE_114833 ATTRIBUTE_PURE
+#else
+# define ATTRIBUTE_PURE_114833 /* empty */
 #endif
 
 #if (__STDC_VERSION__ < 199901 && !defined restrict \
@@ -604,12 +623,8 @@  typedef time_tz tz_time_t;
 
 # undef  asctime
 # define asctime tz_asctime
-# undef  asctime_r
-# define asctime_r tz_asctime_r
 # undef  ctime
 # define ctime tz_ctime
-# undef  ctime_r
-# define ctime_r tz_ctime_r
 # undef  difftime
 # define difftime tz_difftime
 # undef  gmtime
@@ -654,6 +669,12 @@  typedef time_tz tz_time_t;
 # define tzfree tz_tzfree
 # undef  tzset
 # define tzset tz_tzset
+# if SUPPORT_POSIX2008
+#  undef  asctime_r
+#  define asctime_r tz_asctime_r
+#  undef  ctime_r
+#  define ctime_r tz_ctime_r
+# endif
 # if HAVE_STRFTIME_L
 #  undef  strftime_l
 #  define strftime_l tz_strftime_l
@@ -679,10 +700,12 @@  typedef time_tz tz_time_t;
 #  define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED
 # endif
 DEPRECATED_IN_C23 char *asctime(struct tm const *);
-char *asctime_r(struct tm const *restrict, char *restrict);
 DEPRECATED_IN_C23 char *ctime(time_t const *);
+#if SUPPORT_POSIX2008
+char *asctime_r(struct tm const *restrict, char *restrict);
 char *ctime_r(time_t const *, char *);
-ATTRIBUTE_UNSEQUENCED double difftime(time_t, time_t);
+#endif
+double difftime(time_t, time_t);
 size_t strftime(char *restrict, size_t, char const *restrict,
 		struct tm const *restrict);
 # if HAVE_STRFTIME_L
@@ -713,7 +736,7 @@  void tzset(void);
 time_t timegm(struct tm *);
 #endif
 
-#if !HAVE_DECL_ASCTIME_R && !defined asctime_r
+#if !HAVE_DECL_ASCTIME_R && !defined asctime_r && SUPPORT_POSIX2008
 extern char *asctime_r(struct tm const *restrict, char *restrict);
 #endif
 
@@ -798,10 +821,10 @@  timezone_t tzalloc(char const *);
 void tzfree(timezone_t);
 # if STD_INSPIRED
 #  if TZ_TIME_T || !defined posix2time_z
-ATTRIBUTE_REPRODUCIBLE time_t posix2time_z(timezone_t, time_t);
+ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t);
 #  endif
 #  if TZ_TIME_T || !defined time2posix_z
-ATTRIBUTE_REPRODUCIBLE time_t time2posix_z(timezone_t, time_t);
+ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t);
 #  endif
 # endif
 #endif
@@ -973,8 +996,9 @@  enum {
 
 /* 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.
+   E.g., suppose a rule applies from 2012 on with transitions
+   in March and September, plus one-off transitions in November 2013,
+   and suppose the rule cannot be expressed as a proleptic TZ string.
    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,
diff --git a/timezone/tzfile.h b/timezone/tzfile.h
index 3155010ed1..b154146654 100644
--- a/timezone/tzfile.h
+++ b/timezone/tzfile.h
@@ -76,14 +76,16 @@  struct tzhead {
 ** If tzh_version is '2' or greater, the above is followed by a second instance
 ** of tzhead and a second instance of the data in which each coded transition
 ** time uses 8 rather than 4 chars,
-** then a POSIX-TZ-environment-variable-style string for use in handling
+** then a POSIX.1-2017 proleptic TZ 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.1-2017
 ** representation for such instants).
 **
-** If tz_version is '3' or greater, the above is extended as follows.
+** If tz_version is '3' or greater, the TZ string can be any POSIX.1-2024
+** proleptic TZ string, which means the above is extended as follows.
 ** First, the TZ string's hour offset may range from -167
-** through 167 as compared to the POSIX-required 0 through 24.
+** through 167 as compared to the range 0 through 24 required
+** by POSIX.1-2017 and earlier.
 ** 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.
diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh
index 38941bbc55..ca3d82c6aa 100755
--- a/timezone/tzselect.ksh
+++ b/timezone/tzselect.ksh
@@ -20,12 +20,6 @@  REPORT_BUGS_TO=tz@iana.org
 #	Korn Shell <http://www.kornshell.com/>
 #	MirBSD Korn Shell <http://www.mirbsd.org/mksh.htm>
 #
-# 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 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:
@@ -45,7 +39,6 @@  set -f
 
 # Specify default values for environment variables if they are unset.
 : ${AWK=awk}
-: ${PWD=`pwd`}
 : ${TZDIR=$PWD}
 
 # Output one argument as-is to standard output, with trailing newline.
@@ -54,13 +47,6 @@  say() {
   printf '%s\n' "$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."
-  exit 1
-}
-
 coord=
 location_limit=10
 zonetabtype=zone1970
@@ -117,8 +103,7 @@  then
 else
   doselect() {
     # Field width of the prompt numbers.
-    print_nargs_length="BEGIN {print length(\"$#\");}"
-    select_width=`$AWK "$print_nargs_length"`
+    select_width=${##}
 
     select_i=
 
@@ -129,14 +114,14 @@  else
 	select_i=0
 	for select_word
 	do
-	  select_i=`$AWK "BEGIN { print $select_i + 1 }"`
+	  select_i=$(($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 `$AWK "BEGIN { print $select_i - 1 }"`
+	  shift $(($select_i - 1))
 	  select_result=$1
 	  break
 	fi
@@ -170,7 +155,7 @@  do
   esac
 done
 
-shift `$AWK "BEGIN { print $OPTIND - 1 }"`
+shift $(($OPTIND - 1))
 case $# in
 0) ;;
 *) say >&2 "$0: $1: unknown argument"; exit 1
@@ -178,11 +163,13 @@  esac
 
 # 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
+# which means the shell and (presumably) awk do not need transliteration.
+# It is true if the byte string has some other length in characters, or
+# if this is a POSIX.1-2017 or earlier shell that does not support $'...'.
+CUNEIFORM_SIGN_URU_TIMES_KI=$'\360\222\215\205'
+if test ${#CUNEIFORM_SIGN_URU_TIMES_KI} = 1
+then translit=false
+else translit=true
 fi
 
 # Read into shell variable $1 the contents of file $2.
@@ -192,10 +179,10 @@  fi
 # 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=\$( (iconv -f UTF-8 -t //TRANSLIT) 2>/dev/null <\"\$2\")" ||
+    eval "$1=\$( (iconv -f UTF-8) 2>/dev/null <\"\$2\")"
   }; } ||
-  eval "$1=\`cat <\"\$2\"\`" || {
+  eval "$1=\$(cat <\"\$2\")" || {
     say >&2 "$0: time zone files are not set up correctly"
     exit 1
   }
@@ -403,7 +390,7 @@  while
     echo >&2 \
       'Please select a continent, ocean, "coord", "TZ", "time", or "now".'
 
-    quoted_continents=`
+    quoted_continents=$(
       $AWK '
 	function handle_entry(entry) {
 	  entry = substr(entry, 1, index(entry, "/") - 1)
@@ -433,12 +420,12 @@  while
       sort -u |
       tr '\n' ' '
       echo ''
-    `
+    )
 
     eval '
       doselect '"$quoted_continents"' \
 	"coord - I want to use geographical coordinates." \
-	"TZ - I want to specify the timezone using a POSIX.1-2017 TZ string." \
+	"TZ - I want to specify the timezone using a proleptic TZ string." \
 	"time - I know local time already." \
 	"now - Like \"time\", but configure only for timestamps from now on."
       continent=$select_result
@@ -462,16 +449,17 @@  while
 
   case $continent in
   TZ)
-    # Ask the user for a POSIX.1-2017 TZ string.  Check that it conforms.
+    # Ask the user for a proleptic 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
+	sign = "[-+]?"
+	hhmm = "(:[0-5][0-9](:[0-5][0-9])?)?"
+	offset = sign "(2[0-4]|[0-1]?[0-9])" hhmm
+	time = sign "(16[0-7]|(1[0-5]|[0-9]?)[0-9])" hhmm
 	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])")
@@ -492,7 +480,7 @@  while
       read tz
       $AWK "$check_POSIX_TZ_string" ="$tz"
     do
-      say >&2 "'$tz' is not a conforming POSIX.1-2017 timezone string."
+      say >&2 "'$tz' is not a conforming POSIX proleptic TZ string."
     done
     TZ_for_date=$tz;;
   *)
@@ -507,14 +495,14 @@  while
 	  '74 degrees 3 minutes west.'
 	read coord
       esac
-      distance_table=`
+      distance_table=$(
 	$AWK \
 	  "$output_distances_or_times" \
 	  ="$coord" ="$TZ_COUNTRY_TABLE" ="$TZ_ZONE_TABLE" |
 	sort -n |
 	$AWK "{print} NR == $location_limit { exit }"
-      `
-      regions=`
+      )
+      regions=$(
 	$AWK '
 	  BEGIN {
 	    distance_table = substr(ARGV[1], 2)
@@ -526,13 +514,13 @@  while
 	    }
 	  }
 	' ="$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=`
+      tz=$(
 	$AWK '
 	  BEGIN {
 	    distance_table = substr(ARGV[1], 2)
@@ -546,22 +534,22 @@  while
 	    }
 	  }
 	' ="$distance_table" ="$region"
-      `;;
+      );;
     *)
       case $continent in
       now|time)
 	minute_format='%a %b %d %H:%M'
-	old_minute=`TZ=UTC0 date +"$minute_format"`
+	old_minute=$(TZ=UTC0 date +"$minute_format")
 	for i in 1 2 3
 	do
-	  time_table_command=`
+	  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"`
+	  )
+	  time_table=$(eval "$time_table_command")
+	  new_minute=$(TZ=UTC0 date +"$minute_format")
 	  case $old_minute in
 	  "$new_minute") break
 	  esac
@@ -569,11 +557,11 @@  while
 	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` || {
+	sorted_table=$(say "$time_table" | sort -k2n -k2,5 -k1n) || {
 	  say >&2 "$0: cannot sort time table"
 	  exit 1
 	}
-	eval doselect `
+	eval doselect $(
 	  $AWK '
 	    BEGIN {
 	      sorted_table = substr(ARGV[1], 2)
@@ -590,10 +578,10 @@  while
 	      }
 	    }
 	  ' ="$sorted_table"
-	`
+	)
 	time=$select_result
 	continent_re='^'
-	zone_table=`
+	zone_table=$(
 	  $AWK '
 	    BEGIN {
 	      time = substr(ARGV[1], 2)
@@ -609,13 +597,13 @@  while
 	      }
 	    }
 	  ' ="$time" ="$time_table"
-	`
-	countries=`
+	)
+	countries=$(
 	  $AWK \
 	    "$output_country_list" \
 	    ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
 	  sort -f
-	`
+	)
 	;;
       *)
 	continent_re="^$continent/"
@@ -623,16 +611,16 @@  while
       esac
 
       # Get list of names of countries in the continent or ocean.
-      countries=`
+      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=`
+      regions=$(
 	$AWK '
 	  BEGIN {
 	    TZ_ZONE_TABLE = substr(ARGV[1], 2)
@@ -653,7 +641,7 @@  while
 		print comment[i]
 	  }
 	' ="$zone_table"
-      `
+      )
 
       # If there's more than one country, ask the user which one.
       case $countries in
@@ -669,7 +657,7 @@  while
 
 
       # Get list of timezones in the country.
-      regions=`
+      regions=$(
 	$AWK '
 	  BEGIN {
 	    country = substr(ARGV[1], 2)
@@ -696,7 +684,7 @@  while
 	    }
 	  }
 	' ="$country" ="$TZ_COUNTRY_TABLE" ="$zone_table"
-      `
+      )
 
       # If there's more than one region, ask the user which one.
       case $regions in
@@ -707,7 +695,7 @@  while
       esac
 
       # Determine tz from country and region.
-      tz=`
+      tz=$(
 	$AWK '
 	  BEGIN {
 	    country = substr(ARGV[1], 2)
@@ -735,7 +723,7 @@  while
 	    }
 	  }
 	' ="$country" ="$region" ="$TZ_COUNTRY_TABLE" ="$zone_table"
-      `
+      )
     esac
 
     # Make sure the corresponding zoneinfo file exists.
@@ -754,14 +742,11 @@  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`
-    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"
+    TZdate=$(LANG=C TZ="$TZ_for_date" date)
+    UTdate=$(LANG=C TZ=UTC0 date)
+    TZsecsetc=${TZdate##*[0-5][0-9]:}
+    UTsecsetc=${UTdate##*[0-5][0-9]:}
+    if test "${TZsecsetc%%[!0-9]*}" = "${UTsecsetc%%[!0-9]*}"
     then
       extra_info="
 Selected time is now:	$TZdate.
@@ -801,7 +786,7 @@  done
 
 case $SHELL in
 *csh) file=.login line="setenv TZ '$tz'";;
-*)    file=.profile line="TZ='$tz'; export TZ"
+*)    file=.profile line="export TZ='$tz'"
 esac
 
 test -t 1 && say >&2 "
diff --git a/timezone/zdump.c b/timezone/zdump.c
index 7d99cc74bd..e817873337 100644
--- a/timezone/zdump.c
+++ b/timezone/zdump.c
@@ -89,7 +89,7 @@  static bool	warned;
 static bool	errout;
 
 static char const *abbr(struct tm const *);
-ATTRIBUTE_REPRODUCIBLE static intmax_t delta(struct tm *, struct tm *);
+static intmax_t delta(struct tm *, struct tm *);
 static void dumptime(struct tm const *);
 static time_t hunt(timezone_t, time_t, time_t, bool);
 static void show(timezone_t, char *, time_t, bool);
@@ -97,7 +97,7 @@  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);
-ATTRIBUTE_REPRODUCIBLE static time_t yeartot(intmax_t);
+ATTRIBUTE_PURE_114833 static time_t yeartot(intmax_t);
 
 /* Is C an ASCII digit?  */
 static bool
@@ -134,7 +134,7 @@  size_overflow(void)
 
 /* 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
+ATTRIBUTE_PURE_114833 static ptrdiff_t
 sumsize(ptrdiff_t a, ptrdiff_t b)
 {
 #ifdef ckd_add
@@ -162,7 +162,7 @@  xstrsize(char const *str)
 
 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
    on failure.  SIZE should be positive.  */
-ATTRIBUTE_MALLOC static void *
+static void *
 xmalloc(ptrdiff_t size)
 {
   void *p = malloc(size);
@@ -932,7 +932,7 @@  showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
 # include <stdarg.h>
 
 /* A substitute for snprintf that is good enough for zdump.  */
-ATTRIBUTE_FORMAT((printf, 3, 4)) static int
+static int
 my_snprintf(char *s, size_t size, char const *format, ...)
 {
   int n;
diff --git a/timezone/zic.c b/timezone/zic.c
index 00f00e307a..cf8e79dff4 100644
--- a/timezone/zic.c
+++ b/timezone/zic.c
@@ -470,7 +470,7 @@  size_overflow(void)
   memory_exhausted(_("size overflow"));
 }
 
-ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+ATTRIBUTE_PURE_114833 static ptrdiff_t
 size_sum(size_t a, size_t b)
 {
 #ifdef ckd_add
@@ -484,7 +484,7 @@  size_sum(size_t a, size_t b)
   size_overflow();
 }
 
-ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+ATTRIBUTE_PURE_114833 static ptrdiff_t
 size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
 {
 #ifdef ckd_mul
@@ -499,7 +499,7 @@  size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
   size_overflow();
 }
 
-ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+ATTRIBUTE_PURE_114833 static ptrdiff_t
 align_to(ptrdiff_t size, ptrdiff_t alignment)
 {
   ptrdiff_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits);
@@ -523,7 +523,7 @@  memcheck(void *ptr)
 	return ptr;
 }
 
-ATTRIBUTE_MALLOC static void *
+static void *
 emalloc(size_t size)
 {
   return memcheck(malloc(size));
@@ -535,7 +535,7 @@  erealloc(void *ptr, size_t size)
   return memcheck(realloc(ptr, size));
 }
 
-ATTRIBUTE_MALLOC static char *
+static char *
 estrdup(char const *str)
 {
   return memcheck(strdup(str));
@@ -1435,7 +1435,7 @@  relname(char const *target, char const *linkname)
 /* Return true if A and B must have the same parent dir if A and B exist.
    Return false if this is not necessarily true (though it might be true).
    Keep it simple, and do not inspect the file system.  */
-static bool
+ATTRIBUTE_PURE_114833 static bool
 same_parent_dirs(char const *a, char const *b)
 {
   for (; *a == *b; a++, b++)
@@ -2982,10 +2982,10 @@  rule_cmp(struct rule const *a, struct rule const *b)
 	return a->r_dayofmonth - b->r_dayofmonth;
 }
 
-/* Store into RESULT a POSIX.1-2017 TZ string that represent the future
+/* Store into RESULT a proleptic TZ string that represent the future
    predictions for the zone ZPFIRST with ZONECOUNT entries.  Return a
    compatibility indicator (a TZDB release year) if successful, a
-   negative integer if no such TZ string exissts.  */
+   negative integer if no such TZ string exists.  */
 static int
 stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 {
@@ -3177,8 +3177,7 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	if (noise) {
 		if (!*envvar)
 			warning("%s %s",
-				_("no POSIX.1-2017 environment variable"
-				  " for zone"),
+				_("no proleptic TZ string for zone"),
 				zpfirst->z_name);
 		else if (compat != 0) {
 			/* Circa-COMPAT clients, and earlier clients, might
@@ -3442,7 +3441,7 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	if (do_extend) {
 		/*
 		** If we're extending the explicitly listed observations for
-		** 400 years because we can't fill the POSIX.1-2017 TZ field,
+		** 400 years because we can't fill the proleptic 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
@@ -3627,7 +3626,7 @@  lowerit(char a)
 }
 
 /* case-insensitive equality */
-ATTRIBUTE_REPRODUCIBLE static bool
+ATTRIBUTE_PURE_114833 static bool
 ciequal(register const char *ap, register const char *bp)
 {
 	while (lowerit(*ap) == lowerit(*bp++))
@@ -3636,7 +3635,7 @@  ciequal(register const char *ap, register const char *bp)
 	return false;
 }
 
-ATTRIBUTE_REPRODUCIBLE static bool
+ATTRIBUTE_PURE_114833 static bool
 itsabbr(register const char *abbr, register const char *word)
 {
 	if (lowerit(*abbr) != lowerit(*word))
@@ -3652,7 +3651,7 @@  itsabbr(register const char *abbr, register const char *word)
 
 /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case.  */
 
-ATTRIBUTE_REPRODUCIBLE static bool
+ATTRIBUTE_PURE_114833 static bool
 ciprefix(char const *abbr, char const *word)
 {
   do
@@ -3762,7 +3761,7 @@  time_overflow(void)
   exit(EXIT_FAILURE);
 }
 
-ATTRIBUTE_REPRODUCIBLE static zic_t
+ATTRIBUTE_PURE_114833 static zic_t
 oadd(zic_t t1, zic_t t2)
 {
 #ifdef ckd_add
@@ -3776,7 +3775,7 @@  oadd(zic_t t1, zic_t t2)
   time_overflow();
 }
 
-ATTRIBUTE_REPRODUCIBLE static zic_t
+ATTRIBUTE_PURE_114833 static zic_t
 tadd(zic_t t1, zic_t t2)
 {
 #ifdef ckd_add