Skip to content

Commit 51dee73

Browse files
committed
perf: cache log timestamp formatters
1 parent b858d41 commit 51dee73

1 file changed

Lines changed: 33 additions & 15 deletions

File tree

src/logging/timestamps.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
const validTimeZoneCache = new Map<string, boolean>();
2+
const timestampFormatterCache = new Map<string, Intl.DateTimeFormat>();
3+
let hostTimeZone: string | undefined;
4+
15
export function isValidTimeZone(tz: string): boolean {
6+
const cached = validTimeZoneCache.get(tz);
7+
if (cached !== undefined) {
8+
return cached;
9+
}
10+
let valid = false;
211
try {
312
new Intl.DateTimeFormat("en", { timeZone: tz }).format();
4-
return true;
13+
valid = true;
514
} catch {
6-
return false;
15+
valid = false;
716
}
17+
validTimeZoneCache.set(tz, valid);
18+
return valid;
819
}
920

1021
type TimestampStyle = "short" | "medium" | "long";
@@ -18,26 +29,33 @@ function resolveEffectiveTimeZone(timeZone?: string): string {
1829
const explicit = timeZone ?? process.env.TZ;
1930
return explicit && isValidTimeZone(explicit)
2031
? explicit
21-
: Intl.DateTimeFormat().resolvedOptions().timeZone;
32+
: (hostTimeZone ??= Intl.DateTimeFormat().resolvedOptions().timeZone);
2233
}
2334

2435
function formatOffset(offsetRaw: string): string {
2536
return offsetRaw === "GMT" ? "+00:00" : offsetRaw.slice(3);
2637
}
2738

2839
function getTimestampParts(date: Date, timeZone?: string) {
29-
const fmt = new Intl.DateTimeFormat("en", {
30-
timeZone: resolveEffectiveTimeZone(timeZone),
31-
year: "numeric",
32-
month: "2-digit",
33-
day: "2-digit",
34-
hour: "2-digit",
35-
minute: "2-digit",
36-
second: "2-digit",
37-
hour12: false,
38-
fractionalSecondDigits: 3 as 1 | 2 | 3,
39-
timeZoneName: "longOffset",
40-
});
40+
const effectiveTimeZone = resolveEffectiveTimeZone(timeZone);
41+
let fmt = timestampFormatterCache.get(effectiveTimeZone);
42+
if (!fmt) {
43+
// Log timestamps are formatted on hot paths; Intl construction is much
44+
// costlier than formatToParts, while timezone rules remain process-stable.
45+
fmt = new Intl.DateTimeFormat("en", {
46+
timeZone: effectiveTimeZone,
47+
year: "numeric",
48+
month: "2-digit",
49+
day: "2-digit",
50+
hour: "2-digit",
51+
minute: "2-digit",
52+
second: "2-digit",
53+
hour12: false,
54+
fractionalSecondDigits: 3 as 1 | 2 | 3,
55+
timeZoneName: "longOffset",
56+
});
57+
timestampFormatterCache.set(effectiveTimeZone, fmt);
58+
}
4159

4260
const parts = Object.fromEntries(fmt.formatToParts(date).map((part) => [part.type, part.value]));
4361
return {

0 commit comments

Comments
 (0)