@escape-game-over/atlas 0.1.90 → 0.1.92

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.90",
3
+ "version": "0.1.92",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -9,6 +9,11 @@
9
9
  * import, so a component's contract can be imported from frontmatter too.
10
10
  */
11
11
 
12
+ export {
13
+ type OpenStatus,
14
+ openStatus,
15
+ type VenueNow,
16
+ } from "../hours.ts";
12
17
  export * from "./attribution.ts";
13
18
  export * from "./axon-event.ts";
14
19
  export * from "./background-video.ts";
package/src/hours.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { TimeZone } from "./time-zones.ts";
1
2
  import type { Digit, NonEmpty } from "./types.ts";
2
3
 
3
4
  /**
@@ -192,6 +193,11 @@ export interface HoursFormat {
192
193
  export interface FormattedHours {
193
194
  /** `"Mon–Thu, Sun"`. */
194
195
  readonly days: string;
196
+ /**
197
+ * The same days as ids, in week order: what a page matches today against
198
+ * to light the row it falls in.
199
+ */
200
+ readonly weekdays: NonEmpty<Weekday>;
195
201
  /** `"14:00–23:30"`, or the word for closed. */
196
202
  readonly times: string;
197
203
  }
@@ -250,6 +256,7 @@ export function formatHours(
250
256
  : `${dayName(first)}${between}${dayName(last)}`
251
257
  )
252
258
  .join(and),
259
+ weekdays: group.days,
253
260
  times:
254
261
  group.hours === "closed"
255
262
  ? format.closed
