Skip to content

Instantly share code, notes, and snippets.

@zbraniecki
Last active November 11, 2025 18:40
Show Gist options
  • Select an option

  • Save zbraniecki/4a9c6725b2b6c22aaf05d60e67d3c45f to your computer and use it in GitHub Desktop.

Select an option

Save zbraniecki/4a9c6725b2b6c22aaf05d60e67d3c45f to your computer and use it in GitHub Desktop.
Temporal Benchmarks between V8, SM & Boa
Temporal benchmark starting...
TimeZones: UTC, America/Los_Angeles, Europe/Warsaw, Asia/Shanghai
Calendars: iso8601, gregory, hebrew, chinese, japanese, buddhist, persian
All results are higher-is-better (ops/sec).
Offset lookup via ZDT (proxy for getOffset) | ops/sec mean=1025339 median=1024000 p95=1028016 (n=6)
ZonedDateTime.from Instant+TZ | ops/sec mean=887657 median=885622 p95=891646 (n=6)
ZonedDateTime.add across DST | ops/sec mean=470945 median=471482 p95=474899 (n=6)
PlainDate dateAdd (months/years/leap) | ops/sec mean=1499564 median=1502270 p95=1506575 (n=6)
PlainDateTime since/until (varied units) | ops/sec mean=628153 median=627139 p95=630154 (n=6)
ZonedDateTime.round (minute/hour/day) | ops/sec mean=856836 median=859498 p95=868026 (n=6)
Calendar daysInMonth/daysInYear (via PlainDate) | ops/sec mean=1182064 median=1180839 p95=1191564 (n=6)
Instant parse → toString → parse | ops/sec mean=555016 median=554218 p95=557753 (n=6)
ZDT disambiguation (earlier/later) | ops/sec mean=161503 median=161315 p95=162428 (n=6)
Done.
Temporal benchmark starting...
TimeZones: UTC, America/Los_Angeles, Europe/Warsaw, Asia/Shanghai
Calendars: iso8601, gregory, hebrew, chinese, japanese, buddhist, persian
All results are higher-is-better (ops/sec).
Offset lookup via ZDT (proxy for getOffset) | ops/sec mean=1962124 median=1951565 p95=1985939 (n=6)
ZonedDateTime.from Instant+TZ | ops/sec mean=1906861 median=1904425 p95=1944688 (n=6)
ZonedDateTime.add across DST | ops/sec mean=761141 median=760617 p95=773286 (n=6)
PlainDate dateAdd (months/years/leap) | ops/sec mean=1638710 median=1635960 p95=1649742 (n=6)
PlainDateTime since/until (varied units) | ops/sec mean=621964 median=627305 p95=630760 (n=6)
ZonedDateTime.round (minute/hour/day) | ops/sec mean=2024578 median=1987462 p95=2073019 (n=6)
Calendar daysInMonth/daysInYear (via PlainDate) | ops/sec mean=2337257 median=2360325 p95=2413849 (n=6)
Instant parse → toString → parse | ops/sec mean=922271 median=918877 p95=928272 (n=6)
ZDT disambiguation (earlier/later) | ops/sec mean=542328 median=541847 p95=549108 (n=6)
Done.
Temporal benchmark starting...
TimeZones: UTC, America/Los_Angeles, Europe/Warsaw, Asia/Shanghai
Calendars: iso8601, gregory, hebrew, chinese, japanese, buddhist, persian
All results are higher-is-better (ops/sec).
Offset lookup via ZDT (proxy for getOffset) | ops/sec mean=901034 median=900849 p95=910222 (n=6)
ZonedDateTime.from Instant+TZ | ops/sec mean=4043495 median=4042925 p95=4153690 (n=6)
ZonedDateTime.add across DST | ops/sec mean=2214390 median=2214155 p95=2244384 (n=6)
PlainDate dateAdd (months/years/leap) | ops/sec mean=16978086 median=16944669 p95=16944669 (n=6)
PlainDateTime since/until (varied units) | ops/sec mean=2612837 median=2612760 p95=2621440 (n=6)
ZonedDateTime.round (minute/hour/day) | ops/sec mean=5754449 median=5740380 p95=6199351 (n=6)
Calendar daysInMonth/daysInYear (via PlainDate) | ops/sec mean=103667 median=104000 p95=104000 (n=6)
Instant parse → toString → parse | ops/sec mean=3324521 median=3336422 p95=3348555 (n=6)
ZDT disambiguation (earlier/later) | ops/sec mean=736383 median=736360 p95=736360 (n=6)
Done.
(() => {
// ---- performance.now polyfill ----
if (typeof globalThis.performance === "undefined") {
const perf = {};
if (
typeof process !== "undefined" &&
process.hrtime &&
process.hrtime.bigint
) {
const t0 = process.hrtime.bigint();
perf.now = () => Number(process.hrtime.bigint() - t0) / 1e6;
} else if (typeof Date !== "undefined") {
const d0 = Date.now();
perf.now = () => Date.now() - d0;
} else if (
typeof Temporal !== "undefined" &&
Temporal.Now &&
Temporal.Now.instant
) {
const i0 = Temporal.Now.instant().epochMilliseconds;
perf.now = () => Temporal.Now.instant().epochMilliseconds - i0;
} else {
let t = 0;
perf.now = () => (t += 1);
}
globalThis.performance = perf;
}
if (typeof Temporal === "undefined") {
console.error("Temporal not available.");
return;
}
// blackholes
globalThis.__bh_num ??= 0;
// ---- small stats ----
const stats = (a) => {
const s = Float64Array.from(a).sort();
const n = s.length;
const mean = s.reduce((x, y) => x + y, 0) / (n || 1);
const med = n
? n & 1
? s[(n - 1) >> 1]
: 0.5 * (s[n / 2 - 1] + s[n / 2])
: 0;
const p95 = n ? s[Math.min(n - 1, Math.floor(0.95 * (n - 1)))] : 0;
return { mean, median: med, p95 };
};
// ---- config ----
const RUN_TARGET_MS = 600; // longer to amortize runtime overhead
const REPEATS = 6;
const MAX_SCALE = 1 << 24;
const BATCH = 2048; // heavy per-iteration Temporal work
// ---- helpers ----
let seed = 123456789;
const rand = () => (seed = (1664525 * seed + 1013904223) >>> 0) / 2 ** 32;
const pad2 = (x) => String(x).padStart(2, "0");
const instantFromUTC = (y, m, d, h = 0, mi = 0, s = 0) =>
Temporal.Instant.from(
`${String(y).padStart(4, "0")}-${pad2(m)}-${pad2(d)}T${pad2(h)}:${pad2(mi)}:${pad2(s)}Z`,
);
const toPow2 = (arr) => {
let n = 1;
while (n < arr.length) n <<= 1;
if (n === arr.length) return { data: arr, mask: n - 1 };
const out = arr.slice();
for (let i = arr.length; i < n; i++) out.push(arr[i - arr.length]);
return { data: out, mask: n - 1 };
};
// ---- datasets ----
const tzIdsRaw = [
"UTC",
"America/Los_Angeles",
"Europe/Warsaw",
"Asia/Shanghai",
].filter((id) => {
try {
Temporal.ZonedDateTime.from({
timeZone: id,
calendar: "iso8601",
year: 2020,
month: 1,
day: 1,
hour: 0,
});
return true;
} catch {
return false;
}
});
const { data: tzIds, mask: TZ_MASK } = toPow2(
tzIdsRaw.length ? tzIdsRaw : ["UTC"],
);
const calIdsRaw = [
"gregory",
"hebrew",
"islamic",
"chinese",
"japanese",
"buddhist",
"persian",
]; // non-ISO only to stress rules
const usableCalIds = calIdsRaw.filter((id) => {
try {
Temporal.PlainDate.from({ year: 2020, month: 1, day: 1 }).withCalendar(
id,
);
return true;
} catch {
return false;
}
});
const { data: calendarIds, mask: CAL_MASK } = toPow2(
usableCalIds.length ? usableCalIds : ["gregory"],
);
const instantsRaw = (() => {
const years = Array.from({ length: 21 }, (_, i) => 2010 + i);
const offs = [-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7];
const out = [];
for (const y of years) {
for (const [m, d] of [
[3, 14],
[10, 31],
]) {
for (const k of offs) {
const dt = new Date(Date.UTC(y, m - 1, d));
dt.setUTCDate(dt.getUTCDate() + k);
out.push(
instantFromUTC(
dt.getUTCFullYear(),
dt.getUTCMonth() + 1,
dt.getUTCDate(),
),
);
}
}
}
const base = Temporal.Instant.from("2020-03-08T06:00:00Z");
for (let i = -12; i <= 12; i++) out.push(base.add({ hours: i }));
return out;
})();
const { data: INSTANTS, mask: INS_MASK } = toPow2(instantsRaw);
const DURATIONS = [
Temporal.Duration.from({ hours: 25 }),
Temporal.Duration.from({ minutes: 1439 }),
Temporal.Duration.from({ months: 1 }),
Temporal.Duration.from({ years: 1, days: 3 }),
];
const { data: DURS, mask: DUR_MASK } = toPow2(DURATIONS);
// precompute ZDTs for one zone for rounding
const ROUND_ZONE = tzIds[0];
const ZDT_FOR_ROUND = INSTANTS.map((ins) =>
ins.toZonedDateTimeISO(ROUND_ZONE),
);
const { data: ZDT_ROUND, mask: ZR_MASK } = toPow2(ZDT_FOR_ROUND);
const ROUND_OPTS = [
{ smallestUnit: "minute" },
{ smallestUnit: "hour" },
{ smallestUnit: "day" },
];
const { data: ROUND_OPT_ARR, mask: RO_MASK } = toPow2(ROUND_OPTS);
// random PlainDates for calendar queries
const DATES_RAW = (() => {
const out = [];
for (let i = 0; i < 4096; i++) {
const y = 1200 + Math.floor(rand() * 1400); // wide range stresses non-Gregorian rules
const m = 1 + Math.floor(rand() * 12);
const d = 1 + Math.floor(rand() * 28);
out.push(Temporal.PlainDate.from({ year: y, month: m, day: d }));
}
return out;
})();
const { data: DATES, mask: DATE_MASK } = toPow2(DATES_RAW);
// ---- harness ----
const time = (f, iters) => {
const t0 = performance.now();
f(iters);
return performance.now() - t0;
};
const autoscale = (fn) => {
let it = 1 << 6; // each iteration performs BATCH ops
for (let i = 0; i < 10; i++) {
const ms = time(fn, it);
if (ms > RUN_TARGET_MS / 3 || it >= MAX_SCALE) break;
const scale = Math.max(
2,
Math.min(64, Math.ceil(RUN_TARGET_MS / 3 / Math.max(1, ms))),
);
it *= scale;
}
return it;
};
const run = (name, builder) => {
try {
const fn = builder();
const it = autoscale(fn);
time(fn, it); // warmup
const runs = [];
for (let r = 0; r < REPEATS; r++) {
const ms = time(fn, it);
runs.push((it * BATCH) / (ms / 1000)); // report Temporal ops/sec
}
const s = stats(runs);
console.log(
`${name.padEnd(44)}| ops/sec mean=${s.mean.toFixed(0)} median=${s.median.toFixed(0)} p95=${s.p95.toFixed(0)} (n=${REPEATS})`,
);
} catch (e) {
console.warn(`${name} -> skipped (${e && e.message})`);
}
};
console.log("Temporal benchmark starting...");
console.log(`TimeZones: ${tzIds.join(", ")}`);
console.log(`Calendars (non-ISO): ${calendarIds.join(", ")}`);
console.log("Higher is better. Designed to minimize JS overhead.");
// 1) Offset lookup via ZDT
run("Offset lookup via ZDT ", () => {
const zones = tzIds,
ins = INSTANTS;
const zMask = TZ_MASK,
iMask = INS_MASK;
let i = 0,
j = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const zdt = ins[i++ & iMask].toZonedDateTimeISO(zones[j++ & zMask]);
sink ^= zdt.offsetNanoseconds;
}
}
__bh_num ^= sink;
};
});
// 2) ZDT from Instant + TZ
run("ZDT from Instant + TZ ", () => {
const zones = tzIds,
ins = INSTANTS;
const zMask = TZ_MASK,
iMask = INS_MASK;
let i = 0,
j = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const zdt = ins[(i += 3) & iMask].toZonedDateTimeISO(
zones[j++ & zMask],
);
sink ^= zdt.epochMilliseconds & 0xffff; // Number path, avoid BigInt overhead
}
}
__bh_num ^= sink;
};
});
// 3) ZDT add across DST
run("ZDT.add across DST ", () => {
const zone = tzIds.includes("America/Los_Angeles")
? "America/Los_Angeles"
: tzIds[0];
const ins = INSTANTS,
durs = DURS;
const iMask = INS_MASK,
dMask = DUR_MASK;
let i = 0,
d = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const zdt = ins[i++ & iMask].toZonedDateTimeISO(zone);
const res = zdt.add(durs[d++ & dMask], { overflow: "constrain" });
sink ^= res.epochMilliseconds & 0xffff;
}
}
__bh_num ^= sink;
};
});
// 4) PlainDate add/sub (months/years/leap)
run("PlainDate add (months/years/leap) ", () => {
const dates = [];
for (let y = 1600; y <= 2400; y++)
dates.push(
Temporal.PlainDate.from({
year: y,
month: (y % 12) + 1,
day: (y % 28) + 1,
}),
);
const { data: DSET, mask: DMSK } = toPow2(dates);
const durs = [
Temporal.Duration.from({ months: 1 }),
Temporal.Duration.from({ years: 1 }),
Temporal.Duration.from({ years: 4, days: 1 }),
];
const { data: D2, mask: DM2 } = toPow2(durs);
let i = 0,
d = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const r = DSET[i++ & DMSK].add(D2[d++ & DM2]);
sink ^= r.year;
}
}
__bh_num ^= sink;
};
});
// 5) PlainDateTime since/until
run("PlainDateTime since/until (varied units) ", () => {
const base = Temporal.PlainDateTime.from("2016-02-28T23:59:59.999999999");
const ends = [
base.add({ days: 365 }),
base.add({ months: 13 }),
base.add({ hours: 49, minutes: 5 }),
];
const opts = [
{ largestUnit: "years" },
{ largestUnit: "months" },
{ largestUnit: "days" },
{ largestUnit: "hours", smallestUnit: "minutes" },
];
const { data: ENDS, mask: E_MASK } = toPow2(ends);
const { data: OPTS, mask: O_MASK } = toPow2(opts);
let i = 0,
j = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const a = base.add({ days: i++ & 7 });
const dur = a.until(ENDS[j++ & E_MASK], OPTS[(j + 3) & O_MASK]);
sink ^= dur.days ?? 0;
}
}
__bh_num ^= sink;
};
});
// 6) ZDT.round
run("ZDT.round (minute/hour/day) ", () => {
const base = ZDT_ROUND;
const opts = ROUND_OPT_ARR;
const bMask = ZR_MASK,
oMask = RO_MASK;
let i = 0,
j = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const r = base[i++ & bMask].round(opts[j++ & oMask]);
sink ^= r.epochMilliseconds & 0xffff;
}
}
__bh_num ^= sink;
};
});
// 7) Calendar queries via PlainDate getters (heavier set)
run("Calendar queries (daysInMonth/year/leap/dow)", () => {
const dates = DATES,
cals = calendarIds;
const dMask = DATE_MASK,
cMask = CAL_MASK;
let i = 0,
j = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const d = dates[i++ & dMask].withCalendar(cals[j++ & cMask]);
// multiple getters per op to amplify calendar math
sink ^=
d.daysInMonth ^ d.daysInYear ^ (d.inLeapYear ? 1 : 0) ^ d.dayOfWeek;
}
}
__bh_num ^= sink;
};
});
// 8) Instant arithmetic churn (no strings)
run("Instant add/sub churn ", () => {
const ins = INSTANTS;
const iMask = INS_MASK;
const durA = Temporal.Duration.from({
minutes: 37,
seconds: 59,
milliseconds: 7,
});
const durB = Temporal.Duration.from({ hours: 13, minutes: 5 });
let i = 0,
sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
let x = ins[i++ & iMask];
x = x.add(durA).subtract(durB).add(durA);
sink ^= x.epochMilliseconds & 0xffff;
}
}
__bh_num ^= sink;
};
});
// 9) Disambiguation earlier/later
run("ZDT disambiguation (earlier/later) ", () => {
const zone = tzIds.includes("America/Los_Angeles")
? "America/Los_Angeles"
: tzIds[0];
const local = Temporal.PlainDateTime.from("2020-11-01T01:30:00");
let sink = 0;
return (iters) => {
for (let k = 0; k < iters; k++) {
let b = BATCH;
while (b--) {
const earlier = Temporal.ZonedDateTime.from({
timeZone: zone,
calendar: "iso8601",
year: local.year,
month: local.month,
day: local.day,
hour: local.hour,
minute: local.minute,
second: local.second,
millisecond: local.millisecond,
microsecond: local.microsecond,
nanosecond: local.nanosecond,
offset: undefined,
disambiguation: "earlier",
});
const later = Temporal.ZonedDateTime.from({
timeZone: zone,
calendar: "iso8601",
year: local.year,
month: local.month,
day: local.day,
hour: local.hour,
minute: local.minute,
second: local.second,
millisecond: local.millisecond,
microsecond: local.microsecond,
nanosecond: local.nanosecond,
offset: undefined,
disambiguation: "later",
});
sink ^=
(later.epochMilliseconds - earlier.epochMilliseconds) & 0xffff;
}
}
__bh_num ^= sink;
};
});
console.log("Done.");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment