Skip to content

Commit b7a95c2

Browse files
committed
Improve IANAZone.offset performance
1 parent 35a50c2 commit b7a95c2

File tree

1 file changed

+93
-34
lines changed

1 file changed

+93
-34
lines changed

src/zones/IANAZone.js

Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
import { formatOffset, parseZoneInfo, isUndefined, objToLocalTS } from "../impl/util.js";
22
import Zone from "../zone.js";
33

4-
let dtfCache = {};
5-
function makeDTF(zone) {
6-
if (!dtfCache[zone]) {
7-
dtfCache[zone] = new Intl.DateTimeFormat("en-US", {
4+
let directOffsetDTFCache = {};
5+
function makeDirectOffsetDTF(zone) {
6+
if (!directOffsetDTFCache[zone]) {
7+
directOffsetDTFCache[zone] = new Intl.DateTimeFormat("en-US", {
8+
timeZone: zone,
9+
timeZoneName: "longOffset",
10+
year: "numeric",
11+
});
12+
}
13+
return directOffsetDTFCache[zone];
14+
}
15+
16+
let calculatedOffsetDTFCache = {};
17+
function makeCalculatedOffsetDTF(zone) {
18+
if (!calculatedOffsetDTFCache[zone]) {
19+
calculatedOffsetDTFCache[zone] = new Intl.DateTimeFormat("en-US", {
820
hour12: false,
921
timeZone: zone,
1022
year: "numeric",
@@ -16,7 +28,7 @@ function makeDTF(zone) {
1628
era: "short",
1729
});
1830
}
19-
return dtfCache[zone];
31+
return calculatedOffsetDTFCache[zone];
2032
}
2133

2234
const typeToPos = {
@@ -52,7 +64,65 @@ function partsOffset(dtf, date) {
5264
return filled;
5365
}
5466

67+
function calculatedOffset(zone, ts) {
68+
const date = new Date(ts);
69+
70+
if (isNaN(date)) return NaN;
71+
72+
const dtf = makeCalculatedOffsetDTF(zone);
73+
let [year, month, day, adOrBc, hour, minute, second] = dtf.formatToParts
74+
? partsOffset(dtf, date)
75+
: hackyOffset(dtf, date);
76+
77+
if (adOrBc === "BC") {
78+
year = -Math.abs(year) + 1;
79+
}
80+
81+
// because we're using hour12 and https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat
82+
const adjustedHour = hour === 24 ? 0 : hour;
83+
84+
const asUTC = objToLocalTS({
85+
year,
86+
month,
87+
day,
88+
hour: adjustedHour,
89+
minute,
90+
second,
91+
millisecond: 0,
92+
});
93+
94+
let asTS = +date;
95+
const over = asTS % 1000;
96+
asTS -= over >= 0 ? over : 1000 + over;
97+
return (asUTC - asTS) / (60 * 1000);
98+
}
99+
100+
function directOffset(zone, ts) {
101+
const dtf = makeDirectOffsetDTF(zone);
102+
103+
let formatted;
104+
105+
try {
106+
formatted = dtf.format(ts);
107+
} catch (e) {
108+
return NaN;
109+
}
110+
111+
const idx = formatted.search(/GMT([+-][0-9][0-9]:[0-9][0-9](:[0-9][0-9])?)?/);
112+
const sign = formatted.charCodeAt(idx + 3);
113+
114+
if (isNaN(sign)) return 0;
115+
116+
return (
117+
(44 - sign) *
118+
(Number(formatted.slice(idx + 4, idx + 6)) * 60 +
119+
Number(formatted.slice(idx + 7, idx + 9)) +
120+
Number(formatted.slice(idx + 10, idx + 12)) / 60)
121+
);
122+
}
123+
55124
let ianaZoneCache = {};
125+
let offsetFunc;
56126
/**
57127
* A zone identified by an IANA identifier, like America/New_York
58128
* @implements {Zone}
@@ -75,7 +145,8 @@ export default class IANAZone extends Zone {
75145
*/
76146
static resetCache() {
77147
ianaZoneCache = {};
78-
dtfCache = {};
148+
calculatedOffsetDTFCache = {};
149+
directOffsetDTFCache = {};
79150
}
80151

81152
/**
@@ -145,36 +216,24 @@ export default class IANAZone extends Zone {
145216

146217
/** @override **/
147218
offset(ts) {
148-
const date = new Date(ts);
149-
150-
if (isNaN(date)) return NaN;
151-
152-
const dtf = makeDTF(this.name);
153-
let [year, month, day, adOrBc, hour, minute, second] = dtf.formatToParts
154-
? partsOffset(dtf, date)
155-
: hackyOffset(dtf, date);
156-
157-
if (adOrBc === "BC") {
158-
year = -Math.abs(year) + 1;
219+
if (offsetFunc === undefined) {
220+
try {
221+
const ts = Date.now();
222+
// directOffset will raise an error if not supported by the engine
223+
// also check it works correctly as it relies on a specific format
224+
if (
225+
directOffset("Etc/GMT", ts) !== 0 ||
226+
directOffset("Etc/GMT+1", ts) !== -60 ||
227+
directOffset("Etc/GMT-1", ts) !== +60
228+
)
229+
throw new Error("Invalid offset");
230+
offsetFunc = directOffset;
231+
} catch (e) {
232+
offsetFunc = calculatedOffset;
233+
}
159234
}
160235

161-
// because we're using hour12 and https://bugs.chromium.org/p/chromium/issues/detail?id=1025564&can=2&q=%2224%3A00%22%20datetimeformat
162-
const adjustedHour = hour === 24 ? 0 : hour;
163-
164-
const asUTC = objToLocalTS({
165-
year,
166-
month,
167-
day,
168-
hour: adjustedHour,
169-
minute,
170-
second,
171-
millisecond: 0,
172-
});
173-
174-
let asTS = +date;
175-
const over = asTS % 1000;
176-
asTS -= over >= 0 ? over : 1000 + over;
177-
return (asUTC - asTS) / (60 * 1000);
236+
return offsetFunc(this.name, ts);
178237
}
179238

180239
/** @override **/

0 commit comments

Comments
 (0)