@@ -347,3 +354,156 @@ const SCHEMA_DAY: Readonly<Record<Weekday, string>> = {
347
354
  export function schemaDays(group: HoursGroup): readonly string[] {
348
355
  return group.days.map((day) => SCHEMA_DAY[day]);
349
356
  }
357
+
358
+ /**
359
+ * The day before each day, and the day after, written out rather than worked
360
+ * out from positions in `WEEK`.
361
+ */
362
+ const YESTERDAY: Readonly<Record<Weekday, Weekday>> = {
363
+ monday: "sunday",
364
+ tuesday: "monday",
365
+ wednesday: "tuesday",
366
+ thursday: "wednesday",
367
+ friday: "thursday",
368
+ saturday: "friday",
369
+ sunday: "saturday",
370
+ };
371
+ const TOMORROW: Readonly<Record<Weekday, Weekday>> = {
372
+ monday: "tuesday",
373
+ tuesday: "wednesday",
374
+ wednesday: "thursday",
375
+ thursday: "friday",
376
+ friday: "saturday",
377
+ saturday: "sunday",
378
+ sunday: "monday",
379
+ };
380
+
381
+ /**
382
+ * The venue's wall clock at `now`: the day, and the time.
383
+ *
384
+ * `now` is a moment, the same everywhere; `timeZone` is the venue's, so a
385
+ * visitor in Athens looking at a London venue gets London's time. The
386
+ * visitor's own zone never enters into it.
387
+ *
388
+ * The formatter's output is read, not shown, so its locale only has to name
389
+ * weekdays the way `Weekday` spells them: `"en"`.
390
+ */
391
+ function clockAt(
392
+ now: Date,
393
+ timeZone: TimeZone
394
+ ): { readonly day: Weekday; readonly time: string } {
395
+ // Ask for the weekday, hour and minute as they read in the venue's zone,
396
+ // as separate pieces: [{ type: "weekday", value: "Saturday" }, …].
397
+ // `h23` makes midnight "00", not "24" (which some locales give).
398
+ const parts = new Intl.DateTimeFormat("en", {
399
+ timeZone,
400
+ weekday: "long",
401
+ hour: "2-digit",
402
+ minute: "2-digit",
403
+ hourCycle: "h23",
404
+ }).formatToParts(now);
405
+
406
+ // The value of one piece, e.g. part("hour") → "00".
407
+ const part = (type: Intl.DateTimeFormatPartTypes): string => {
408
+ return parts.find((each) => each.type === type)?.value ?? "";
409
+ };
410
+
411
+ // "Saturday" → "saturday". Matched against `WEEK` rather than cast, so
412
+ // the result is a `Weekday` the compiler knows about.
413
+ const day = WEEK.find((each) => each === part("weekday").toLowerCase());
414
+ if (day === undefined) {
415
+ throw new Error(`No weekday in ${now.toISOString()} at ${timeZone}.`);
416
+ }
417
+
418
+ // "00:30": the same shape as `TimeOfDay`, so it compares against the
419
+ // hours directly.
420
+ return { day, time: `${part("hour")}:${part("minute")}` };
421
+ }
422
+
423
+ /**
424
+ * Where a venue stands, as a page words it: open until when, or when it
425
+ * opens next. Only "closed today" has no time to go with it.
426
+ */
427
+ export type OpenStatus =
428
+ | { readonly state: "open"; readonly time: TimeOfDay }
429
+ | { readonly state: "opensToday"; readonly time: TimeOfDay }
430
+ | { readonly state: "opensTomorrow"; readonly time: TimeOfDay }
431
+ | { readonly state: "closedToday" };
432
+
433
+ /** A day's spans; none on a day off. */
434
+ function spansOn(hours: WeeklyHours, day: Weekday): readonly TimeRange[] {
435
+ const dayHours = hours[day];
436
+ return dayHours === "closed" ? [] : dayHours;
437
+ }
438
+
439
+ /** `14:00`–`00:45`: closes the next day. */
440
+ function pastMidnight(range: TimeRange): boolean {
441
+ return range.closes < range.opens;
442
+ }
443
+
444
+ /** Today on the venue's clock, and where the venue stands. */
445
+ export interface VenueNow {
446
+ /** For lighting today's row in a printed week. */
447
+ readonly today: Weekday;
448
+ readonly status: OpenStatus;
449
+ }
450
+
451
+ /**
452
+ * Open or shut at `now`, on the venue's clock. Computed in the browser, not
453
+ * at build: the answer changes by the minute.
454
+ *
455
+ * Times are compared as text, which for `"HH:MM"` is the same as comparing
456
+ * them as times. A span past midnight keeps Friday's opening going into
457
+ * Saturday's small hours: at 00:30 on Saturday, `14:00`–`00:45` on Friday is
458
+ * still open.
459
+ */
460
+ export function openStatus(
461
+ hours: WeeklyHours,
462
+ timeZone: TimeZone,
463
+ now: Date
464
+ ): VenueNow {
465
+ const { day, time } = clockAt(now, timeZone);
466
+ return { today: day, status: statusAt(hours, day, time) };
467
+ }
468
+
469
+ /**
470
+ * The status at one wall-clock moment. Checked in this order, and the first
471
+ * that matches wins.
472
+ */
473
+ function statusAt(hours: WeeklyHours, day: Weekday, time: string): OpenStatus {
474
+ // 1. Still in last night's opening: yesterday has a span past midnight
475
+ // (`14:00`–`00:45`) and it is not yet 00:45.
476
+ const lateYesterday = spansOn(hours, YESTERDAY[day]).find(
477
+ (range) => pastMidnight(range) && time < range.closes
478
+ );
479
+ if (lateYesterday !== undefined) {
480
+ return { state: "open", time: lateYesterday.closes };
481
+ }
482
+
483
+ // 2. Open now: a span of today has started and not yet closed. A span
484
+ // past midnight, once started, stays open for the rest of today.
485
+ const today = spansOn(hours, day);
486
+ const current = today.find(
487
+ (range) =>
488
+ range.opens <= time && (pastMidnight(range) || time < range.closes)
489
+ );
490
+ if (current !== undefined) {
491
+ return { state: "open", time: current.closes };
492
+ }
493
+
494
+ // 3. Shut now, but a span of today has not started yet: before opening,
495
+ // or between the spans of a split day.
496
+ const later = today.find((range) => time < range.opens);
497
+ if (later !== undefined) {
498
+ return { state: "opensToday", time: later.opens };
499
+ }
500
+
501
+ // 4. Done for today: when tomorrow's first span opens.
502
+ const [tomorrow] = spansOn(hours, TOMORROW[day]);
503
+ if (tomorrow !== undefined) {
504
+ return { state: "opensTomorrow", time: tomorrow.opens };
505
+ }
506
+
507
+ // 5. Done for today, and tomorrow is a day off.
508
+ return { state: "closedToday" };
509
+ }
@@ -1,6 +1,7 @@
1
1
  import type { LocalesOf, SiteConfigShape } from "../config.ts";
2
2
  import type { IsNever, NoExcessKeys, StringKeys } from "../types.ts";
3
3
  import type {
4
+ AddedMessageExists,
4
5
  EntryTokens,
5
6
  MismatchedLocales,
6
7
  OverridePlaceholderMismatch,
@@ -137,10 +138,32 @@ export type MessageOverrides<Catalog, L extends string> = {
137
138
  readonly [K in StringKeys<Catalog>]?: Readonly<Partial<Record<L, string>>>;
138
139
  };
139
140
 
140
- /** Merges a project's override catalog over the base catalog, locale by locale. */
141
+ /**
142
+ * What `defineProject` checks an `addMessages` against: each key new to the
143
+ * base catalog, written for exactly the locales the project publishes, with
144
+ * the same `{placeholders}` and `[marks]` in each.
145
+ *
146
+ * New only, because a key the base already has is copy every project shares,
147
+ * and rewording it is what `overrideMessages` is for — with its own check that
148
+ * the placeholders survive. An addition shadowing a base key would get round
149
+ * that check, and two fields could then say different things about one key.
150
+ */
151
+ export type ValidateAdditions<T, Base, Published extends string> = {
152
+ [K in keyof T]: K extends keyof Base
153
+ ? AddedMessageExists<K & string>
154
+ : NoExcessKeys<T[K], Published> &
155
+ Readonly<Record<Published, string>> &
156
+ SelfConsistent<T[K], K>;
157
+ };
158
+
159
+ /**
160
+ * Merges a project's override catalog over the base catalog, locale by locale,
161
+ * then adds the project's own messages.
162
+ */
141
163
  export function mergeCatalog<L extends string>(
142
164
  base: BaseCatalog<L>,
143
- overrides: OverrideCatalog<L>
165
+ overrides: OverrideCatalog<L>,
166
+ additions: OverrideCatalog<L> = {}
144
167
  ): Readonly<Record<string, Readonly<Record<L, string>>>> {
145
168
  const merged: Record<string, Record<string, string>> = {};
146
169
  for (const [key, entry] of Object.entries(base)) {
@@ -154,5 +177,15 @@ export function mergeCatalog<L extends string>(
154
177
  if (typeof text === "string") target[locale] = text;
155
178
  }
156
179
  }
180
+ for (const [key, entry] of Object.entries(additions)) {
181
+ // Unreachable via the typed API, which refuses a base key here; an
182
+ // Astro config is evaluated before the type check, so say it plainly.
183
+ if (key in merged) {
184
+ throw new Error(
185
+ `addMessages: "${key}" is already in the default catalog. Reword it in overrideMessages instead.`
186
+ );
187
+ }
188
+ merged[key] = { ...entry } as Record<string, string>;
189
+ }
157
190
  return merged as Readonly<Record<string, Readonly<Record<L, string>>>>;
158
191
  }
@@ -112,6 +112,11 @@ export interface PlaceholderMismatch<Key extends string, L extends string> {
112
112
  readonly __PLACEHOLDER_MISMATCH__: `Message "${Key}" uses different {placeholders} or [marks] in locale "${L}" than in its other locales`;
113
113
  }
114
114
 
115
+ /** Editor-facing error when a project adds a message the base already has. */
116
+ export interface AddedMessageExists<Key extends string> {
117
+ readonly __ADDED_MESSAGE_EXISTS__: `Message "${Key}" is already in the default catalog: reword it in overrideMessages instead of adding it`;
118
+ }
119
+
115
120
  /** Editor-facing error when an override drops or invents one. */
116
121
  export interface OverridePlaceholderMismatch<
117
122
  Key extends string,
package/src/index.ts CHANGED
@@ -122,8 +122,11 @@ export {
122
122
  groupHours,
123
123
  type HoursFormat,
124
124
  type HoursGroup,
125
+ type OpenStatus,
126
+ openStatus,
125
127
  type TimeOfDay,
126
128
  type TimeRange,
129
+ type VenueNow,
127
130
  type Weekday,
128
131
  type WeeklyHours,
129
132
  } from "./hours.ts";
@@ -230,6 +233,7 @@ export {
230
233
  tierRange,
231
234
  } from "./money.ts";
232
235
  export type {
236
+ AddedCatalog,
233
237
  DefinedProject,
234
238
  ProjectInput,
235
239
  ProjectRouteId,
@@ -284,6 +288,7 @@ export type {
284
288
  StaticPath,
285
289
  } from "./site/index.ts";
286
290
  export type { Sitemap, SitemapConfig } from "./sitemap.ts";
291
+ export { TIME_ZONES, type TimeZone } from "./time-zones.ts";
287
292
  export type { IsoDate, Letter, NonEmpty, StringKeys } from "./types.ts";
288
293
  export {
289
294
  absoluteUrl,
package/src/project.ts CHANGED
@@ -10,6 +10,7 @@ import type { Fonts } from "./fonts.ts";
10
10
  import type {
11
11
  BaseCatalog,
12
12
  OverrideCatalog,
13
+ ValidateAdditions,
13
14
  ValidateOverrideCatalog,
14
15
  } from "./i18n/define.ts";
15
16
  import type { ImageAsset } from "./image.ts";
@@ -139,6 +140,20 @@ export interface ProjectInput<L extends string, Routes> {
139
140
  * than the argument.
140
141
  */
141
142
  readonly overrideMessages: OverrideCatalog<L>;
143
+ /**
144
+ * Copy only this project has: keys the default catalog does not declare,
145
+ * each written for every locale the project publishes and no others.
146
+ *
147
+ * For what one deployment says and no other does — a venue's own FAQ, a
148
+ * house rule — so it goes through `translate` and `rich` like any message,
149
+ * marks and placeholders included, without the shared catalog carrying it
150
+ * in languages the venue never publishes. A key the default catalog
151
+ * already has is refused: reword it in `overrideMessages`.
152
+ *
153
+ * Optional, unlike `overrideMessages`: nothing downstream is computed from
154
+ * it except the catalog's keys, which an absent field leaves as they are.
155
+ */
156
+ readonly addMessages?: OverrideCatalog<L>;
142
157
  /** Slug retranslations and page on/off switches. See `overrideMessages`. */
143
158
  readonly overrideRoutes: RouteOverrides<Routes, L>;
144
159
  }
@@ -289,7 +304,20 @@ export type ProjectOverrideChecks<
289
304
  Routes,
290
305
  T["overrideRoutes"]
291
306
  >;
292
- };
307
+ } & (T extends { readonly addMessages: infer A }
308
+ ? {
309
+ readonly addMessages: ValidateAdditions<
310
+ A,
311
+ Catalog,
312
+ T["enabledLocales"][number]
313
+ >;
314
+ }
315
+ : unknown);
316
+
317
+ /** A project's own messages, as a catalog: none when it adds none. */
318
+ export type AddedCatalog<P> = P extends { readonly addMessages: infer A }
319
+ ? A
320
+ : Record<never, never>;
293
321
 
294
322
  export function defineProject<
295
323
  const C extends SiteConfigShape,
@@ -324,5 +352,22 @@ export function defineProject<
324
352
  );
325
353
  }
326
354
  }
355
+ // The same two rules the type states, for the same reason: the config is
356
+ // evaluated before the type check runs.
357
+ for (const [key, entry] of Object.entries(project.addMessages ?? {})) {
358
+ if (key in _messages) {
359
+ throw new Error(
360
+ `Project "${project.siteName}": addMessages "${key}" is already in the default catalog. Reword it in overrideMessages instead.`
361
+ );
362
+ }
363
+ const missing = project.enabledLocales.filter(
364
+ (locale) => typeof entry[locale] !== "string"
365
+ );
366
+ if (missing.length > 0) {
367
+ throw new Error(
368
+ `Project "${project.siteName}": addMessages "${key}" has no ${missing.join(", ")}. A message a project adds is written for every locale it publishes.`
369
+ );
370
+ }
371
+ }
327
372
  return project;
328
373
  }
@@ -22,7 +22,7 @@ import {
22
22
  buildNotFoundMeta,
23
23
  resolveShareImage,
24
24
  } from "../meta/index.ts";
25
- import type { ProjectInput, ProjectRouteId } from "../project.ts";
25
+ import type { AddedCatalog, ProjectInput, ProjectRouteId } from "../project.ts";
26
26
  import {
27
27
  buildRedirects,
28
28
  type RedirectRule,
@@ -102,7 +102,7 @@ export function createSite<
102
102
  project: P & { readonly enabledLocales: readonly Published[] }
103
103
  ): Site<
