[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