From 10f93018f44f1f5a10319657a90e8b9645a6ba79 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
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/tz@iana.org/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