104
104
  Published,
105
- Catalog,
105
+ Catalog & AddedCatalog<P>,
106
106
  ProjectRouteId<Routes, P>,
107
107
  SectionPrefixOf<Routes, P["overrideRoutes"]>,
108
108
  OrphanSectionPrefixOf<Routes, P["overrideRoutes"]>
@@ -179,8 +179,15 @@ export function createSite<
179
179
  const llmsUrl: HttpsUrl | undefined =
180
180
  config_.llms === false ? undefined : joinUrl(siteUrl, `/${llmsName}`);
181
181
 
182
- const catalog = mergeCatalog<L>(messages, project.overrideMessages);
183
- const translateFactory = createTranslateFactory<Catalog, L>();
182
+ // The project's own messages join the catalog here, typed and resolved like
183
+ // any other: `Full` is what every reader below knows the keys by.
184
+ type Full = Catalog & AddedCatalog<P>;
185
+ const catalog = mergeCatalog<L>(
186
+ messages,
187
+ project.overrideMessages,
188
+ project.addMessages
189
+ );
190
+ const translateFactory = createTranslateFactory<Full, L>();
184
191
 
185
192
  const resolved = mergeRoutes<L, Routes>(routes, project.overrideRoutes);
186
193
 
@@ -831,14 +838,14 @@ export function createSite<
831
838
 
832
839
  translate: (locale) => translateFactory(catalog, locale),
833
840
  rich: (locale) =>
