Hello,

The attached patch avoids walking through every intervening year when no
Rule can apply.  On current main, this two-line input demonstrates the
problem:

    Rule R 1000000000 only - Jan 1 0 0 -
    Zone Test 0 R T

With `zic -b slim`, the untouched build used 2.23 user-seconds on this
machine.  The patched build completed below the timer's 0.01-second
resolution.  Both produced the same 116-byte TZif file, with SHA-256:

    108f87d03de1731b0ee8759d12806d080b1fd896f5539b67bf17f67c84147f39

The input deliberately uses a no-op Rule, so the output stays constant while
the current cost scales linearly with the numeric year.  The patch tracks the
next year in which any Rule can apply and jumps directly to it.  Active
recurring Rule ranges still advance one year at a time.

I verified byte-for-byte output across all 598 generated zones and focused
cases for recurring max rules, separated ranges, same-year transitions, and
Zone continuations.  The full Clang ASan/UBSan `make check` passes, as does a
GCC 15 warnings-as-errors build.

This is separate from the recent getleapdatetime change: that patch made one
Leap/Expires year constant-time, whereas this change removes empty annual
iteration between Rule applicability boundaries in outzone.

Thanks,
Darren Carreras
From 8da21768e539fe77f487fd263bca7eaf169af963 Mon Sep 17 00:00:00 2001
From: Darren Carreras <[email protected]>
Date: Fri, 7 Aug 2026 10:31:00 -0400
Subject: [PATCH] zic: skip years in which no Rule applies

A sparse Rule range currently makes outzone iterate through every
intervening year even though the loop has no work to do.

* zic.c (outzone): Track the earliest later applicability boundary while
scanning the rules and advance directly to it.  Recurring active ranges
still advance one year at a time.
---
 zic.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/zic.c b/zic.c
index 2e3a344..1d9c0b2 100644
--- a/zic.c
+++ b/zic.c
@@ -3545,7 +3545,9 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				defaulttype = type;
 		} else {
 		  zic_t year;
-		  for (year = min_year; year <= max_year; ++year) {
+		  for (year = min_year; ; ) {
+			bool next_year_found = false;
+			zic_t next_year = 0;
 			if (useuntil && year > zp->z_untilrule.r_hiyear)
 				break;
 			/*
@@ -3559,6 +3561,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				struct rule *rp = &zp->z_rules[j];
 				eats(zp->z_filenum, zp->z_linenum,
 				     rp->r_filenum, rp->r_linenum);
+				/* Find the next year in which any rule can apply.  */
+				if (year < rp->r_hiyear) {
+					zic_t next = max(year + 1, rp->r_loyear);
+					if (next <= max_year
+					    && (!next_year_found || next < next_year)) {
+						next_year_found = true;
+						next_year = next;
+					}
+				}
 				rp->r_todo = year >= rp->r_loyear &&
 						year <= rp->r_hiyear;
 				if (rp->r_todo) {
@@ -3674,6 +3685,9 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				  nonTZlimtype = type;
 				}
 			}
+			if (!next_year_found)
+				break;
+			year = next_year;
 		  }
 		}
 		if (usestart) {
-- 
2.50.1 (Apple Git-155)

Reply via email to