date-vir 9.1.0 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export * from './full-date/is-valid-full-date.js';
24
24
  export * from './full-date/luxon-date-time-conversion.js';
25
25
  export * from './full-date/maybe-create-date.js';
26
26
  export * from './full-date/parsing.js';
27
+ export * from './timezone/timezone-abbreviations.js';
27
28
  export * from './timezone/timezone-checks.js';
28
29
  export * from './timezone/timezone-names.js';
29
30
  export * from './timezone/timezone-shape.js';
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ export * from './full-date/is-valid-full-date.js';
24
24
  export * from './full-date/luxon-date-time-conversion.js';
25
25
  export * from './full-date/maybe-create-date.js';
26
26
  export * from './full-date/parsing.js';
27
+ export * from './timezone/timezone-abbreviations.js';
27
28
  export * from './timezone/timezone-checks.js';
28
29
  export * from './timezone/timezone-names.js';
29
30
  export * from './timezone/timezone-shape.js';
@@ -0,0 +1,44 @@
1
+ import { TimezoneName } from './timezones.js';
2
+ /**
3
+ * Maps a timezone abbreviation that a human would type (or that a web page would print next to a
4
+ * time) to the IANA timezone it most likely means.
5
+ *
6
+ * Both halves of a region's pair map to the same zone: `EST` and `EDT` both give
7
+ * `America/New_York`. The abbreviation says which region, the timestamp says which offset. Mapping
8
+ * `EST` to a fixed -5 zone instead would turn a July time labeled `EST` by a site that never
9
+ * bothers to write `EDT` into an hour-off reading.
10
+ *
11
+ * Abbreviations are not unique across the world and IANA never promised they would be, so each
12
+ * collision here is resolved toward the most populous region that uses it: `CST` is US Central and
13
+ * not China or Cuba, `IST` is India and not Ireland or Israel, `AST` is Atlantic and not Arabia,
14
+ * `GST` is the Gulf and not Chamorro, `AMT` is the Amazon and not Armenia. Pass a real IANA name
15
+ * instead when you need one of the others.
16
+ *
17
+ * `EAST`, `GET`, `NRT`, and `WST` are real abbreviations that are also ordinary words or airport
18
+ * codes. Don't feed arbitrary scraped text through {@link getTimezoneFromAbbreviation} expecting
19
+ * those four to miss.
20
+ *
21
+ * `AMST`, `BRST`, `EGST`, `MSD`, and `PYST` name zones that have since abolished the daylight
22
+ * saving they refer to, but people still type them, so they map to the zone anyway.
23
+ *
24
+ * @category Internal
25
+ * @see {@link getTimezoneFromAbbreviation}
26
+ */
27
+ export declare const timezoneAbbreviations: Record<string, TimezoneName>;
28
+ /**
29
+ * Same as indexing {@link timezoneAbbreviations} directly but accepts any casing and surrounding
30
+ * whitespace, and accepts a string that isn't an abbreviation at all. Returns `undefined` rather
31
+ * than a guess when the abbreviation is unknown, so callers can fall back or alert instead of
32
+ * silently picking a wrong offset.
33
+ *
34
+ * @category Timezone
35
+ * @example
36
+ *
37
+ * ```ts
38
+ * import {getTimezoneFromAbbreviation} from 'date-vir';
39
+ *
40
+ * getTimezoneFromAbbreviation('pst'); // 'America/Los_Angeles'
41
+ * getTimezoneFromAbbreviation('nope'); // undefined
42
+ * ```
43
+ */
44
+ export declare function getTimezoneFromAbbreviation(abbreviation: string): TimezoneName | undefined;
@@ -0,0 +1,222 @@
1
+ // cspell:disable
2
+ import { TimezoneName } from './timezones.js';
3
+ /**
4
+ * Maps a timezone abbreviation that a human would type (or that a web page would print next to a
5
+ * time) to the IANA timezone it most likely means.
6
+ *
7
+ * Both halves of a region's pair map to the same zone: `EST` and `EDT` both give
8
+ * `America/New_York`. The abbreviation says which region, the timestamp says which offset. Mapping
9
+ * `EST` to a fixed -5 zone instead would turn a July time labeled `EST` by a site that never
10
+ * bothers to write `EDT` into an hour-off reading.
11
+ *
12
+ * Abbreviations are not unique across the world and IANA never promised they would be, so each
13
+ * collision here is resolved toward the most populous region that uses it: `CST` is US Central and
14
+ * not China or Cuba, `IST` is India and not Ireland or Israel, `AST` is Atlantic and not Arabia,
15
+ * `GST` is the Gulf and not Chamorro, `AMT` is the Amazon and not Armenia. Pass a real IANA name
16
+ * instead when you need one of the others.
17
+ *
18
+ * `EAST`, `GET`, `NRT`, and `WST` are real abbreviations that are also ordinary words or airport
19
+ * codes. Don't feed arbitrary scraped text through {@link getTimezoneFromAbbreviation} expecting
20
+ * those four to miss.
21
+ *
22
+ * `AMST`, `BRST`, `EGST`, `MSD`, and `PYST` name zones that have since abolished the daylight
23
+ * saving they refer to, but people still type them, so they map to the zone anyway.
24
+ *
25
+ * @category Internal
26
+ * @see {@link getTimezoneFromAbbreviation}
27
+ */
28
+ export const timezoneAbbreviations = {
29
+ ACDT: TimezoneName['Australia/Adelaide'],
30
+ ACRT: TimezoneName['America/Rio_Branco'],
31
+ ACST: TimezoneName['Australia/Adelaide'],
32
+ ACT: TimezoneName['Australia/Adelaide'],
33
+ ACWST: TimezoneName['Australia/Eucla'],
34
+ ADT: TimezoneName['America/Halifax'],
35
+ AEDT: TimezoneName['Australia/Sydney'],
36
+ AEST: TimezoneName['Australia/Sydney'],
37
+ AET: TimezoneName['Australia/Sydney'],
38
+ AFT: TimezoneName['Asia/Kabul'],
39
+ AKDT: TimezoneName['America/Anchorage'],
40
+ AKST: TimezoneName['America/Anchorage'],
41
+ AKT: TimezoneName['America/Anchorage'],
42
+ AMST: TimezoneName['America/Manaus'],
43
+ AMT: TimezoneName['America/Manaus'],
44
+ ANAT: TimezoneName['Asia/Kamchatka'],
45
+ AOE: TimezoneName['Etc/GMT+12'],
46
+ ART: TimezoneName['America/Argentina/Buenos_Aires'],
47
+ AST: TimezoneName['America/Halifax'],
48
+ AT: TimezoneName['America/Halifax'],
49
+ AWST: TimezoneName['Australia/Perth'],
50
+ AWT: TimezoneName['Australia/Perth'],
51
+ AZOST: TimezoneName['Atlantic/Azores'],
52
+ AZOT: TimezoneName['Atlantic/Azores'],
53
+ AZT: TimezoneName['Asia/Baku'],
54
+ BNT: TimezoneName['Asia/Brunei'],
55
+ BOT: TimezoneName['America/La_Paz'],
56
+ BRST: TimezoneName['America/Sao_Paulo'],
57
+ BRT: TimezoneName['America/Sao_Paulo'],
58
+ BST: TimezoneName['Europe/London'],
59
+ BTT: TimezoneName['Asia/Thimphu'],
60
+ CAT: TimezoneName['Africa/Maputo'],
61
+ CCT: TimezoneName['Indian/Cocos'],
62
+ CDT: TimezoneName['America/Chicago'],
63
+ CEST: TimezoneName['Europe/Berlin'],
64
+ CET: TimezoneName['Europe/Berlin'],
65
+ CHADT: TimezoneName['Pacific/Chatham'],
66
+ CHAST: TimezoneName['Pacific/Chatham'],
67
+ CHOT: TimezoneName['Asia/Choibalsan'],
68
+ CHST: TimezoneName['Pacific/Guam'],
69
+ CHUT: TimezoneName['Pacific/Chuuk'],
70
+ CKT: TimezoneName['Pacific/Rarotonga'],
71
+ CLST: TimezoneName['America/Santiago'],
72
+ CLT: TimezoneName['America/Santiago'],
73
+ COT: TimezoneName['America/Bogota'],
74
+ CST: TimezoneName['America/Chicago'],
75
+ CT: TimezoneName['America/Chicago'],
76
+ CVT: TimezoneName['Atlantic/Cape_Verde'],
77
+ CXT: TimezoneName['Indian/Christmas'],
78
+ EASST: TimezoneName['Pacific/Easter'],
79
+ EAST: TimezoneName['Pacific/Easter'],
80
+ EAT: TimezoneName['Africa/Nairobi'],
81
+ EDT: TimezoneName['America/New_York'],
82
+ EEST: TimezoneName['Europe/Athens'],
83
+ EET: TimezoneName['Europe/Athens'],
84
+ EGST: TimezoneName['America/Scoresbysund'],
85
+ EGT: TimezoneName['America/Scoresbysund'],
86
+ EST: TimezoneName['America/New_York'],
87
+ ET: TimezoneName['America/New_York'],
88
+ FJT: TimezoneName['Pacific/Fiji'],
89
+ FKST: TimezoneName['Atlantic/Stanley'],
90
+ FNT: TimezoneName['America/Noronha'],
91
+ GALT: TimezoneName['Pacific/Galapagos'],
92
+ GAMT: TimezoneName['Pacific/Gambier'],
93
+ GET: TimezoneName['Asia/Tbilisi'],
94
+ GFT: TimezoneName['America/Cayenne'],
95
+ GILT: TimezoneName['Pacific/Tarawa'],
96
+ GMT: TimezoneName.UTC,
97
+ GST: TimezoneName['Asia/Dubai'],
98
+ GYT: TimezoneName['America/Guyana'],
99
+ HADT: TimezoneName['America/Adak'],
100
+ HAST: TimezoneName['America/Adak'],
101
+ HAT: TimezoneName['America/Adak'],
102
+ HDT: TimezoneName['Pacific/Honolulu'],
103
+ HKT: TimezoneName['Asia/Hong_Kong'],
104
+ HOVT: TimezoneName['Asia/Hovd'],
105
+ HST: TimezoneName['Pacific/Honolulu'],
106
+ HT: TimezoneName['Pacific/Honolulu'],
107
+ ICT: TimezoneName['Asia/Bangkok'],
108
+ IDT: TimezoneName['Asia/Jerusalem'],
109
+ IOT: TimezoneName['Indian/Chagos'],
110
+ IRKT: TimezoneName['Asia/Irkutsk'],
111
+ IRST: TimezoneName['Asia/Tehran'],
112
+ IST: TimezoneName['Asia/Kolkata'],
113
+ JST: TimezoneName['Asia/Tokyo'],
114
+ KALT: TimezoneName['Europe/Kaliningrad'],
115
+ KGT: TimezoneName['Asia/Bishkek'],
116
+ KOST: TimezoneName['Pacific/Kosrae'],
117
+ KRAT: TimezoneName['Asia/Krasnoyarsk'],
118
+ KST: TimezoneName['Asia/Seoul'],
119
+ LHST: TimezoneName['Australia/Lord_Howe'],
120
+ LINT: TimezoneName['Pacific/Kiritimati'],
121
+ MAGT: TimezoneName['Asia/Magadan'],
122
+ MART: TimezoneName['Pacific/Marquesas'],
123
+ MDT: TimezoneName['America/Denver'],
124
+ MESZ: TimezoneName['Europe/Berlin'],
125
+ MEZ: TimezoneName['Europe/Berlin'],
126
+ MHT: TimezoneName['Pacific/Majuro'],
127
+ MMT: TimezoneName['Asia/Yangon'],
128
+ MSD: TimezoneName['Europe/Moscow'],
129
+ MSK: TimezoneName['Europe/Moscow'],
130
+ MST: TimezoneName['America/Denver'],
131
+ MT: TimezoneName['America/Denver'],
132
+ MUT: TimezoneName['Indian/Mauritius'],
133
+ MVT: TimezoneName['Indian/Maldives'],
134
+ MYT: TimezoneName['Asia/Kuala_Lumpur'],
135
+ NCT: TimezoneName['Pacific/Noumea'],
136
+ NDT: TimezoneName['America/St_Johns'],
137
+ NFT: TimezoneName['Pacific/Norfolk'],
138
+ NOVT: TimezoneName['Asia/Novosibirsk'],
139
+ NPT: TimezoneName['Asia/Kathmandu'],
140
+ NRT: TimezoneName['Pacific/Nauru'],
141
+ NST: TimezoneName['America/St_Johns'],
142
+ NT: TimezoneName['America/St_Johns'],
143
+ NUT: TimezoneName['Pacific/Niue'],
144
+ NZDT: TimezoneName['Pacific/Auckland'],
145
+ NZST: TimezoneName['Pacific/Auckland'],
146
+ NZT: TimezoneName['Pacific/Auckland'],
147
+ OMST: TimezoneName['Asia/Omsk'],
148
+ PDT: TimezoneName['America/Los_Angeles'],
149
+ PET: TimezoneName['America/Lima'],
150
+ PGT: TimezoneName['Pacific/Port_Moresby'],
151
+ PHST: TimezoneName['Asia/Manila'],
152
+ PHT: TimezoneName['Asia/Manila'],
153
+ PKT: TimezoneName['Asia/Karachi'],
154
+ PMDT: TimezoneName['America/Miquelon'],
155
+ PMST: TimezoneName['America/Miquelon'],
156
+ PONT: TimezoneName['Pacific/Pohnpei'],
157
+ PST: TimezoneName['America/Los_Angeles'],
158
+ PT: TimezoneName['America/Los_Angeles'],
159
+ PWT: TimezoneName['Pacific/Palau'],
160
+ PYST: TimezoneName['America/Asuncion'],
161
+ PYT: TimezoneName['America/Asuncion'],
162
+ RET: TimezoneName['Indian/Reunion'],
163
+ SAKT: TimezoneName['Asia/Sakhalin'],
164
+ SAMT: TimezoneName['Europe/Samara'],
165
+ SAST: TimezoneName['Africa/Johannesburg'],
166
+ SBT: TimezoneName['Pacific/Guadalcanal'],
167
+ SCT: TimezoneName['Indian/Mahe'],
168
+ SGT: TimezoneName['Asia/Singapore'],
169
+ SLST: TimezoneName['Asia/Colombo'],
170
+ SRT: TimezoneName['America/Paramaribo'],
171
+ SST: TimezoneName['Pacific/Pago_Pago'],
172
+ TAHT: TimezoneName['Pacific/Tahiti'],
173
+ TJT: TimezoneName['Asia/Dushanbe'],
174
+ TLT: TimezoneName['Asia/Dili'],
175
+ TMT: TimezoneName['Asia/Ashgabat'],
176
+ TOT: TimezoneName['Pacific/Tongatapu'],
177
+ TRT: TimezoneName['Europe/Istanbul'],
178
+ TVT: TimezoneName['Pacific/Funafuti'],
179
+ ULAT: TimezoneName['Asia/Ulaanbaatar'],
180
+ UT: TimezoneName.UTC,
181
+ UTC: TimezoneName.UTC,
182
+ UYT: TimezoneName['America/Montevideo'],
183
+ UZT: TimezoneName['Asia/Tashkent'],
184
+ VET: TimezoneName['America/Caracas'],
185
+ VLAT: TimezoneName['Asia/Vladivostok'],
186
+ WAKT: TimezoneName['Pacific/Wake'],
187
+ WAST: TimezoneName['Africa/Windhoek'],
188
+ WAT: TimezoneName['Africa/Lagos'],
189
+ WEST: TimezoneName['Europe/Lisbon'],
190
+ WET: TimezoneName['Europe/Lisbon'],
191
+ WFT: TimezoneName['Pacific/Wallis'],
192
+ WGST: TimezoneName['America/Nuuk'],
193
+ WGT: TimezoneName['America/Nuuk'],
194
+ WIB: TimezoneName['Asia/Jakarta'],
195
+ WIT: TimezoneName['Asia/Jayapura'],
196
+ WITA: TimezoneName['Asia/Makassar'],
197
+ WST: TimezoneName['Pacific/Apia'],
198
+ YAKT: TimezoneName['Asia/Yakutsk'],
199
+ YEKT: TimezoneName['Asia/Yekaterinburg'],
200
+ Z: TimezoneName.UTC,
201
+ ZULU: TimezoneName.UTC,
202
+ };
203
+ /**
204
+ * Same as indexing {@link timezoneAbbreviations} directly but accepts any casing and surrounding
205
+ * whitespace, and accepts a string that isn't an abbreviation at all. Returns `undefined` rather
206
+ * than a guess when the abbreviation is unknown, so callers can fall back or alert instead of
207
+ * silently picking a wrong offset.
208
+ *
209
+ * @category Timezone
210
+ * @example
211
+ *
212
+ * ```ts
213
+ * import {getTimezoneFromAbbreviation} from 'date-vir';
214
+ *
215
+ * getTimezoneFromAbbreviation('pst'); // 'America/Los_Angeles'
216
+ * getTimezoneFromAbbreviation('nope'); // undefined
217
+ * ```
218
+ */
219
+ export function getTimezoneFromAbbreviation(abbreviation) {
220
+ const searchedAbbreviation = abbreviation.trim().toUpperCase();
221
+ return timezoneAbbreviations[searchedAbbreviation];
222
+ }
@@ -7,9 +7,11 @@ const knownTimezones = new Set(allTimezoneNames);
7
7
  * Caches the result of the {@link Info.isValidIANAZone} fallback. That call constructs an
