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
