On 2025-12-16 00:10, renchunhui2025--- via tz wrote:

After further investigation, we found that zdump is actually able to 
comprehensively detect invalid tzfiles, including cases that may later trigger 
aborts at runtime.

Thanks for looking into this. However, I doubt whether any zdump implementation does a comprehensive test, in the sense of checking for strict conformance to every requirement specified by Internet RFC 9636. More likely, each zdump implementation checks only for problems of concern to it.


when zdump loads certain malformed tzfiles, it can hit assertions in tzfile.c 
and abort with exit code 134

tzfile.c is not part of tzcode so I assume you're talking about the GNU C Library's tzfile.c, which is a TZif file reader.

I could not reproduce the problem that you mentioned: when I built TZDB zdump like this:

  make clean
  make CFLAGS='-DUSE_LTZ=0' TZDOBJS=zdump.o zdump

on Fedora 42 x86-64, which has glibc-2.41-11.fc42.x86_64, zdump did not hit any assertions with the test case you provided, namely:

Rule NOS 1970 2037 - Jan 27 23:43:01 2:0 SDST
Rule NOS 1970 2037 - Jan 28 0:51:01 0 S
Zone OS/NOS +7:0:0 NOS NO%s

Perhaps you are using a different version of glibc, or perhaps you are not running on x86-64, or perhaps you have an old version of zdump.c; any of these could explain why you see a problem but I do not.

That being said, I looked at the TZif file generated by that test case, and it's clearly incorrect. I installed into the development repository the attached patch to fix the problem that I found; please give it a try. Perhaps this fix will be enough to address the problem you observed.
From 10f93018f44f1f5a10319657a90e8b9645a6ba79 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Tue, 16 Dec 2025 20:09:29 -0800
Subject: [PROPOSED] Omit no-op transitions when Rule+Zone cancel
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fix zic so that it does not generate a no-op transition for
TZDB 2025c’s Asia/Tbilisi at POSIX time 859662000, i.e.,
at 1997-03-29 19:00 UTC or 1997-03-30 00:00 local time.
The no-op transition was generated because there is a Zone line
"4:00 1:00 %z 1997 Mar lastSun" followed by "4:00 E-EurAsia %z ..."
and the relevant Rule as of 1997 Mar lastSun is
"Rule E-EurAsia 1981 max - Mar lastSun 0:00 1:00 -".
Because the first Zone line’s UNTIL and the Rule’s time are the same,
zic coalesced the two into a no-op transition where the
timestamps before and after are both +05 with DST.
This change also fixes a bug with the following test:
  Rule NOS 1970 2037 - Jan 27 23:43:01 2:0 SDST
  Rule NOS 1970 2037 - Jan 28 0:51:01 0 S
  Zone OS/NOS +7:0:0 NOS NO%s
In this unrealistic test, the fall-back occurs during the
spring-forward gap so there should be no TZif transitions.
Without the fix zic incorrectly outputs a 2-hour spring-forward
transition from 1970-01-27 22:51:01 +07 to 1970-01-28 00:51:01 +09,
and then outputs 66 no-op transitions every year until 2037-01-28
00:51:01 +09, and then the non-matching TZ string "NOS-7".
This test case was reported by Renchunhui in:
https://lists.iana.org/hyperkitty/list/[email protected]/message/TEUFHPYKORVAXWQV5FTNAOAQOXEMQDWZ/
* NEWS: Mention this.
* zic.c (writezone): When removing an unwanted transition
due to a Rule line applying at the same time as a Zone’s UNTIL time,
do not turn the preceding transition into a no-op.
---
 NEWS  | 10 ++++++++++
 zic.c | 16 ++++++++++------
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/NEWS b/NEWS
index 70116304..2ae02d59 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,15 @@
 News for the tz database
 
+Unreleased, experimental changes
+
+  Changes to code
+
+    zic no longer generates a no-op transition when
+    simultaneous Rule and Zone changes cancel each other out.
+    This occurs in tzdata only in Asia/Tbilisi on 1997-03-30.
+    (Thanks to Renchunhui for a test case showing the bug.)
+
+
 Release 2025c - 2025-12-10 14:42:37 -0800
 
   Briefly:
diff --git a/zic.c b/zic.c
index 81a1c5a8..163eefa8 100644
--- a/zic.c
+++ b/zic.c
@@ -2674,15 +2674,19 @@ writezone(const char *const name, const char *const string, char version,
 		toi = 0;
 		fromi = 0;
 		for ( ; fromi < timecnt; ++fromi) {
-			if (toi != 0
-			    && ((attypes[fromi].at
+			if (toi != 0) {
+			    unsigned char type_2 =
+			      toi == 1 ? 0 : attypes[toi - 2].type;
+			    if ((attypes[fromi].at
 				 + utoffs[attypes[toi - 1].type])
-				<= (attypes[toi - 1].at
-				    + utoffs[toi == 1 ? 0
-					     : attypes[toi - 2].type]))) {
+				<= attypes[toi - 1].at + utoffs[type_2]) {
+				    if (attypes[fromi].type == type_2)
+					toi--;
+				    else
 					attypes[toi - 1].type =
 						attypes[fromi].type;
-					continue;
+				    continue;
+			    }
 			}
 			if (toi == 0
 			    || attypes[fromi].dontmerge
-- 
2.51.0

Reply via email to