[PATCH v3 1/1] offtime: optimized calculation of year from unix timestamp

Gary Gende gary@garygende.com
Mon Sep 1 10:01:22 GMT 2025


---
 time/offtime.c | 48 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 34 insertions(+), 14 deletions(-)

diff --git a/time/offtime.c b/time/offtime.c
index c94573e931..1de2fc7530 100644
--- a/time/offtime.c
+++ b/time/offtime.c
@@ -18,9 +18,24 @@
 #include <errno.h>
 #include <time.h>
 
+#ifndef MIN
+# define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
 #define	SECS_PER_HOUR	(60 * 60)
 #define	SECS_PER_DAY	(SECS_PER_HOUR * 24)
 
+/* The number of days in 400 years, 100 years, 4 years, and 1 year,
+   when starting at the beginning of a 400 year leap year cycle.  */
+#define DIVISOR_400     (303 * 365 + 97 * 366)
+#define DIVISOR_100     (76 * 365 + 24 * 366)
+#define DIVISOR_4       (3 * 365 + 366)
+#define DIVISOR_1       365
+
+/* The number of of days that must be added to start at year -2147483999
+   instead of year 1970.  */
+#define DAYS_OFFSET     DIVISOR_400 * 5368714LL + (280 * 365 + 89 * 366)
+
 /* Compute the `struct tm' representation of T,
    offset OFFSET seconds east of UTC,
    and store year, yday, mon, mday, wday, hour, min, sec into *TP.
@@ -52,22 +67,27 @@ __offtime (__time64_t t, long int offset, struct tm *tp)
   tp->tm_wday = (4 + days) % 7;
   if (tp->tm_wday < 0)
     tp->tm_wday += 7;
-  y = 1970;
 
-#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
-#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
+  /* Year calculation must start at the beginning of a 400-yr leap year cycle.
+     -2147483999 is the first such year that exists before the minimum value
+     of tm->tm_year.  */
+  y = -2147483999;
+  days += DAYS_OFFSET;
+
+  y += (days / DIVISOR_400) * 400;
+  days %= DIVISOR_400;
+
+  /* adjust for 4x 100-yr cycles being 1 day shorter than a 400-yr cycle  */
+  y += MIN (3, (days / DIVISOR_100)) * 100;
+  days -= MIN (3, (days / DIVISOR_100)) * DIVISOR_100;
+
+  y += (days / DIVISOR_4) * 4;
+  days %= DIVISOR_4;
+
+  /* adjust for 4x 1-yr cycles being 1 day shorter than a 4-yr cycle  */
+  y += MIN (3, (days / DIVISOR_1));
+  days -= MIN (3, (days / DIVISOR_1)) * DIVISOR_1;
 
-  while (days < 0 || days >= (__isleap (y) ? 366 : 365))
-    {
-      /* Guess a corrected year, assuming 365 days per year.  */
-      __time64_t yg = y + days / 365 - (days % 365 < 0);
-
-      /* Adjust DAYS and Y to match the guessed year.  */
-      days -= ((yg - y) * 365
-	       + LEAPS_THRU_END_OF (yg - 1)
-	       - LEAPS_THRU_END_OF (y - 1));
-      y = yg;
-    }
   tp->tm_year = y - 1900;
   if (tp->tm_year != y - 1900)
     {
-- 
2.43.0



More information about the Libc-alpha mailing list