1 | import dayjs from "dayjs";
|
2 | import { TimezoneAbbrMap, Weekday, Month } from "./types";
|
3 |
|
4 | export const TIMEZONE_ABBR_MAP: TimezoneAbbrMap = {
|
5 | ACDT: 630,
|
6 | ACST: 570,
|
7 | ADT: -180,
|
8 | AEDT: 660,
|
9 | AEST: 600,
|
10 | AFT: 270,
|
11 | AKDT: -480,
|
12 | AKST: -540,
|
13 | ALMT: 360,
|
14 | AMST: -180,
|
15 | AMT: -240,
|
16 | ANAST: 720,
|
17 | ANAT: 720,
|
18 | AQTT: 300,
|
19 | ART: -180,
|
20 | AST: -240,
|
21 | AWDT: 540,
|
22 | AWST: 480,
|
23 | AZOST: 0,
|
24 | AZOT: -60,
|
25 | AZST: 300,
|
26 | AZT: 240,
|
27 | BNT: 480,
|
28 | BOT: -240,
|
29 | BRST: -120,
|
30 | BRT: -180,
|
31 | BST: 60,
|
32 | BTT: 360,
|
33 | CAST: 480,
|
34 | CAT: 120,
|
35 | CCT: 390,
|
36 | CDT: -300,
|
37 | CEST: 120,
|
38 |
|
39 |
|
40 | CET: {
|
41 | timezoneOffsetDuringDst: 2 * 60,
|
42 | timezoneOffsetNonDst: 60,
|
43 | dstStart: (year: number) => getLastWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2),
|
44 | dstEnd: (year: number) => getLastWeekdayOfMonth(year, Month.OCTOBER, Weekday.SUNDAY, 3),
|
45 | },
|
46 | CHADT: 825,
|
47 | CHAST: 765,
|
48 | CKT: -600,
|
49 | CLST: -180,
|
50 | CLT: -240,
|
51 | COT: -300,
|
52 | CST: -360,
|
53 | CT: {
|
54 | timezoneOffsetDuringDst: -5 * 60,
|
55 | timezoneOffsetNonDst: -6 * 60,
|
56 | dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
|
57 | dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
|
58 | },
|
59 | CVT: -60,
|
60 | CXT: 420,
|
61 | ChST: 600,
|
62 | DAVT: 420,
|
63 | EASST: -300,
|
64 | EAST: -360,
|
65 | EAT: 180,
|
66 | ECT: -300,
|
67 | EDT: -240,
|
68 | EEST: 180,
|
69 | EET: 120,
|
70 | EGST: 0,
|
71 | EGT: -60,
|
72 | EST: -300,
|
73 | ET: {
|
74 | timezoneOffsetDuringDst: -4 * 60,
|
75 | timezoneOffsetNonDst: -5 * 60,
|
76 | dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
|
77 | dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
|
78 | },
|
79 | FJST: 780,
|
80 | FJT: 720,
|
81 | FKST: -180,
|
82 | FKT: -240,
|
83 | FNT: -120,
|
84 | GALT: -360,
|
85 | GAMT: -540,
|
86 | GET: 240,
|
87 | GFT: -180,
|
88 | GILT: 720,
|
89 | GMT: 0,
|
90 | GST: 240,
|
91 | GYT: -240,
|
92 | HAA: -180,
|
93 | HAC: -300,
|
94 | HADT: -540,
|
95 | HAE: -240,
|
96 | HAP: -420,
|
97 | HAR: -360,
|
98 | HAST: -600,
|
99 | HAT: -90,
|
100 | HAY: -480,
|
101 | HKT: 480,
|
102 | HLV: -210,
|
103 | HNA: -240,
|
104 | HNC: -360,
|
105 | HNE: -300,
|
106 | HNP: -480,
|
107 | HNR: -420,
|
108 | HNT: -150,
|
109 | HNY: -540,
|
110 | HOVT: 420,
|
111 | ICT: 420,
|
112 | IDT: 180,
|
113 | IOT: 360,
|
114 | IRDT: 270,
|
115 | IRKST: 540,
|
116 | IRKT: 540,
|
117 | IRST: 210,
|
118 | IST: 330,
|
119 | JST: 540,
|
120 | KGT: 360,
|
121 | KRAST: 480,
|
122 | KRAT: 480,
|
123 | KST: 540,
|
124 | KUYT: 240,
|
125 | LHDT: 660,
|
126 | LHST: 630,
|
127 | LINT: 840,
|
128 | MAGST: 720,
|
129 | MAGT: 720,
|
130 | MART: -510,
|
131 | MAWT: 300,
|
132 | MDT: -360,
|
133 | MESZ: 120,
|
134 | MEZ: 60,
|
135 | MHT: 720,
|
136 | MMT: 390,
|
137 | MSD: 240,
|
138 | MSK: 180,
|
139 | MST: -420,
|
140 | MT: {
|
141 | timezoneOffsetDuringDst: -6 * 60,
|
142 | timezoneOffsetNonDst: -7 * 60,
|
143 | dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
|
144 | dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
|
145 | },
|
146 | MUT: 240,
|
147 | MVT: 300,
|
148 | MYT: 480,
|
149 | NCT: 660,
|
150 | NDT: -90,
|
151 | NFT: 690,
|
152 | NOVST: 420,
|
153 | NOVT: 360,
|
154 | NPT: 345,
|
155 | NST: -150,
|
156 | NUT: -660,
|
157 | NZDT: 780,
|
158 | NZST: 720,
|
159 | OMSST: 420,
|
160 | OMST: 420,
|
161 | PDT: -420,
|
162 | PET: -300,
|
163 | PETST: 720,
|
164 | PETT: 720,
|
165 | PGT: 600,
|
166 | PHOT: 780,
|
167 | PHT: 480,
|
168 | PKT: 300,
|
169 | PMDT: -120,
|
170 | PMST: -180,
|
171 | PONT: 660,
|
172 | PST: -480,
|
173 | PT: {
|
174 | timezoneOffsetDuringDst: -7 * 60,
|
175 | timezoneOffsetNonDst: -8 * 60,
|
176 | dstStart: (year: number) => getNthWeekdayOfMonth(year, Month.MARCH, Weekday.SUNDAY, 2, 2),
|
177 | dstEnd: (year: number) => getNthWeekdayOfMonth(year, Month.NOVEMBER, Weekday.SUNDAY, 1, 2),
|
178 | },
|
179 | PWT: 540,
|
180 | PYST: -180,
|
181 | PYT: -240,
|
182 | RET: 240,
|
183 | SAMT: 240,
|
184 | SAST: 120,
|
185 | SBT: 660,
|
186 | SCT: 240,
|
187 | SGT: 480,
|
188 | SRT: -180,
|
189 | SST: -660,
|
190 | TAHT: -600,
|
191 | TFT: 300,
|
192 | TJT: 300,
|
193 | TKT: 780,
|
194 | TLT: 540,
|
195 | TMT: 300,
|
196 | TVT: 720,
|
197 | ULAT: 480,
|
198 | UTC: 0,
|
199 | UYST: -120,
|
200 | UYT: -180,
|
201 | UZT: 300,
|
202 | VET: -210,
|
203 | VLAST: 660,
|
204 | VLAT: 660,
|
205 | VUT: 660,
|
206 | WAST: 120,
|
207 | WAT: 60,
|
208 | WEST: 60,
|
209 | WESZ: 60,
|
210 | WET: 0,
|
211 | WEZ: 0,
|
212 | WFT: 720,
|
213 | WGST: -120,
|
214 | WGT: -180,
|
215 | WIB: 420,
|
216 | WIT: 540,
|
217 | WITA: 480,
|
218 | WST: 780,
|
219 | WT: 0,
|
220 | YAKST: 600,
|
221 | YAKT: 600,
|
222 | YAPT: 600,
|
223 | YEKST: 360,
|
224 | YEKT: 360,
|
225 | };
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | export function getNthWeekdayOfMonth(year: number, month: Month, weekday: Weekday, n: 1 | 2 | 3 | 4, hour = 0): Date {
|
239 | let dayOfMonth = 0;
|
240 | let i = 0;
|
241 | while (i < n) {
|
242 | dayOfMonth++;
|
243 | const date = new Date(year, month - 1, dayOfMonth);
|
244 | if (date.getDay() === weekday) i++;
|
245 | }
|
246 | return new Date(year, month - 1, dayOfMonth, hour);
|
247 | }
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | export function getLastWeekdayOfMonth(year: number, month: Month, weekday: Weekday, hour = 0): Date {
|
260 |
|
261 |
|
262 | const oneIndexedWeekday = weekday === 0 ? 7 : weekday;
|
263 | const date = new Date(year, month - 1 + 1, 1, 12);
|
264 | const firstWeekdayNextMonth = date.getDay() === 0 ? 7 : date.getDay();
|
265 | let dayDiff;
|
266 | if (firstWeekdayNextMonth === oneIndexedWeekday) dayDiff = 7;
|
267 | else if (firstWeekdayNextMonth < oneIndexedWeekday) dayDiff = 7 + firstWeekdayNextMonth - oneIndexedWeekday;
|
268 | else dayDiff = firstWeekdayNextMonth - oneIndexedWeekday;
|
269 | date.setDate(date.getDate() - dayDiff);
|
270 | return new Date(year, month - 1, date.getDate(), hour);
|
271 | }
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 | export function toTimezoneOffset(
|
283 | timezoneInput?: string | number,
|
284 | date?: Date,
|
285 | timezoneOverrides: TimezoneAbbrMap = {}
|
286 | ): number | null {
|
287 | if (timezoneInput == null) {
|
288 | return null;
|
289 | }
|
290 |
|
291 | if (typeof timezoneInput === "number") {
|
292 | return timezoneInput;
|
293 | }
|
294 |
|
295 | const matchedTimezone = timezoneOverrides[timezoneInput] ?? TIMEZONE_ABBR_MAP[timezoneInput];
|
296 | if (matchedTimezone == null) {
|
297 | return null;
|
298 | }
|
299 |
|
300 | if (typeof matchedTimezone == "number") {
|
301 | return matchedTimezone;
|
302 | }
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 | if (date == null) {
|
309 | return null;
|
310 | }
|
311 |
|
312 |
|
313 | if (
|
314 | dayjs(date).isAfter(matchedTimezone.dstStart(date.getFullYear())) &&
|
315 | !dayjs(date).isAfter(matchedTimezone.dstEnd(date.getFullYear()))
|
316 | ) {
|
317 | return matchedTimezone.timezoneOffsetDuringDst;
|
318 | }
|
319 |
|
320 |
|
321 | return matchedTimezone.timezoneOffsetNonDst;
|
322 | }
|