@remit/calendar-service 0.0.18 → 0.0.19

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.
Files changed (73) hide show
  1. package/dist/accept.d.ts +71 -0
  2. package/dist/accept.js +159 -0
  3. package/dist/build.d.ts +79 -0
  4. package/dist/build.js +196 -0
  5. package/dist/correct.d.ts +4 -0
  6. package/dist/correct.js +22 -0
  7. package/dist/errors.d.ts +56 -0
  8. package/dist/errors.js +1 -0
  9. package/dist/etag.d.ts +9 -0
  10. package/{src/etag.ts → dist/etag.js} +1 -3
  11. package/dist/expand.d.ts +73 -0
  12. package/dist/expand.js +188 -0
  13. package/dist/feed.d.ts +81 -0
  14. package/dist/feed.js +157 -0
  15. package/dist/fixtures.d.ts +19 -0
  16. package/dist/fixtures.js +37 -0
  17. package/dist/index.d.ts +16 -0
  18. package/dist/index.js +15 -0
  19. package/dist/memory-store.d.ts +105 -0
  20. package/dist/memory-store.js +319 -0
  21. package/dist/parse.d.ts +39 -0
  22. package/dist/parse.js +87 -0
  23. package/dist/project.d.ts +30 -0
  24. package/dist/project.js +57 -0
  25. package/dist/put.d.ts +52 -0
  26. package/dist/put.js +78 -0
  27. package/dist/reopen.d.ts +14 -0
  28. package/dist/reopen.js +92 -0
  29. package/dist/scope.d.ts +70 -0
  30. package/dist/scope.js +687 -0
  31. package/dist/subscribe.d.ts +96 -0
  32. package/dist/subscribe.js +298 -0
  33. package/dist/suggest.d.ts +74 -0
  34. package/dist/suggest.js +138 -0
  35. package/dist/time.d.ts +76 -0
  36. package/dist/time.js +164 -0
  37. package/dist/window.d.ts +85 -0
  38. package/dist/window.js +161 -0
  39. package/package.json +40 -35
  40. package/src/accept.test.ts +0 -403
  41. package/src/accept.ts +0 -250
  42. package/src/build.test.ts +0 -113
  43. package/src/build.ts +0 -311
  44. package/src/correct.test.ts +0 -173
  45. package/src/correct.ts +0 -40
  46. package/src/errors.ts +0 -62
  47. package/src/etag.test.ts +0 -54
  48. package/src/expand.test.ts +0 -361
  49. package/src/expand.ts +0 -303
  50. package/src/feed.test.ts +0 -334
  51. package/src/feed.ts +0 -203
  52. package/src/feeds/google-secret-address.ics +0 -99
  53. package/src/fixtures.ts +0 -52
  54. package/src/index.ts +0 -128
  55. package/src/memory-store.ts +0 -485
  56. package/src/parse.test.ts +0 -264
  57. package/src/parse.ts +0 -138
  58. package/src/project.test.ts +0 -247
  59. package/src/project.ts +0 -122
  60. package/src/put.test.ts +0 -318
  61. package/src/put.ts +0 -133
  62. package/src/reopen.test.ts +0 -529
  63. package/src/reopen.ts +0 -199
  64. package/src/scope.test.ts +0 -1421
  65. package/src/scope.ts +0 -1003
  66. package/src/subscribe.test.ts +0 -582
  67. package/src/subscribe.ts +0 -433
  68. package/src/suggest.test.ts +0 -376
  69. package/src/suggest.ts +0 -244
  70. package/src/time.ts +0 -260
  71. package/src/window.test.ts +0 -648
  72. package/src/window.ts +0 -281
  73. package/tsconfig.json +0 -8
