@procedures/ics 0.7.0 → 0.8.1

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/days.d.ts CHANGED
@@ -15,6 +15,17 @@ export declare function dateTime(date: string, label: string): number;
15
15
  * - A date that is not valid, or a result outside the years 0000 to 9999, throws a `RangeError`.
16
16
  */
17
17
  export declare function shiftDay(date: string, days: number): string;
18
+ /**
19
+ * This function gives the date `months` months after a date (`YYYY-MM-DD`). A negative number gives
20
+ * an earlier date. The number of months must be an integer.
21
+ *
22
+ * - When the day is not in the month of the result, the result is the last day of that month: one
23
+ * month after `2026-01-31` is `2026-02-28`.
24
+ * - For the arithmetic of `Date.UTC`, where the 31st goes into the next month, add the days to the
25
+ * 1st: `shiftDay(shiftMonth("2026-01-01", 1), 30)` is `2026-03-03`.
26
+ * - A date that is not valid, or a result outside the years 0000 to 9999, throws a `RangeError`.
27
+ */
28
+ export declare function shiftMonth(date: string, months: number): string;
18
29
  /**
19
30
  * This function gives the number of whole days from the date `from` to the date `to`
20
31
  * (`YYYY-MM-DD`). It is negative when `to` is before `from`, and 0 for the same date.
@@ -1 +1 @@
1
- {"version":3,"file":"days.d.ts","sourceRoot":"","sources":["../src/days.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO5D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wGAAwG;AACxG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;CAAO,GAAG,MAAM,CAG1I"}
1
+ {"version":3,"file":"days.d.ts","sourceRoot":"","sources":["../src/days.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO5D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAQD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAU/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wGAAwG;AACxG,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;CAAO,GAAG,MAAM,CAG1I"}
package/dist/days.js CHANGED
@@ -33,6 +33,33 @@ export function shiftDay(date, days) {
33
33
  throw new RangeError(`shiftDay: ${date} plus ${String(days)} days is not from the year 0000 to 9999`);
34
34
  return text.slice(0, 10);
35
35
  }
36
+ /** This function tells if a year has a 29th of February, in the Gregorian calendar (the year 0000 has one). */
37
+ const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
38
+ /** The number of days of each month of a year that is not a leap year. */
39
+ const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
40
+ /**
41
+ * This function gives the date `months` months after a date (`YYYY-MM-DD`). A negative number gives
42
+ * an earlier date. The number of months must be an integer.
43
+ *
44
+ * - When the day is not in the month of the result, the result is the last day of that month: one
45
+ * month after `2026-01-31` is `2026-02-28`.
46
+ * - For the arithmetic of `Date.UTC`, where the 31st goes into the next month, add the days to the
47
+ * 1st: `shiftDay(shiftMonth("2026-01-01", 1), 30)` is `2026-03-03`.
48
+ * - A date that is not valid, or a result outside the years 0000 to 9999, throws a `RangeError`.
49
+ */
50
+ export function shiftMonth(date, months) {
51
+ if (!Number.isSafeInteger(months))
52
+ throw new RangeError(`shiftMonth: the number of months ${String(months)} is not an integer`);
53
+ dateTime(date, "shiftMonth");
54
+ const total = Number(date.slice(0, 4)) * 12 + Number(date.slice(5, 7)) - 1 + months;
55
+ const year = Math.floor(total / 12);
56
+ if (!(year >= 0 && year <= 9999))
57
+ throw new RangeError(`shiftMonth: ${date} plus ${String(months)} months is not from the year 0000 to 9999`);
58
+ const month = total - year * 12;
59
+ const last = month === 1 && isLeapYear(year) ? 29 : MONTH_DAYS[month];
60
+ const day = Math.min(Number(date.slice(8, 10)), last);
61
+ return `${String(year).padStart(4, "0")}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
62
+ }
36
63
  /**
37
64
  * This function gives the number of whole days from the date `from` to the date `to`
38
65
  * (`YYYY-MM-DD`). It is negative when `to` is before `from`, and 0 for the same date.
package/dist/days.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"days.js","sourceRoot":"","sources":["../src/days.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,GAAG,GAAG,UAAU,CAAC;AAEvB,0BAA0B;AAC1B,MAAM,IAAI,GAAG,qBAAqB,CAAC;AAEnC;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAa;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IAC5E,gGAAgG;IAChG,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7E,MAAM,IAAI,UAAU,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,IAAY;IACjD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,gCAAgC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACxH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACpG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,aAAa,IAAI,SAAS,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACjI,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,EAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,GAAG,GAAG,CAAC;AAC7E,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAO,GAA+E,EAAE;IAChI,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IACrD,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAChI,CAAC"}
1
+ {"version":3,"file":"days.js","sourceRoot":"","sources":["../src/days.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@ export * from "./procedures.js";
3
3
  export * from "./ics.js";
4
4
  export * from "./write.js";
5
5
  export { localDate, localTime, zonedOffset } from "./zone.js";
6
- export { daysBetween, shiftDay, weekdayName, weekdayOf } from "./days.js";
6
+ export { daysBetween, shiftDay, shiftMonth, weekdayName, weekdayOf } from "./days.js";
7
7
  export * from "./types.js";
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC1E,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtF,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,6 @@ export * from "./ics.js";
5
5
  export * from "./write.js";
6
6
  // The calendar days: the date of an instant in a zone, and the arithmetic of dates
7
7
  export { localDate, localTime, zonedOffset } from "./zone.js";
8
- export { daysBetween, shiftDay, weekdayName, weekdayOf } from "./days.js";
8
+ export { daysBetween, shiftDay, shiftMonth, weekdayName, weekdayOf } from "./days.js";
9
9
  export * from "./types.js";
10
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,mFAAmF;AACnF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC1E,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,mFAAmF;AACnF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtF,cAAc,YAAY,CAAC"}
@@ -5,11 +5,23 @@ import { type AnyProcedure } from "@procedures/client";
5
5
  export declare const icsParseProcedure: AnyProcedure;
6
6
  export declare const icsOccurrencesProcedure: AnyProcedure;
7
7
  export declare const icsWriteProcedure: AnyProcedure;
8
+ /** The part of `fetch` that `fetchCalendar` uses. A test or a host gives its own with the option `fetch`. */
9
+ export type FetchText = (url: string, init: {
10
+ headers: Record<string, string>;
11
+ signal: AbortSignal;
12
+ }) => Promise<{
13
+ ok: boolean;
14
+ status: number;
15
+ text(): Promise<string>;
16
+ }>;
8
17
  /**
9
18
  * This function gets the text of a calendar feed. The address of a private feed is a secret, so
10
- * an error never includes it.
19
+ * an error never includes it. An error of the request includes its system error code. The option
20
+ * `fetch` replaces the global `fetch`.
11
21
  */
12
- export declare function fetchCalendar(url: string, timeoutMs: number, signal?: AbortSignal): Promise<string>;
22
+ export declare function fetchCalendar(url: string, timeoutMs: number, signal?: AbortSignal, options?: {
23
+ readonly fetch?: FetchText | undefined;
24
+ }): Promise<string>;
13
25
  export declare const icsFetchProcedure: AnyProcedure;
14
26
  export declare const icsProcedures: AnyProcedure[];
15
27
  //# sourceMappingURL=procedures.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AA+C3G,eAAO,MAAM,iBAAiB,EAAE,YAUN,CAAC;AAiB3B,eAAO,MAAM,uBAAuB,EAAE,YAcZ,CAAC;AAM3B,eAAO,MAAM,iBAAiB,EAAE,YAUN,CAAC;AAY3B;;;GAGG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAezG;AAED,eAAO,MAAM,iBAAiB,EAAE,YAaN,CAAC;AAE3B,eAAO,MAAM,aAAa,EAAE,YAAY,EAAuF,CAAC"}
1
+ {"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AA+C3G,eAAO,MAAM,iBAAiB,EAAE,YAUN,CAAC;AAiB3B,eAAO,MAAM,uBAAuB,EAAE,YAcZ,CAAC;AAM3B,eAAO,MAAM,iBAAiB,EAAE,YAUN,CAAC;AAY3B,6GAA6G;AAC7G,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,KAC3D,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAgBvE;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAO,GACvD,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED,eAAO,MAAM,iBAAiB,EAAE,YAaN,CAAC;AAE3B,eAAO,MAAM,aAAa,EAAE,YAAY,EAAuF,CAAC"}
@@ -91,18 +91,35 @@ const FetchInputSchema = z.object({
91
91
  url: z.url().describe("The address of the calendar feed. A private feed address is a secret: no error repeats it."),
92
92
  timeoutMs: z.number().int().positive().max(120_000).default(20_000).describe("The time limit, in milliseconds"),
93
93
  });
94
+ /**
95
+ * The system error code of a failed request (`ECONNREFUSED`, `ENOTFOUND`, `UND_ERR_SOCKET`), or
96
+ * undefined. The `fetch` of Node puts it in the field `cause` of its `TypeError`. The function
97
+ * gives only a code of capital letters, digits and underscores, never a message, because a
98
+ * message can include the secret address.
99
+ */
100
+ function errorCodeOf(error) {
101
+ for (const each of [error?.cause, error]) {
102
+ const code = each?.code;
103
+ if (typeof code === "string" && /^[A-Z][A-Z0-9_]{1,40}$/.test(code))
104
+ return code;
105
+ }
106
+ return undefined;
107
+ }
94
108
  /**
95
109
  * This function gets the text of a calendar feed. The address of a private feed is a secret, so
96
- * an error never includes it.
110
+ * an error never includes it. An error of the request includes its system error code. The option
111
+ * `fetch` replaces the global `fetch`.
97
112
  */
98
- export async function fetchCalendar(url, timeoutMs, signal) {
113
+ export async function fetchCalendar(url, timeoutMs, signal, options = {}) {
99
114
  const signals = signal ? [signal, AbortSignal.timeout(timeoutMs)] : [AbortSignal.timeout(timeoutMs)];
115
+ const fetchText = options.fetch ?? fetch;
100
116
  let response;
101
117
  try {
102
- response = await fetch(url, { signal: AbortSignal.any(signals), headers: { accept: "text/calendar, */*" } });
118
+ response = await fetchText(url, { signal: AbortSignal.any(signals), headers: { accept: "text/calendar, */*" } });
103
119
  }
104
120
  catch (error) {
105
- const reason = error.name === "TimeoutError" ? `no answer in ${timeoutMs} ms` : "the request failed";
121
+ const code = errorCodeOf(error);
122
+ const reason = error.name === "TimeoutError" ? `no answer in ${timeoutMs} ms` : `the request failed${code === undefined ? "" : ` (${code})`}`;
106
123
  throw new Error(`The calendar feed did not answer: ${reason}`);
107
124
  }
108
125
  if (!response.ok)
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@procedures/ics",
4
- "version": "0.7.0",
4
+ "version": "0.8.1",
5
5
  "description": "An iCalendar (RFC 5545) reader and writer without dependencies: the events of a calendar, their occurrences in a time window with the recurrences, and a calendar from events.",
6
6
  "keywords": [
7
7
  "ics",
@@ -59,7 +59,7 @@
59
59
  "provenance": true
60
60
  },
61
61
  "dependencies": {
62
- "@procedures/client": "0.7.0",
62
+ "@procedures/client": "0.8.1",
63
63
  "zod": "^4.6.5"
64
64
  },
65
65
  "devDependencies": {
package/src/days.ts CHANGED
@@ -36,6 +36,34 @@ export function shiftDay(date: string, days: number): string {
36
36
  return text.slice(0, 10);
37
37
  }
38
38
 
39
+ /** This function tells if a year has a 29th of February, in the Gregorian calendar (the year 0000 has one). */
40
+ const isLeapYear = (year: number): boolean => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
41
+
42
+ /** The number of days of each month of a year that is not a leap year. */
43
+ const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] as const;
44
+
45
+ /**
46
+ * This function gives the date `months` months after a date (`YYYY-MM-DD`). A negative number gives
47
+ * an earlier date. The number of months must be an integer.
48
+ *
49
+ * - When the day is not in the month of the result, the result is the last day of that month: one
50
+ * month after `2026-01-31` is `2026-02-28`.
51
+ * - For the arithmetic of `Date.UTC`, where the 31st goes into the next month, add the days to the
52
+ * 1st: `shiftDay(shiftMonth("2026-01-01", 1), 30)` is `2026-03-03`.
53
+ * - A date that is not valid, or a result outside the years 0000 to 9999, throws a `RangeError`.
54
+ */
55
+ export function shiftMonth(date: string, months: number): string {
56
+ if (!Number.isSafeInteger(months)) throw new RangeError(`shiftMonth: the number of months ${String(months)} is not an integer`);
57
+ dateTime(date, "shiftMonth");
58
+ const total = Number(date.slice(0, 4)) * 12 + Number(date.slice(5, 7)) - 1 + months;
59
+ const year = Math.floor(total / 12);
60
+ if (!(year >= 0 && year <= 9999)) throw new RangeError(`shiftMonth: ${date} plus ${String(months)} months is not from the year 0000 to 9999`);
61
+ const month = total - year * 12;
62
+ const last = month === 1 && isLeapYear(year) ? 29 : MONTH_DAYS[month]!;
63
+ const day = Math.min(Number(date.slice(8, 10)), last);
64
+ return `${String(year).padStart(4, "0")}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
65
+ }
66
+
39
67
  /**
40
68
  * This function gives the number of whole days from the date `from` to the date `to`
41
69
  * (`YYYY-MM-DD`). It is negative when `to` is before `from`, and 0 for the same date.
package/src/index.ts CHANGED
@@ -5,5 +5,5 @@ export * from "./ics.js";
5
5
  export * from "./write.js";
6
6
  // The calendar days: the date of an instant in a zone, and the arithmetic of dates
7
7
  export { localDate, localTime, zonedOffset } from "./zone.js";
8
- export { daysBetween, shiftDay, weekdayName, weekdayOf } from "./days.js";
8
+ export { daysBetween, shiftDay, shiftMonth, weekdayName, weekdayOf } from "./days.js";
9
9
  export * from "./types.js";
package/src/procedures.ts CHANGED
@@ -118,17 +118,46 @@ const FetchInputSchema = z.object({
118
118
  });
119
119
  type FetchInput = z.infer<typeof FetchInputSchema>;
120
120
 
121
+ /** The part of `fetch` that `fetchCalendar` uses. A test or a host gives its own with the option `fetch`. */
122
+ export type FetchText = (
123
+ url: string,
124
+ init: { headers: Record<string, string>; signal: AbortSignal }
125
+ ) => Promise<{ ok: boolean; status: number; text(): Promise<string> }>;
126
+
127
+ /**
128
+ * The system error code of a failed request (`ECONNREFUSED`, `ENOTFOUND`, `UND_ERR_SOCKET`), or
129
+ * undefined. The `fetch` of Node puts it in the field `cause` of its `TypeError`. The function
130
+ * gives only a code of capital letters, digits and underscores, never a message, because a
131
+ * message can include the secret address.
132
+ */
133
+ function errorCodeOf(error: unknown): string | undefined {
134
+ for (const each of [(error as { cause?: unknown } | null)?.cause, error]) {
135
+ const code = (each as { code?: unknown } | null | undefined)?.code;
136
+ if (typeof code === "string" && /^[A-Z][A-Z0-9_]{1,40}$/.test(code)) return code;
137
+ }
138
+ return undefined;
139
+ }
140
+
121
141
  /**
122
142
  * This function gets the text of a calendar feed. The address of a private feed is a secret, so
123
- * an error never includes it.
143
+ * an error never includes it. An error of the request includes its system error code. The option
144
+ * `fetch` replaces the global `fetch`.
124
145
  */