834
- createRichText<Catalog, L, RouteId>({
841
+ createRichText<Full, L, RouteId>({
835
842
  catalog,
836
843
  locale,
837
844
  link: richLink(locale),
838
845
  origin: siteUrl,
839
846
  }),
840
847
  plain: (locale) =>
841
- createPlainText<Catalog, L>({
848
+ createPlainText<Full, L>({
842
849
  catalog,
843
850
  locale,
844
851
  link: richLink(locale),
@@ -0,0 +1,481 @@
1
+ /**
2
+ * Every IANA time zone a venue can be in: `"Europe/London"`, `"Asia/Dubai"`.
3
+ *
4
+ * A union rather than `string` so a typo (`"Europe/Londn"`) fails to compile
5
+ * instead of throwing in a visitor's browser, the one place nobody looks.
6
+ *
7
+ * Two lists merged, because neither is complete on its own. tzdata's zones
8
+ * (2026c) carry the current names (`Asia/Kolkata`, `Europe/Kyiv`); ICU's
9
+ * `Intl.supportedValuesOf("timeZone")` still answers with the old ones
10
+ * (`Asia/Calcutta`, `Europe/Kiev`) and has a few tzdata folds into links.
11
+ * Every engine accepts both spellings, so both are allowed. `Factory`, tzdata's
12
+ * placeholder, is left out.
13
+ *
14
+ * Regenerate when tzdata adds a zone, which happens about once a year.
15
+ */
16
+ export const TIME_ZONES = [
17
+ "Africa/Abidjan",
18
+ "Africa/Accra",
19
+ "Africa/Addis_Ababa",
20
+ "Africa/Algiers",
21
+ "Africa/Asmera",
22
+ "Africa/Bamako",
23
+ "Africa/Bangui",
24
+ "Africa/Banjul",
25
+ "Africa/Bissau",
26
+ "Africa/Blantyre",
27
+ "Africa/Brazzaville",
28
+ "Africa/Bujumbura",
29
+ "Africa/Cairo",
30
+ "Africa/Casablanca",
31
+ "Africa/Ceuta",
32
+ "Africa/Conakry",
33
+ "Africa/Dakar",
34
+ "Africa/Dar_es_Salaam",
35
+ "Africa/Djibouti",
36
+ "Africa/Douala",
37
+ "Africa/El_Aaiun",
38
+ "Africa/Freetown",
39
+ "Africa/Gaborone",
40
+ "Africa/Harare",
41
+ "Africa/Johannesburg",
42
+ "Africa/Juba",
43
+ "Africa/Kampala",
44
+ "Africa/Khartoum",
45
+ "Africa/Kigali",
46
+ "Africa/Kinshasa",
47
+ "Africa/Lagos",
48
+ "Africa/Libreville",
49
+ "Africa/Lome",
50
+ "Africa/Luanda",
51
+ "Africa/Lubumbashi",
52
+ "Africa/Lusaka",
53
+ "Africa/Malabo",
54
+ "Africa/Maputo",
55
+ "Africa/Maseru",
56
+ "Africa/Mbabane",
57
+ "Africa/Mogadishu",
58
+ "Africa/Monrovia",
59
+ "Africa/Nairobi",
60
+ "Africa/Ndjamena",
61
+ "Africa/Niamey",
62
+ "Africa/Nouakchott",
63
+ "Africa/Ouagadougou",
64
+ "Africa/Porto-Novo",
65
+ "Africa/Sao_Tome",
66
+ "Africa/Tripoli",
67
+ "Africa/Tunis",
68
+ "Africa/Windhoek",
69
+ "America/Adak",
70
+ "America/Anchorage",
71
+ "America/Anguilla",
72
+ "America/Antigua",
73
+ "America/Araguaina",
74
+ "America/Argentina/Buenos_Aires",
75
+ "America/Argentina/Catamarca",
76
+ "America/Argentina/Cordoba",
77
+ "America/Argentina/Jujuy",
78
+ "America/Argentina/La_Rioja",
79
+ "America/Argentina/Mendoza",
80
+ "America/Argentina/Rio_Gallegos",
81
+ "America/Argentina/Salta",
82
+ "America/Argentina/San_Juan",
83
+ "America/Argentina/San_Luis",
84
+ "America/Argentina/Tucuman",
85
+ "America/Argentina/Ushuaia",
86
+ "America/Aruba",
87
+ "America/Asuncion",
88
+ "America/Bahia",
89
+ "America/Bahia_Banderas",
90
+ "America/Barbados",
91
+ "America/Belem",
92
+ "America/Belize",
93
+ "America/Blanc-Sablon",
94
+ "America/Boa_Vista",
95
+ "America/Bogota",
96
+ "America/Boise",
97
+ "America/Buenos_Aires",
98
+ "America/Cambridge_Bay",
99
+ "America/Campo_Grande",
100
+ "America/Cancun",
101
+ "America/Caracas",
102
+ "America/Catamarca",
103
+ "America/Cayenne",
104
+ "America/Cayman",
105
+ "America/Chicago",
106
+ "America/Chihuahua",
107
+ "America/Ciudad_Juarez",
108
+ "America/Coral_Harbour",
109
+ "America/Cordoba",
110
+ "America/Costa_Rica",
111
+ "America/Coyhaique",
112
+ "America/Creston",
113
+ "America/Cuiaba",
114
+ "America/Curacao",
115
+ "America/Danmarkshavn",
116
+ "America/Dawson",
117
+ "America/Dawson_Creek",
118
+ "America/Denver",
119
+ "America/Detroit",
120
+ "America/Dominica",
121
+ "America/Edmonton",
122
+ "America/Eirunepe",
123
+ "America/El_Salvador",
124
+ "America/Fort_Nelson",
125
+ "America/Fortaleza",
126
+ "America/Glace_Bay",
127
+ "America/Godthab",
128
+ "America/Goose_Bay",
129
+ "America/Grand_Turk",
130
+ "America/Grenada",
131
+ "America/Guadeloupe",
132
+ "America/Guatemala",
133
+ "America/Guayaquil",
134
+ "America/Guyana",
135
+ "America/Halifax",
136
+ "America/Havana",
137
+ "America/Hermosillo",
138
+ "America/Indiana/Indianapolis",
139
+ "America/Indiana/Knox",
140
+ "America/Indiana/Marengo",
141
+ "America/Indiana/Petersburg",
142
+ "America/Indiana/Tell_City",
143
+ "America/Indiana/Vevay",
144
+ "America/Indiana/Vincennes",
145
+ "America/Indiana/Winamac",
146
+ "America/Indianapolis",
147
+ "America/Inuvik",
148
+ "America/Iqaluit",
149
+ "America/Jamaica",
150
+ "America/Jujuy",
151
+ "America/Juneau",
152
+ "America/Kentucky/Louisville",
153
+ "America/Kentucky/Monticello",
154
+ "America/Kralendijk",
155
+ "America/La_Paz",
156
+ "America/Lima",
157
+ "America/Los_Angeles",
158
+ "America/Louisville",
159
+ "America/Lower_Princes",
160
+ "America/Maceio",
161
+ "America/Managua",
162
+ "America/Manaus",
163
+ "America/Marigot",
164
+ "America/Martinique",
165
+ "America/Matamoros",
166
+ "America/Mazatlan",
167
+ "America/Mendoza",
168
+ "America/Menominee",
169
+ "America/Merida",
170
+ "America/Metlakatla",
171
+ "America/Mexico_City",
172
+ "America/Miquelon",
173
+ "America/Moncton",
174
+ "America/Monterrey",
175
+ "America/Montevideo",
176
+ "America/Montserrat",
177
+ "America/Nassau",
178
+ "America/New_York",
179
+ "America/Nome",
180
+ "America/Noronha",
181
+ "America/North_Dakota/Beulah",
182
+ "America/North_Dakota/Center",
183
+ "America/North_Dakota/New_Salem",
184
+ "America/Nuuk",
185
+ "America/Ojinaga",
186
+ "America/Panama",
187
+ "America/Paramaribo",
188
+ "America/Phoenix",
189
+ "America/Port-au-Prince",
190
+ "America/Port_of_Spain",
191
+ "America/Porto_Velho",
192
+ "America/Puerto_Rico",
193
+ "America/Punta_Arenas",
194
+ "America/Rankin_Inlet",
195
+ "America/Recife",
196
+ "America/Regina",
197
+ "America/Resolute",
198
+ "America/Rio_Branco",
199
+ "America/Santarem",
200
+ "America/Santiago",
201
+ "America/Santo_Domingo",
202
+ "America/Sao_Paulo",
203
+ "America/Scoresbysund",
204
+ "America/Sitka",
205
+ "America/St_Barthelemy",
206
+ "America/St_Johns",
207
+ "America/St_Kitts",
208
+ "America/St_Lucia",
209
+ "America/St_Thomas",
210
+ "America/St_Vincent",
211
+ "America/Swift_Current",
212
+ "America/Tegucigalpa",
213
+ "America/Thule",
214
+ "America/Tijuana",
215
+ "America/Toronto",
216
+ "America/Tortola",
217
+ "America/Vancouver",
218
+ "America/Whitehorse",
219
+ "America/Winnipeg",
220
+ "America/Yakutat",
221
+ "Antarctica/Casey",
222
+ "Antarctica/Davis",
223
+ "Antarctica/DumontDUrville",
224
+ "Antarctica/Macquarie",
225
+ "Antarctica/Mawson",
226
+ "Antarctica/McMurdo",
227
+ "Antarctica/Palmer",
228
+ "Antarctica/Rothera",
229
+ "Antarctica/Syowa",
230
+ "Antarctica/Troll",
231
+ "Antarctica/Vostok",
232
+ "Arctic/Longyearbyen",
233
+ "Asia/Aden",
234
+ "Asia/Almaty",
235
+ "Asia/Amman",
236
+ "Asia/Anadyr",
237
+ "Asia/Aqtau",
238
+ "Asia/Aqtobe",
239
+ "Asia/Ashgabat",
240
+ "Asia/Atyrau",
241
+ "Asia/Baghdad",
242
+ "Asia/Bahrain",
243
+ "Asia/Baku",
244
+ "Asia/Bangkok",
245
+ "Asia/Barnaul",
246
+ "Asia/Beirut",
247
+ "Asia/Bishkek",
248
+ "Asia/Brunei",
249
+ "Asia/Calcutta",
250
+ "Asia/Chita",
251
+ "Asia/Colombo",
252
+ "Asia/Damascus",
253
+ "Asia/Dhaka",
254
+ "Asia/Dili",
255
+ "Asia/Dubai",
256
+ "Asia/Dushanbe",
257
+ "Asia/Famagusta",
258
+ "Asia/Gaza",
259
+ "Asia/Hebron",
260
+ "Asia/Ho_Chi_Minh",
261
+ "Asia/Hong_Kong",
262
+ "Asia/Hovd",
263
+ "Asia/Irkutsk",
264
+ "Asia/Jakarta",
265
+ "Asia/Jayapura",
266
+ "Asia/Jerusalem",
267
+ "Asia/Kabul",
268
+ "Asia/Kamchatka",
269
+ "Asia/Karachi",
270
+ "Asia/Kathmandu",
271
+ "Asia/Katmandu",
272
+ "Asia/Khandyga",
273
+ "Asia/Kolkata",
274
+ "Asia/Krasnoyarsk",
275
+ "Asia/Kuala_Lumpur",
276
+ "Asia/Kuching",
277
+ "Asia/Kuwait",
278
+ "Asia/Macau",
279
+ "Asia/Magadan",
280
+ "Asia/Makassar",
281
+ "Asia/Manila",
282
+ "Asia/Muscat",
283
+ "Asia/Nicosia",
284
+ "Asia/Novokuznetsk",
285
+ "Asia/Novosibirsk",
286
+ "Asia/Omsk",
287
+ "Asia/Oral",
288
+ "Asia/Phnom_Penh",
289
+ "Asia/Pontianak",
290
+ "Asia/Pyongyang",
291
+ "Asia/Qatar",
292
+ "Asia/Qostanay",
293
+ "Asia/Qyzylorda",
294
+ "Asia/Rangoon",
295
+ "Asia/Riyadh",
296
+ "Asia/Saigon",
297
+ "Asia/Sakhalin",
298
+ "Asia/Samarkand",
299
+ "Asia/Seoul",
300
+ "Asia/Shanghai",
301
+ "Asia/Singapore",
302
+ "Asia/Srednekolymsk",
303
+ "Asia/Taipei",
304
+ "Asia/Tashkent",
305
+ "Asia/Tbilisi",
306
+ "Asia/Tehran",
307
+ "Asia/Thimphu",
308
+ "Asia/Tokyo",
309
+ "Asia/Tomsk",
310
+ "Asia/Ulaanbaatar",
311
+ "Asia/Urumqi",
312
+ "Asia/Ust-Nera",
313
+ "Asia/Vientiane",
314
+ "Asia/Vladivostok",
315
+ "Asia/Yakutsk",
316
+ "Asia/Yangon",
317
+ "Asia/Yekaterinburg",
318
+ "Asia/Yerevan",
319
+ "Atlantic/Azores",
320
+ "Atlantic/Bermuda",
321
+ "Atlantic/Canary",
322
+ "Atlantic/Cape_Verde",
323
+ "Atlantic/Faeroe",
324
+ "Atlantic/Faroe",
325
+ "Atlantic/Madeira",
326
+ "Atlantic/Reykjavik",
327
+ "Atlantic/South_Georgia",
328
+ "Atlantic/St_Helena",
329
+ "Atlantic/Stanley",
330
+ "Australia/Adelaide",
331
+ "Australia/Brisbane",
332
+ "Australia/Broken_Hill",
333
+ "Australia/Darwin",
334
+ "Australia/Eucla",
335
+ "Australia/Hobart",
336
+ "Australia/Lindeman",
337
+ "Australia/Lord_Howe",
338
+ "Australia/Melbourne",
339
+ "Australia/Perth",
340
+ "Australia/Sydney",
341
+ "Etc/GMT",
342
+ "Etc/GMT+1",
343
+ "Etc/GMT+10",
344
+ "Etc/GMT+11",
345
+ "Etc/GMT+12",
346
+ "Etc/GMT+2",
347
+ "Etc/GMT+3",
348
+ "Etc/GMT+4",
349
+ "Etc/GMT+5",
350
+ "Etc/GMT+6",
351
+ "Etc/GMT+7",
352
+ "Etc/GMT+8",
353
+ "Etc/GMT+9",
354
+ "Etc/GMT-1",
355
+ "Etc/GMT-10",
356
+ "Etc/GMT-11",
357
+ "Etc/GMT-12",
358
+ "Etc/GMT-13",
359
+ "Etc/GMT-14",
360
+ "Etc/GMT-2",
361
+ "Etc/GMT-3",
362
+ "Etc/GMT-4",
363
+ "Etc/GMT-5",
364
+ "Etc/GMT-6",
365
+ "Etc/GMT-7",
366
+ "Etc/GMT-8",
367
+ "Etc/GMT-9",
368
+ "Etc/UTC",
369
+ "Europe/Amsterdam",
370
+ "Europe/Andorra",
371
+ "Europe/Astrakhan",
372
+ "Europe/Athens",
373
+ "Europe/Belgrade",
374
+ "Europe/Berlin",
375
+ "Europe/Bratislava",
376
+ "Europe/Brussels",
377
+ "Europe/Bucharest",
378
+ "Europe/Budapest",
379
+ "Europe/Busingen",
380
+ "Europe/Chisinau",
381
+ "Europe/Copenhagen",
382
+ "Europe/Dublin",
383
+ "Europe/Gibraltar",
384
+ "Europe/Guernsey",
385
+ "Europe/Helsinki",
386
+ "Europe/Isle_of_Man",
387
+ "Europe/Istanbul",
388
+ "Europe/Jersey",
389
+ "Europe/Kaliningrad",
390
+ "Europe/Kiev",
391
+ "Europe/Kirov",
392
+ "Europe/Kyiv",
393
+ "Europe/Lisbon",
394
+ "Europe/Ljubljana",
395
+ "Europe/London",
396
+ "Europe/Luxembourg",
397
+ "Europe/Madrid",
398
+ "Europe/Malta",
399
+ "Europe/Mariehamn",
400
+ "Europe/Minsk",
401
+ "Europe/Monaco",
402
+ "Europe/Moscow",
403
+ "Europe/Oslo",
404
+ "Europe/Paris",
405
+ "Europe/Podgorica",
406
+ "Europe/Prague",
407
+ "Europe/Riga",
408
+ "Europe/Rome",
409
+ "Europe/Samara",
410
+ "Europe/San_Marino",
411
+ "Europe/Sarajevo",
412
+ "Europe/Saratov",
413
+ "Europe/Simferopol",
414
+ "Europe/Skopje",
415
+ "Europe/Sofia",
416
+ "Europe/Stockholm",
417
+ "Europe/Tallinn",
418
+ "Europe/Tirane",
419
+ "Europe/Ulyanovsk",
420
+ "Europe/Vaduz",
421
+ "Europe/Vatican",
422
+ "Europe/Vienna",
423
+ "Europe/Vilnius",
424
+ "Europe/Volgograd",
425
+ "Europe/Warsaw",
426
+ "Europe/Zagreb",
427
+ "Europe/Zurich",
428
+ "Indian/Antananarivo",
429
+ "Indian/Chagos",
430
+ "Indian/Christmas",
431
+ "Indian/Cocos",
432
+ "Indian/Comoro",
433
+ "Indian/Kerguelen",
434
+ "Indian/Mahe",
435
+ "Indian/Maldives",
436
+ "Indian/Mauritius",
437
+ "Indian/Mayotte",
438
+ "Indian/Reunion",
439
+ "Pacific/Apia",
440
+ "Pacific/Auckland",
441
+ "Pacific/Bougainville",
442
+ "Pacific/Chatham",
443
+ "Pacific/Easter",
444
+ "Pacific/Efate",
445
+ "Pacific/Enderbury",
446
+ "Pacific/Fakaofo",
447
+ "Pacific/Fiji",
448
+ "Pacific/Funafuti",
449
+ "Pacific/Galapagos",
450
+ "Pacific/Gambier",
451
+ "Pacific/Guadalcanal",
452
+ "Pacific/Guam",
453
+ "Pacific/Honolulu",
454
+ "Pacific/Kanton",
455
+ "Pacific/Kiritimati",
456
+ "Pacific/Kosrae",
457
+ "Pacific/Kwajalein",
458
+ "Pacific/Majuro",
459
+ "Pacific/Marquesas",
460
+ "Pacific/Midway",
461
+ "Pacific/Nauru",
462
+ "Pacific/Niue",
463
+ "Pacific/Norfolk",
464
+ "Pacific/Noumea",
465
+ "Pacific/Pago_Pago",
466
+ "Pacific/Palau",
467
+ "Pacific/Pitcairn",
468
+ "Pacific/Ponape",
469
+ "Pacific/Port_Moresby",
470
+ "Pacific/Rarotonga",
471
+ "Pacific/Saipan",
472
+ "Pacific/Tahiti",
473
+ "Pacific/Tarawa",
474
+ "Pacific/Tongatapu",
475
+ "Pacific/Truk",
476
+ "Pacific/Wake",
477
+ "Pacific/Wallis",
478
+ ] as const;
479
+
480
+ /** One of `TIME_ZONES`. */
481
+ export type TimeZone = (typeof TIME_ZONES)[number];