[libc-commits] [libc] [libc] Implement fast date conversion algorithm of Ben Joffe (PR #208312)

Michael Jones via libc-commits libc-commits at lists.llvm.org
Thu Jul 9 10:53:16 PDT 2026


================
@@ -128,89 +99,112 @@ ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm) {
   if (total_seconds < time_min || total_seconds > time_max)
     return cpp::unexpected(TIME_OVERFLOW);
 
-  int64_t seconds =
-      total_seconds - time_constants::SECONDS_UNTIL2000_MARCH_FIRST;
-  int64_t days = seconds / time_constants::SECONDS_PER_DAY;
-  int64_t remainingSeconds = seconds % time_constants::SECONDS_PER_DAY;
-  if (remainingSeconds < 0) {
-    remainingSeconds += time_constants::SECONDS_PER_DAY;
+  // Step 1: Convert seconds to days + remaining seconds
+  // Handle negative timestamps correctly (before Unix epoch)
+  int64_t days = total_seconds / time_constants::SECONDS_PER_DAY;
+  int64_t remaining_seconds = total_seconds % time_constants::SECONDS_PER_DAY;
+  if (remaining_seconds < 0) {
+    remaining_seconds += time_constants::SECONDS_PER_DAY;
     days--;
   }
 
-  int64_t wday = (time_constants::WEEK_DAY_OF2000_MARCH_FIRST + days) %
-                 time_constants::DAYS_PER_WEEK;
-  if (wday < 0)
-    wday += time_constants::DAYS_PER_WEEK;
-
-  // Compute the number of 400 year cycles.
-  int64_t numOfFourHundredYearCycles = days / time_constants::DAYS_PER400_YEARS;
-  int64_t remainingDays = days % time_constants::DAYS_PER400_YEARS;
-  if (remainingDays < 0) {
-    remainingDays += time_constants::DAYS_PER400_YEARS;
-    numOfFourHundredYearCycles--;
-  }
-
-  // The remaining number of years after computing the number of
-  // "four hundred year cycles" will be 4 hundred year cycles or less in 400
-  // years.
-  int64_t numOfHundredYearCycles = computeRemainingYears(
-      time_constants::DAYS_PER100_YEARS, 4, &remainingDays);
-
-  // The remaining number of years after computing the number of
-  // "hundred year cycles" will be 25 four year cycles or less in 100 years.
-  int64_t numOfFourYearCycles = computeRemainingYears(
-      time_constants::DAYS_PER4_YEARS, 25, &remainingDays);
-
-  // The remaining number of years after computing the number of
-  // "four year cycles" will be 4 one year cycles or less in 4 years.
-  int64_t remainingYears = computeRemainingYears(
-      time_constants::DAYS_PER_NON_LEAP_YEAR, 4, &remainingDays);
-
-  // Calculate number of years from year 2000.
-  int64_t years = remainingYears + 4 * numOfFourYearCycles +
-                  100 * numOfHundredYearCycles +
-                  400LL * numOfFourHundredYearCycles;
-
-  int leapDay =
-      !remainingYears && (numOfFourYearCycles || !numOfHundredYearCycles);
-
-  // We add 31 and 28 for the number of days in January and February, since our
-  // starting point was March 1st.
-  int64_t yday = remainingDays + 31 + 28 + leapDay;
-  if (yday >= time_constants::DAYS_PER_NON_LEAP_YEAR + leapDay)
-    yday -= time_constants::DAYS_PER_NON_LEAP_YEAR + leapDay;
-
-  int64_t months = 0;
-  while (daysInMonth[months] <= remainingDays) {
-    remainingDays -= daysInMonth[months];
-    months++;
-  }
-
-  if (months >= time_constants::MONTHS_PER_YEAR - 2) {
-    months -= time_constants::MONTHS_PER_YEAR;
-    years++;
-  }
-
-  if (years > INT_MAX || years < INT_MIN)
+  // Save Unix epoch days for wday calculation later
+  const int64_t unix_days = days;
+
+  // See pseudocode lines 1-29 at https://www.benjoffe.com/fast-date-64
+  //
+  // Key idea: count years backwards from a far-future epoch so that both
+  // the 4-year and 400-year cycles start with a leap/long period. This
+  // eliminates the "+3" offset terms from traditional algorithms and
+  // enables pure multiply-shift division (4 multiplications, 0 hardware
+  // divisions).
+
+  // ERAS: number of 400-year eras to shift into the future; chosen to
+  // maximize the symmetric range around the Unix epoch in 64-bit.
+  constexpr int64_t ERAS = 4726498270LL;
+  // D_SHIFT: reversed day count from the epoch alignment point 0000-02-29.
+  // 146097 = days per 400-year era; 719469 = days from 0000-02-29 to
+  // 1970-01-01 (one day earlier than the 719468 used by forward algorithms).
+  constexpr int64_t D_SHIFT = 146097LL * ERAS - 719469LL;
+  // Y_SHIFT: converts reversed year count back to a forward year.
+  constexpr int64_t Y_SHIFT = 400LL * ERAS - 1;
+  // C1-C3: fixed-point reciprocals for multiply-shift division.
+  // The >>64 bit-shift is "free" on 64-bit CPUs (just reads the high
+  // register of the 128-bit multiplication result).
+  constexpr uint64_t C1 = 505054698555331ULL;   // floor(2^64 * 4 / 146097)
+  constexpr uint64_t C2 = 50504432782230121ULL; // ceil(2^64 * 4 / 1461)
+  constexpr uint64_t C3 = 8619973866219416ULL;  // floor(2^64 / 2140)
+
+  // Pseudocode lines 9-11: Adjust for 100/400 leap year rule (Julian Map).
+  int64_t rev = D_SHIFT - unix_days;
+  uint64_t cen = static_cast<uint64_t>((static_cast<UInt128>(rev) * C1) >> 64);
----------------
michaelrj-google wrote:

`cen` is only ever used as `int64_t`. Would it make more sense to just define it as that instead of `uint64_t`?

https://github.com/llvm/llvm-project/pull/208312


More information about the libc-commits mailing list