8
8
  * `Intl.DateTimeFormat` every time (~30µs), so memoizing it keeps repeated checks of the same
9
9
  * string (a legacy alias or an invalid value) cheap. Only strings outside the typed
10
- * {@link knownTimezones} set ever land here, so the cache stays small in practice.
10
+ * {@link knownTimezones} set ever land here. The cache is capped to keep untrusted input from
11
+ * growing it without bound.
11
12
  */
12
13
  const ianaValidityCache = new Map();
14
+ const maxIanaValidityCacheEntries = 1000;
13
15
  /**
14
16
  * Checks that the given timezone is valid.
15
17
  *
@@ -28,6 +30,12 @@ export function isValidTimezone(raw) {
28
30
  return cached;
29
31
  }
30
32
  const isValid = Info.isValidIANAZone(raw);
33
+ if (ianaValidityCache.size >= maxIanaValidityCacheEntries) {
34
+ const oldestEntry = ianaValidityCache.keys().next();
35
+ if (!oldestEntry.done) {
36
+ ianaValidityCache.delete(oldestEntry.value);
37
+ }
38
+ }
31
39
  ianaValidityCache.set(raw, isValid);
32
40
  return isValid;
33
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "date-vir",
3
- "version": "9.1.0",
3
+ "version": "9.2.0",
4
4
  "description": "Easy and explicit dates and times.",
5
5
  "keywords": [
6
6
  "date",
@@ -35,22 +35,22 @@
35
35
  "test:docs": "virmator docs check"
36
36
  },
37
37
  "dependencies": {
38
- "@augment-vir/assert": "^32.2.1",
39
- "@augment-vir/common": "^32.2.1",
40
- "@date-vir/duration": "^9.1.0",
41
- "@types/luxon": "^3.7.3",
38
+ "@augment-vir/assert": "^32.3.1",
39
+ "@augment-vir/common": "^32.3.1",
40
+ "@date-vir/duration": "^9.2.0",
41
+ "@types/luxon": "^3.7.5",
42
42
  "luxon": "^3.7.2",
43
- "object-shape-tester": "^6.14.1",
44
- "type-fest": "^5.8.0"
43
+ "object-shape-tester": "^6.15.0",
44
+ "type-fest": "^5.10.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@augment-vir/test": "^32.2.1",
47
+ "@augment-vir/test": "^32.3.1",
48
48
  "@web/dev-server-esbuild": "^2.0.0",
49
49
  "@web/test-runner": "^1.0.0",
50
50
  "@web/test-runner-commands": "^1.0.1",
51
51
  "@web/test-runner-playwright": "^1.0.0",
52
52
  "@web/test-runner-visual-regression": "^1.0.1",
53
- "element-vir": "^26.17.3",
53
+ "element-vir": "^27.0.1",
54
54
  "istanbul-smart-text-reporter": "^1.1.5",
55
55
  "markdown-code-example-inserter": "^3.0.6",
56
56
  "typedoc": "^0.28.20",