Skip to content

Commit f9de611

Browse files
authored
Optimize util for calculation year, month, day in Date (#2397)
1 parent 24a908d commit f9de611

File tree

3 files changed

+311
-358
lines changed

3 files changed

+311
-358
lines changed

Diff for: std/assembly/date.ts

+28-22
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { Date as Date_binding } from "./bindings/dom";
66
MILLIS_PER_DAY = 1000 * 60 * 60 * 24,
77
MILLIS_PER_HOUR = 1000 * 60 * 60,
88
MILLIS_PER_MINUTE = 1000 * 60,
9-
MILLIS_PER_SECOND = 1000;
9+
MILLIS_PER_SECOND = 1000,
10+
11+
YEARS_PER_EPOCH = 400,
12+
DAYS_PER_EPOCH = 146097,
13+
EPOCH_OFFSET = 719468, // Jan 1, 1970
14+
MILLIS_LIMIT = 8640000000000000;
1015

1116
// ymdFromEpochDays returns values via globals to avoid allocations
1217
// @ts-ignore: decorator
@@ -94,7 +99,7 @@ export class Date {
9499
// instead throwing exception.
95100
if (invalidDate(epochMillis)) throw new RangeError(E_INVALIDDATE);
96101

97-
this.year = ymdFromEpochDays(i32(floorDiv(epochMillis, MILLIS_PER_DAY)));
102+
this.year = dateFromEpoch(epochMillis);
98103
this.month = _month;
99104
this.day = _day;
100105
}
@@ -107,7 +112,7 @@ export class Date {
107112
if (invalidDate(time)) throw new RangeError(E_INVALIDDATE);
108113

109114
this.epochMillis = time;
110-
this.year = ymdFromEpochDays(i32(floorDiv(time, MILLIS_PER_DAY)));
115+
this.year = dateFromEpoch(time);
111116
this.month = _month;
112117
this.day = _day;
113118

@@ -306,7 +311,7 @@ function epochMillis(
306311

307312
// @ts-ignore: decorator
308313
@inline function floorDiv<T extends number>(a: T, b: T): T {
309-
return (a >= 0 ? a : a - b + 1) / b as T;
314+
return (a - (a < 0 ? b - 1 : 0)) / b as T;
310315
}
311316

312317
// @ts-ignore: decorator
@@ -317,41 +322,42 @@ function epochMillis(
317322

318323
function invalidDate(millis: i64): bool {
319324
// @ts-ignore
320-
return (millis < -8640000000000000) | (millis > 8640000000000000);
325+
return (millis < -MILLIS_LIMIT) | (millis > MILLIS_LIMIT);
321326
}
322327

323-
// see: http://howardhinnant.github.io/date_algorithms.html#civil_from_days
324-
function ymdFromEpochDays(z: i32): i32 {
325-
z += 719468;
326-
var era = <u32>floorDiv(z, 146097);
327-
var doe = <u32>z - era * 146097; // [0, 146096]
328-
var yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
329-
var year = yoe + era * 400;
330-
var doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
331-
var mo = (5 * doy + 2) / 153; // [0, 11]
332-
_day = doy - (153 * mo + 2) / 5 + 1; // [1, 31]
333-
mo += mo < 10 ? 3 : -9; // [1, 12]
334-
_month = mo;
335-
year += u32(mo <= 2);
328+
// Based on "Euclidean Affine Functions and Applications to Calendar Algorithms"
329+
// Paper: https://arxiv.org/pdf/2102.06959.pdf
330+
function dateFromEpoch(ms: i64): i32 {
331+
var da = (<i32>floorDiv(ms, MILLIS_PER_DAY) * 4 + EPOCH_OFFSET * 4) | 3;
332+
var q0 = floorDiv(da, DAYS_PER_EPOCH); // [0, 146096]
333+
var r1 = <u32>da - q0 * DAYS_PER_EPOCH;
334+
var u1 = u64(r1 | 3) * 2939745;
335+
var dm1 = <u32>u1 / 11758980;
336+
var n1 = 2141 * dm1 + 197913;
337+
var year = 100 * q0 + i32(u1 >>> 32);
338+
var mo = n1 >>> 16;
339+
_day = (n1 & 0xFFFF) / 2141 + 1; // [1, 31]
340+
if (dm1 >= 306) { mo -= 12; ++year; }
341+
_month = mo; // [1, 12]
336342
return year;
337343
}
338344

339345
// http://howardhinnant.github.io/date_algorithms.html#days_from_civil
340346
function daysSinceEpoch(y: i32, m: i32, d: i32): i32 {
341347
y -= i32(m <= 2);
342-
var era = <u32>floorDiv(y, 400);
343-
var yoe = <u32>y - era * 400; // [0, 399]
348+
var era = <u32>floorDiv(y, YEARS_PER_EPOCH);
349+
var yoe = <u32>y - era * YEARS_PER_EPOCH; // [0, 399]
344350
var doy = <u32>(153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365]
345351
var doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
346-
return era * 146097 + doe - 719468;
352+
return era * 146097 + doe - EPOCH_OFFSET;
347353
}
348354

349355
// TomohikoSakamoto algorithm from https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
350356
function dayOfWeek(year: i32, month: i32, day: i32): i32 {
351357
const tab = memory.data<u8>([0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]);
352358

353359
year -= i32(month < 3);
354-
year += floorDiv(year, 4) - floorDiv(year, 100) + floorDiv(year, 400);
360+
year += floorDiv(year, 4) - floorDiv(year, 100) + floorDiv(year, YEARS_PER_EPOCH);
355361
month = <i32>load<u8>(tab + month - 1);
356362
return euclidRem(year + month + day, 7);
357363
}

0 commit comments

Comments
 (0)