125
- export async function fetchCalendar(url: string, timeoutMs: number, signal?: AbortSignal): Promise<string> {
146
+ export async function fetchCalendar(
147
+ url: string,
148
+ timeoutMs: number,
149
+ signal?: AbortSignal,
150
+ options: { readonly fetch?: FetchText | undefined } = {}
151
+ ): Promise<string> {
126
152
  const signals = signal ? [signal, AbortSignal.timeout(timeoutMs)] : [AbortSignal.timeout(timeoutMs)];
127
- let response: Response;
153
+ const fetchText: FetchText = options.fetch ?? fetch;
154
+ let response: Awaited<ReturnType<FetchText>>;
128
155
  try {
129
- response = await fetch(url, { signal: AbortSignal.any(signals), headers: { accept: "text/calendar, */*" } });
156
+ response = await fetchText(url, { signal: AbortSignal.any(signals), headers: { accept: "text/calendar, */*" } });
130
157
  } catch (error) {
131
- const reason = (error as { name?: string }).name === "TimeoutError" ? `no answer in ${timeoutMs} ms` : "the request failed";
158
+ const code = errorCodeOf(error);
159
+ const reason =
160
+ (error as { name?: string }).name === "TimeoutError" ? `no answer in ${timeoutMs} ms` : `the request failed${code === undefined ? "" : ` (${code})`}`;
132
161
  throw new Error(`The calendar feed did not answer: ${reason}`);
133
162
  }
134
163
  if (!response.ok) throw new Error(`The calendar feed gave HTTP ${response.status}`);