Hello,

The attached patch makes getleapdatetime reuse rpytime's constant-time
400-year decomposition instead of walking one year at a time from
1970.

A leap-seconds file containing:

    Leap 2147483647 Jun 30 23:59:60 + S

made current main spend about 1.67 seconds in the year loop on this
machine; larger syntactically valid years scale linearly and can keep
zic busy for an impractically long time before checked timestamp
arithmetic rejects them. With the patch, the same calculation
completes immediately through the existing checked 400-year path.

Verification:
- make CURL=: check passed under Clang UBSan and stack hardening
- the normal leap-second timezone output tree matched unpatched main
byte for byte
- structured Leap and Expires boundary cases, including INT64_MIN and
INT64_MAX, terminated promptly without sanitizer faults

Thanks,
Darren
From 1bfe13723342bbe1a19461ecc48b034cad7c1168 Mon Sep 17 00:00:00 2001
From: carrerasdarren-cell
 <[email protected]>
Date: Sat, 1 Aug 2026 11:09:33 -0400
Subject: [PATCH] zic: avoid linear work for leap-second years

Leap and Expires lines with outlandish years can make zic loop once
per year between 1970 and the input year.  Reuse rpytime's
constant-time 400-year decomposition and checked arithmetic instead.

* NEWS: Mention this.
* zic.c (getleapdatetime): Compute the timestamp via rpytime.
---
 NEWS  |  3 +++
 zic.c | 33 ++++++++++-----------------------
 2 files changed, 13 insertions(+), 23 deletions(-)

diff --git a/NEWS b/NEWS
index 4948ca7..c322003 100644
--- a/NEWS
+++ b/NEWS
@@ -46,6 +46,9 @@ Unreleased, experimental changes
 
   Changes to code
 
+    zic now quickly rejects outlandish leap-second years instead of
+    iterating once per year.
+
     zic now rejects Link targets that would have invalid names.
     (Thanks to Darren Carreras.)
 
diff --git a/zic.c b/zic.c
index 5ed884e..1985fe0 100644
--- a/zic.c
+++ b/zic.c
@@ -2322,14 +2322,12 @@ getleapdatetime(char **fields, bool expire_line)
 {
 	register const char *		cp;
 	register const struct lookup *	lp;
-	register zic_t			i, j;
 	zic_t				year;
 	int				month, day;
-	zic_t				dayoff, tod;
+	zic_t				tod;
 	zic_t				t;
 	char xs;
 
-	dayoff = 0;
 	cp = fields[LP_YEAR];
 	if (sscanf(cp, "%"SCNdZIC"%c", &year, &xs) != 1) {
 		/*
@@ -2345,38 +2343,27 @@ getleapdatetime(char **fields, bool expire_line)
 		leapminyear = year;
 	    leapseen = true;
 	}
-	j = EPOCH_YEAR;
-	while (j != year) {
-		if (year > j) {
-			i = len_years[isleap(j)];
-			++j;
-		} else {
-			--j;
-			i = -len_years[isleap(j)];
-		}
-		dayoff = oadd(dayoff, i);
-	}
 	if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) {
 		error(N_("invalid month name"));
 		return -1;
 	}
 	month = lp->l_value;
-	j = TM_JANUARY;
-	while (j != month) {
-		i = len_months[isleap(year)][j];
-		dayoff = oadd(dayoff, i);
-		++j;
-	}
 	cp = fields[LP_DAY];
 	if (sscanf(cp, "%d%c", &day, &xs) != 1 ||
 		day <= 0 || day > len_months[isleap(year)][month]) {
 			error(N_("invalid day of month"));
 			return -1;
 	}
-	dayoff = oadd(dayoff, day - 1);
-	t = omul(dayoff, SECSPERDAY);
 	tod = gethms(fields[LP_TIME], N_("invalid time of day"));
-	t = tadd(t, tod);
+	{
+	  struct rule r = {
+	    .r_month = month,
+	    .r_dycode = DC_DOM,
+	    .r_dayofmonth = day,
+	    .r_tod = tod
+	  };
+	  t = rpytime(&r, year);
+	}
 	if (t < 0)
 	  error(N_("leap second precedes Epoch"));
 	return t;
-- 
2.50.1 (Apple Git-155)

Reply via email to