package/src/time.ts DELETED
@@ -1,260 +0,0 @@
1
- import type { CalendarObjectItem } from "@remit/data-ports";
2
- import { ZoneCertainty } from "@remit/domain-enums";
3
- import ICAL from "ical.js";
4
-
5
- /**
6
- * A resolved iCalendar time, in the two forms the store keeps.
7
- *
8
- * `isoUtc` is what a sort key holds: fixed width, so lexicographic order is
9
- * chronological order. `isoOffset` is what a client displays: the event's own
10
- * wall time with the offset it was written in, which `isoUtc` cannot express
11
- * for an all-day event without turning a civil date into an instant.
12
- *
13
- * `certainty` says how much either of them is worth. A zone that could not be
14
- * resolved still produces an instant — there is nothing else to produce — and
15
- * without this marker that guess is indistinguishable from a fact.
16
- */
17
- export interface ResolvedTime {
18
- instantMs: number;
19
- isoUtc: string;
20
- isoOffset: string;
21
- isDate: boolean;
22
- certainty: CalendarObjectItem["zoneCertainty"];
23
- }
24
-
25
- const pad = (value: number, width = 2): string =>
26
- String(Math.abs(value)).padStart(width, "0");
27
-
28
- const civilOf = (
29
- time: ICAL.Time,
30
- ): {
31
- year: number;
32
- month: number;
33
- day: number;
34
- hour: number;
35
- minute: number;
36
- second: number;
37
- } => ({
38
- year: time.year,
39
- month: time.month,
40
- day: time.day,
41
- hour: time.isDate ? 0 : time.hour,
42
- minute: time.isDate ? 0 : time.minute,
43
- second: time.isDate ? 0 : time.second,
44
- });
45
-
46
- /**
47
- * The offset an IANA zone was at on a given instant, in minutes.
48
- *
49
- * Read from the platform's own zone database through `Intl` rather than from a
50
- * bundled copy: ical.js ships no zone data, and a resource whose TZID names a
51
- * zone it carries no VTIMEZONE for is otherwise unresolvable — which is how a
52
- * DST-crossing series silently drifts by an hour.
53
- */
54
- const zoneOffsetMinutes = (timeZone: string, instantMs: number): number => {
55
- const parts = new Intl.DateTimeFormat("en-US", {
56
- timeZone,
57
- hourCycle: "h23",
58
- year: "numeric",
59
- month: "2-digit",
60
- day: "2-digit",
61
- hour: "2-digit",
62
- minute: "2-digit",
63
- second: "2-digit",
64
- }).formatToParts(new Date(instantMs));
65
- const field = (type: string): number =>
66
- Number(parts.find((part) => part.type === type)?.value ?? "0");
67
- const asUtc = Date.UTC(
68
- field("year"),
69
- field("month") - 1,
70
- field("day"),
71
- field("hour"),
72
- field("minute"),
73
- field("second"),
74
- );
75
- return (asUtc - instantMs) / 60_000;
76
- };
77
-
78
- /**
79
- * The zones this platform can be asked about.
80
- *
81
- * Read once from the runtime rather than probed per value: an unusable TZID —
82
- * a Windows zone name, a typo — makes `Intl` throw, and a resource written by a
83
- * client with a zone we cannot name is a resource to read in the next fallback,
84
- * never a crashed write. The list is the canonical IANA set, so a deprecated
85
- * alias falls through to the collection's own zone.
86
- */
87
- const KNOWN_ZONES = new Set(Intl.supportedValuesOf("timeZone"));
88
-
89
- const isKnownZone = (timeZone: string): boolean => KNOWN_ZONES.has(timeZone);
90
-
91
- /**
92
- * The offset a civil wall time carries in a zone. Applied twice because the
93
- * first lookup asks the zone about the wrong instant — the wall time read as if
94
- * it were UTC — which lands in the wrong side of a DST transition for wall
95
- * times within an hour or so of the cutover.
96
- */
97
- const offsetForCivil = (
98
- timeZone: string,
99
- civil: ReturnType<typeof civilOf>,
100
- ): number => {
101
- const asUtc = Date.UTC(
102
- civil.year,
103
- civil.month - 1,
104
- civil.day,
105
- civil.hour,
106
- civil.minute,
107
- civil.second,
108
- );
109
- const firstPass = zoneOffsetMinutes(timeZone, asUtc);
110
- return zoneOffsetMinutes(timeZone, asUtc - firstPass * 60_000);
111
- };
112
-
113
- const formatOffset = (offsetMinutes: number): string => {
114
- const sign = offsetMinutes < 0 ? "-" : "+";
115
- return `${sign}${pad(Math.trunc(offsetMinutes / 60))}:${pad(offsetMinutes % 60)}`;
116
- };
117
-
118
- const formatCivil = (
119
- civil: ReturnType<typeof civilOf>,
120
- offsetMinutes: number,
121
- ): string =>
122
- `${pad(civil.year, 4)}-${pad(civil.month)}-${pad(civil.day)}T${pad(civil.hour)}:${pad(civil.minute)}:${pad(civil.second)}${formatOffset(offsetMinutes)}`;
123
-
124
- export const toUtcIso = (instantMs: number): string =>
125
- `${new Date(instantMs).toISOString().slice(0, 19)}Z`;
126
-
127
- /** Whether this platform can be asked about a zone by name. */
128
- export const isResolvableZone = (timeZone: string): boolean =>
129
- isKnownZone(timeZone);
130
-
131
- /**
132
- * The wall-clock fields an instant reads as in a zone, with the offset that
133
- * zone was at. The zone falls back to UTC when it is one this platform cannot
134
- * name, which is the same fallback `resolveTime` takes.
135
- */
136
- export const civilInZone = (
137
- instantMs: number,
138
- timeZone: string,
139
- ): {
140
- year: number;
141
- month: number;
142
- day: number;
143
- hour: number;
144
- minute: number;
145
- second: number;
146
- offsetMinutes: number;
147
- } => {
148
- const zone = isKnownZone(timeZone) ? timeZone : "UTC";
149
- const offsetMinutes = zoneOffsetMinutes(zone, instantMs);
150
- const shifted = new Date(instantMs + offsetMinutes * 60_000);
151
- return {
152
- year: shifted.getUTCFullYear(),
153
- month: shifted.getUTCMonth() + 1,
154
- day: shifted.getUTCDate(),
155
- hour: shifted.getUTCHours(),
156
- minute: shifted.getUTCMinutes(),
157
- second: shifted.getUTCSeconds(),
158
- offsetMinutes,
159
- };
160
- };
161
-
162
- /**
163
- * An instant as the wall time it reads as in a zone, carrying that zone's
164
- * offset — the form a client renders.
165
- *
166
- * The store keeps occurrences as UTC instants because only a fixed-width form
167
- * sorts correctly, and a client that was handed those would have to re-derive
168
- * the event's own zone to draw it anywhere. This is the other direction, done
169
- * once on the server: an occurrence of a 09:00 Amsterdam meeting comes back as
170
- * `09:00+02:00` in summer and `09:00+01:00` in winter rather than as two
171
- * different times of day.
172
- */
173
- export const toOffsetIso = (instantMs: number, timeZone: string): string => {
174
- const civil = civilInZone(instantMs, timeZone);
175
- return formatCivil(civil, civil.offsetMinutes);
176
- };
177
-
178
- /**
179
- * Resolves one iCalendar time to an instant and to its own wall-clock form.
180
- *
181
- * The zone is taken from the value itself when ical.js could bind one — a
182
- * VTIMEZONE defined in this very resource, or UTC. A floating value, and a
183
- * value whose TZID has no VTIMEZONE, falls back to `tzidHint` (the property's
184
- * own TZID parameter) and then to the collection's timezone, so a resource
185
- * written by a client that assumes the server knows the zone database still
186
- * lands on the right instant.
187
- *
188
- * Which of those happened is reported, never swallowed: a named zone that no
189
- * fallback could resolve comes back `Ambiguous`, so a caller can say the event
190
- * may be hours out instead of drawing it in the wrong place in silence.
191
- */
192
- export const resolveTime = (
193
- time: ICAL.Time,
194
- tzidHint: string,
195
- collectionTimezone: string,
196
- ): ResolvedTime => {
197
- const civil = civilOf(time);
198
-
199
- let offsetMinutes: number;
200
- let certainty: ResolvedTime["certainty"];
201
- if (time.zone === ICAL.Timezone.utcTimezone) {
202
- offsetMinutes = 0;
203
- certainty = ZoneCertainty.Explicit;
204
- } else if (time.zone !== ICAL.Timezone.localTimezone) {
205
- offsetMinutes = time.utcOffset() / 60;
206
- certainty = ZoneCertainty.Explicit;
207
- } else {
208
- const fallback = [tzidHint, collectionTimezone].find(isKnownZone) ?? "UTC";
209
- offsetMinutes = offsetForCivil(fallback, civil);
210
- if (tzidHint === "") {
211
- // No zone was ever named. RFC 5545 floating time, read where the
212
- // collection lives.
213
- certainty = ZoneCertainty.Local;
214
- } else {
215
- certainty = isKnownZone(tzidHint)
216
- ? ZoneCertainty.Explicit
217
- : ZoneCertainty.Ambiguous;
218
- }
219
- }
220
-
221
- const instantMs =
222
- Date.UTC(
223
- civil.year,
224
- civil.month - 1,
225
- civil.day,
226
- civil.hour,
227
- civil.minute,
228
- civil.second,
229
- ) -
230
- offsetMinutes * 60_000;
231
-
232
- return {
233
- instantMs,
234
- isoUtc: toUtcIso(instantMs),
235
- isoOffset: formatCivil(civil, offsetMinutes),
236
- isDate: time.isDate,
237
- certainty,
238
- };
239
- };
240
-
241
- /** The TZID a property was written with, or `""` when it names none. */
242
- export const tzidOf = (property: ICAL.Property | null): string => {
243
- const tzid = property?.getParameter("tzid");
244
- return typeof tzid === "string" ? tzid : "";
245
- };
246
-
247
- /** The zone a component's DTSTART was written in. */
248
- export const dtStartTzid = (component: ICAL.Component): string =>
249
- tzidOf(component.getFirstProperty("dtstart"));
250
-
251
- /**
252
- * The zone a component's end was written in. DTEND carries its own TZID and
253
- * need not match DTSTART's, so an end read with the start's zone silently
254
- * changes the event's length. Only a stated DTEND has a zone of its own: an end
255
- * derived from a duration is already in the start's zone.
256
- */
257
- export const dtEndTzid = (component: ICAL.Component): string =>
258
- component.hasProperty("dtend")
259
- ? tzidOf(component.getFirstProperty("dtend"))
260
- : dtStartTzid(component);