calendaryjs 0.2.1 β 0.2.3
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/README.md +109 -131
- package/assets/logo.svg +17 -0
- package/dist/builder/index.cjs +338 -0
- package/dist/builder/index.d.cts +153 -0
- package/dist/builder/index.d.ts +153 -0
- package/dist/builder/index.js +326 -0
- package/dist/events-Bm7R9XFB.d.cts +590 -0
- package/dist/events-Bm7R9XFB.d.ts +590 -0
- package/dist/index.d.cts +3 -589
- package/dist/index.d.ts +3 -589
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,186 +1,164 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://cdn.jsdelivr.net/npm/calendaryjs/assets/logo.svg" alt="calendaryjs" width="380" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
Declare recurring events like a sentence β and teach the engine <b>any</b> calendar
|
|
7
|
+
system (lunar, hijri, liturgical⦠or your own) with a plugin.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
- π **Recurrence engine** β weekly, monthly, nth-weekday, formula, relative
|
|
11
|
+
- π **Pluggable calendar systems** β one engine, every calendar
|
|
12
|
+
- βοΈ **Fluent builder** β `every("week").on("mon", "wed", "fri")`
|
|
13
|
+
- π **Query API** β filter events across groups & date ranges
|
|
14
|
+
- π§© **Collections** (`.cdy`) + **ICS export** β plain, serializable data
|
|
15
|
+
- πͺΆ Zero-dependency Β· ~5KB Β· browser Β· Node Β· edge Β· RN
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
**calendaryjs turns event _rules_ into dated _occurrences_.** You **declare** events
|
|
20
|
+
(Christmas, every other Tuesday, the 2nd Sunday of Mayβ¦), the engine **expands** their
|
|
21
|
+
recurrence into concrete dates, and you **read, query, or export** the result. The core
|
|
22
|
+
speaks the Gregorian calendar; **plugins** teach it others β lunar, hijri, liturgical,
|
|
23
|
+
or your own.
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
declare (builder) β expand (engine) β read Β· query Β· export
|
|
27
|
+
```
|
|
12
28
|
|
|
13
|
-
##
|
|
29
|
+
## Install
|
|
14
30
|
|
|
15
31
|
```bash
|
|
16
|
-
|
|
32
|
+
npm i calendaryjs
|
|
17
33
|
```
|
|
18
34
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
### Factory API (Recommended)
|
|
35
|
+
## 1 Β· Your first event
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
plain config shown under [Collections](#collections):
|
|
37
|
+
Group events, then read them back as dated occurrences:
|
|
25
38
|
|
|
26
|
-
```
|
|
39
|
+
```ts
|
|
27
40
|
import { calendary } from "calendaryjs";
|
|
28
41
|
import { every, date } from "calendaryjs/builder";
|
|
29
42
|
|
|
30
43
|
const cal = calendary();
|
|
31
|
-
|
|
32
44
|
cal.addGroup({
|
|
33
45
|
id: "holidays",
|
|
34
|
-
|
|
35
|
-
events: [
|
|
36
|
-
every("year").on(date(1, 1)).title("New Year"),
|
|
37
|
-
every("year").on(date(12, 25)).title("Christmas"),
|
|
38
|
-
],
|
|
46
|
+
events: [every("year").on(date(12, 25)).title("Christmas")],
|
|
39
47
|
});
|
|
40
48
|
|
|
41
|
-
// Read events
|
|
42
49
|
cal.getEvents("2026-12-25");
|
|
43
|
-
|
|
50
|
+
// β [{ title: "Christmas", date: "2026-12-25", type: "const", β¦ }]
|
|
51
|
+
```
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
## 2 Β· Recurring events β the builder
|
|
54
|
+
|
|
55
|
+
Every declaration reads like a sentence β **frequency β position β payload** β and
|
|
56
|
+
compiles to a plain, serializable config:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { every, once, date, nth } from "calendaryjs/builder";
|
|
60
|
+
|
|
61
|
+
every("year").on(date(1, 1)).title("New Year"); // Jan 1, every year
|
|
62
|
+
every("year")
|
|
63
|
+
.on(nth(4, "thursday", "november"))
|
|
64
|
+
.title("Thanksgiving");
|
|
65
|
+
every(2, "weeks").on("tuesday").title("Standup"); // every other Tuesday
|
|
66
|
+
every("week").on("monday", "wednesday", "friday").title("Gym"); // pick weekdays
|
|
67
|
+
once("2026-06-15").title("Wedding"); // a single date
|
|
47
68
|
```
|
|
48
69
|
|
|
49
|
-
>
|
|
50
|
-
>
|
|
51
|
-
>
|
|
70
|
+
> Prefer raw objects? The builder just `build()`s into a plain config β hand-write it
|
|
71
|
+
> if you like; both compile to the same model. Full grammar: the
|
|
72
|
+
> [builder guide](https://calendaryjs.dev/guides/builder/).
|
|
73
|
+
|
|
74
|
+
## 3 Β· Read & query
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
cal.getEvents("2026-12-25"); // a single day
|
|
78
|
+
cal.getEventsInRange("2026-01-01", "2026-12-31"); // a range
|
|
79
|
+
cal.search().group("holidays").range("2026-01-01", "2026-12-31").getEvents();
|
|
80
|
+
```
|
|
52
81
|
|
|
53
|
-
|
|
82
|
+
## 4 Β· Add a calendar system
|
|
54
83
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
first β `load()` validates them and errors clearly if any is missing.
|
|
84
|
+
A plugin extends the engine **and** the builder's vocabulary β install it, `use()` it,
|
|
85
|
+
and new event types become available:
|
|
58
86
|
|
|
59
|
-
```
|
|
60
|
-
import { calendary } from "calendaryjs";
|
|
87
|
+
```ts
|
|
61
88
|
import { lunar } from "calendaryjs-plugin-lunar";
|
|
62
89
|
|
|
63
90
|
const cal = calendary().use(lunar());
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
plugins: ["calendaryjs-plugin-lunar"],
|
|
68
|
-
events: [
|
|
69
|
-
{ type: "const", id: "new-year", month: 1, day: 1, title: "New Year" },
|
|
70
|
-
{ type: "lunar", id: "lunar-new-year", lunarMonth: 1, lunarDay: 1, title: "Lunar New Year" },
|
|
71
|
-
],
|
|
91
|
+
cal.addGroup({
|
|
92
|
+
id: "lunar",
|
|
93
|
+
events: [every("year").on(lunar.date(1, 1)).title("Lunar New Year")],
|
|
72
94
|
});
|
|
73
|
-
|
|
74
|
-
// β¦or from a .cdy (JSON) string, e.g. read from disk or fetched
|
|
75
|
-
cal.load(jsonString);
|
|
76
95
|
```
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
| Plugin | Adds |
|
|
98
|
+
| -------------------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
99
|
+
| [calendaryjs-plugin-lunar](https://www.npmjs.com/package/calendaryjs-plugin-lunar) | Lunar (lunisolar) date conversion + events |
|
|
100
|
+
| [calendaryjs-plugin-hijri](https://www.npmjs.com/package/calendaryjs-plugin-hijri) | Islamic (Hijri) date conversion + events |
|
|
101
|
+
| [calendaryjs-plugin-liturgical](https://www.npmjs.com/package/calendaryjs-plugin-liturgical) | Easter computus, movable feasts, seasons |
|
|
79
102
|
|
|
80
|
-
|
|
103
|
+
## 5 Β· Bundle & share β collections
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
Bundle events into a portable **collection** (a `.cdy` file is its JSON form) and load it
|
|
106
|
+
with `cal.load()`. It declares the plugins its events need; register those first β
|
|
107
|
+
`load()` validates them and errors clearly if any is missing.
|
|
84
108
|
|
|
85
|
-
|
|
109
|
+
```ts
|
|
110
|
+
const cal = calendary().use(lunar());
|
|
86
111
|
|
|
87
|
-
cal.
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
cal.load({
|
|
113
|
+
collection: "holidays",
|
|
114
|
+
plugins: ["calendaryjs-plugin-lunar"],
|
|
90
115
|
events: [
|
|
91
|
-
{ type: "const", id: "
|
|
92
|
-
{ type: "
|
|
116
|
+
{ type: "const", id: "new-year", month: 1, day: 1, title: "New Year" },
|
|
117
|
+
{ type: "lunar", id: "tet", lunarMonth: 1, lunarDay: 1, title: "Lunar New Year" },
|
|
93
118
|
],
|
|
94
119
|
});
|
|
120
|
+
|
|
121
|
+
cal.load(jsonString); // β¦or from a .cdy (JSON) string, read from disk or fetched
|
|
95
122
|
```
|
|
96
123
|
|
|
97
|
-
##
|
|
124
|
+
## Reference
|
|
125
|
+
|
|
126
|
+
### Event types
|
|
98
127
|
|
|
99
128
|
| Type | Description | Example |
|
|
100
129
|
| ------------- | ------------------------------------------ | ---------------------------- |
|
|
101
130
|
| `const` | Fixed annual date | Christmas (Dec 25) |
|
|
102
131
|
| `fixed` | One-time date | Wedding (Jun 15, 2025) |
|
|
103
132
|
| `monthly` | Same day every month (interval/exclusions) | Payday (15th) |
|
|
104
|
-
| `weekly` |
|
|
133
|
+
| `weekly` | One or more weekdays (interval/range) | Standup (Mon, Wed, Fri) |
|
|
105
134
|
| `nth-weekday` | Nth weekday of a month, or anchored | Thanksgiving (4th Thu / Nov) |
|
|
106
135
|
| `formula` | Custom formula | Last Monday of May |
|
|
107
136
|
| `relative` | Offset from a registered anchor | 49 days after an anchor |
|
|
108
137
|
|
|
109
138
|
Plugins add more types (e.g. `lunar`, `hijri`, `liturgical`).
|
|
110
139
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
import { calendary } from "calendaryjs";
|
|
115
|
-
|
|
116
|
-
// Extend with custom functionality
|
|
117
|
-
calendary.extend((option, C, d) => {
|
|
118
|
-
C.prototype.myMethod = function () {
|
|
119
|
-
return "custom method";
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Use plugins
|
|
124
|
-
import { lunar } from "calendaryjs-plugin-lunar";
|
|
125
|
-
|
|
126
|
-
const cal = calendary();
|
|
127
|
-
cal.use(lunar());
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Event Properties
|
|
140
|
+
### Event properties
|
|
131
141
|
|
|
132
|
-
Every event shares `BaseEventProperties`
|
|
142
|
+
Every event shares `BaseEventProperties` plus its type-specific date fields.
|
|
133
143
|
**`id`, `type`, and `title` are required; everything else is optional.**
|
|
134
144
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
| `allDay` | `boolean` | β |
|
|
153
|
-
| `startTime` | `string` | `"09:00"` |
|
|
154
|
-
| `endTime` | `string` | `"17:00"` |
|
|
155
|
-
| `duration` | `number` | β |
|
|
156
|
-
|
|
157
|
-
### Classification
|
|
158
|
-
|
|
159
|
-
| Property | Type | Notes |
|
|
160
|
-
| ------------ | ------------------------------------------- | ---------------------------------------------------- |
|
|
161
|
-
| `keywords` | `string[]` | β |
|
|
162
|
-
| `categories` | `string[]` | β |
|
|
163
|
-
| `status` | `'confirmed' \| 'tentative' \| 'cancelled'` | β |
|
|
164
|
-
| `priority` | `number` | z-index on a shared day; higher = on top (default 0) |
|
|
165
|
-
| `source` | `string` | origin: plugin / feed / ICS subscription |
|
|
166
|
-
| `metadata` | `Record<string, unknown>` | arbitrary per-event data |
|
|
167
|
-
| `reminders` | `Reminder[]` | β |
|
|
168
|
-
|
|
169
|
-
### Recurrence control
|
|
170
|
-
|
|
171
|
-
| Property | Type | Notes |
|
|
172
|
-
| --------------- | ------------------ | --------------------------------------------------------------- |
|
|
173
|
-
| `onConflict` | `ConflictResolver` | same-day conflict: keep / drop / reschedule |
|
|
174
|
-
| `overrideDates` | `OverrideDatesMap` | force-move an occurrence to another date |
|
|
175
|
-
| `exceptions` | `EventExceptions` | per-occurrence skip / override (ICS `EXDATE` / `RECURRENCE-ID`) |
|
|
176
|
-
|
|
177
|
-
## Plugins
|
|
178
|
-
|
|
179
|
-
| Package | Description |
|
|
180
|
-
| -------------------------------------------------------------------------------------------- | ---------------------- |
|
|
181
|
-
| [calendaryjs-plugin-lunar](https://www.npmjs.com/package/calendaryjs-plugin-lunar) | Lunar calendar events |
|
|
182
|
-
| [calendaryjs-plugin-liturgical](https://www.npmjs.com/package/calendaryjs-plugin-liturgical) | Easter & offset events |
|
|
183
|
-
| [calendaryjs-plugin-hijri](https://www.npmjs.com/package/calendaryjs-plugin-hijri) | Islamic (Hijri) events |
|
|
145
|
+
| Property | Type | Notes |
|
|
146
|
+
| ---------------------------------- | ------------------------------------- | ---------------------------------------------------- |
|
|
147
|
+
| `id` Β· `type` Β· `title` | `string` | required |
|
|
148
|
+
| `description` Β· `location` Β· `url` | `string` | display metadata |
|
|
149
|
+
| `icon` Β· `color` | `string` | per-event motif |
|
|
150
|
+
| `allDay` | `boolean` | β |
|
|
151
|
+
| `startTime` Β· `endTime` | `string` | `"09:00"` β¦ `"17:00"` |
|
|
152
|
+
| `duration` | `number` | minutes |
|
|
153
|
+
| `keywords` Β· `categories` | `string[]` | classification |
|
|
154
|
+
| `status` | `confirmed \| tentative \| cancelled` | β |
|
|
155
|
+
| `priority` | `number` | z-index on a shared day; higher = on top (default 0) |
|
|
156
|
+
| `source` | `string` | origin: plugin / feed / ICS subscription |
|
|
157
|
+
| `metadata` | `Record<string, unknown>` | arbitrary per-event data |
|
|
158
|
+
| `reminders` | `Reminder[]` | lead-time offsets (β ICS `VALARM`) |
|
|
159
|
+
| `onConflict` | `ConflictResolver` | same-day conflict: keep / drop / reschedule |
|
|
160
|
+
| `overrideDates` | `OverrideDatesMap` | force-move an occurrence to another date |
|
|
161
|
+
| `exceptions` | `EventExceptions` | per-occurrence skip / override (ICS `EXDATE`) |
|
|
184
162
|
|
|
185
163
|
## License
|
|
186
164
|
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="560" height="128" viewBox="0 0 560 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="calendary.js">
|
|
2
|
+
<!-- light-theme variant: dark ink mark + text, amber accents -->
|
|
3
|
+
<g transform="translate(-8 0)">
|
|
4
|
+
<path d="M28 28H100V44H44V84H100V100H28V28Z" fill="#16120F" />
|
|
5
|
+
<rect x="72" y="56" width="16" height="16" rx="2" fill="#F59E0B" />
|
|
6
|
+
</g>
|
|
7
|
+
<text
|
|
8
|
+
x="116"
|
|
9
|
+
y="87"
|
|
10
|
+
font-family="'JetBrains Mono', 'DejaVu Sans Mono', ui-monospace, monospace"
|
|
11
|
+
font-weight="700"
|
|
12
|
+
font-size="60"
|
|
13
|
+
letter-spacing="-1"
|
|
14
|
+
>
|
|
15
|
+
<tspan fill="#16120F">calendary</tspan><tspan fill="#F59E0B">.js</tspan>
|
|
16
|
+
</text>
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/builder/builder.ts
|
|
4
|
+
function date(month, day) {
|
|
5
|
+
return { type: "const", fields: { month, day } };
|
|
6
|
+
}
|
|
7
|
+
var WEEKDAYS = {
|
|
8
|
+
sunday: 0,
|
|
9
|
+
monday: 1,
|
|
10
|
+
tuesday: 2,
|
|
11
|
+
wednesday: 3,
|
|
12
|
+
thursday: 4,
|
|
13
|
+
friday: 5,
|
|
14
|
+
saturday: 6
|
|
15
|
+
};
|
|
16
|
+
var MONTHS = {
|
|
17
|
+
jan: 1,
|
|
18
|
+
feb: 2,
|
|
19
|
+
mar: 3,
|
|
20
|
+
apr: 4,
|
|
21
|
+
may: 5,
|
|
22
|
+
jun: 6,
|
|
23
|
+
jul: 7,
|
|
24
|
+
aug: 8,
|
|
25
|
+
sep: 9,
|
|
26
|
+
oct: 10,
|
|
27
|
+
nov: 11,
|
|
28
|
+
dec: 12
|
|
29
|
+
};
|
|
30
|
+
function nth(n, weekday, month) {
|
|
31
|
+
const m = typeof month === "number" ? month : MONTHS[month];
|
|
32
|
+
if (!m) throw new Error(`unknown month "${month}"`);
|
|
33
|
+
return { type: "nth-weekday", fields: { nth: n, dayOfWeek: WEEKDAYS[weekday], month: m } };
|
|
34
|
+
}
|
|
35
|
+
var PLURAL = {
|
|
36
|
+
days: "day",
|
|
37
|
+
weeks: "week",
|
|
38
|
+
months: "month",
|
|
39
|
+
years: "year"
|
|
40
|
+
};
|
|
41
|
+
var BaseBuilder = class {
|
|
42
|
+
payload = {};
|
|
43
|
+
explicitId;
|
|
44
|
+
title(value) {
|
|
45
|
+
this.payload.title = value;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
id(value) {
|
|
49
|
+
this.explicitId = value;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
priority(value) {
|
|
53
|
+
this.payload.priority = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
description(value) {
|
|
57
|
+
this.payload.description = value;
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
location(value) {
|
|
61
|
+
this.payload.location = value;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
url(value) {
|
|
65
|
+
this.payload.url = value;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
color(value) {
|
|
69
|
+
this.payload.color = value;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
icon(value) {
|
|
73
|
+
this.payload.icon = value;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
source(value) {
|
|
77
|
+
this.payload.source = value;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
status(value) {
|
|
81
|
+
this.payload.status = value;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
allDay(value = true) {
|
|
85
|
+
this.payload.allDay = value;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/** Clock time(s): `.at("09:00")` or `.at("09:00", "10:30")`. */
|
|
89
|
+
at(start, end) {
|
|
90
|
+
this.payload.startTime = start;
|
|
91
|
+
if (end) this.payload.endTime = end;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
/** Duration in minutes. */
|
|
95
|
+
duration(minutes) {
|
|
96
|
+
this.payload.duration = minutes;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
keywords(...values) {
|
|
100
|
+
this.payload.keywords = values;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
categories(...values) {
|
|
104
|
+
this.payload.categories = values;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
/** Merge arbitrary metadata (later calls override earlier keys). */
|
|
108
|
+
metadata(data) {
|
|
109
|
+
const prev = this.payload.metadata ?? {};
|
|
110
|
+
this.payload.metadata = { ...prev, ...data };
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/** A lead-time reminder before the occurrence. Accumulates. */
|
|
114
|
+
remind(before, opts) {
|
|
115
|
+
const trigger = (before.minutes ?? 0) + (before.hours ?? 0) * 60 + (before.days ?? 0) * 1440;
|
|
116
|
+
const reminders = this.payload.reminders ?? [];
|
|
117
|
+
reminders.push({
|
|
118
|
+
type: opts?.type ?? "notification",
|
|
119
|
+
trigger,
|
|
120
|
+
...opts?.message ? { message: opts.message } : {}
|
|
121
|
+
});
|
|
122
|
+
this.payload.reminders = reminders;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
/** Drop one occurrence (by its original date) β ICS EXDATE. */
|
|
126
|
+
skip(date2) {
|
|
127
|
+
this.exceptions()[date2] = { skip: true };
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/** Replace properties for one occurrence β ICS RECURRENCE-ID. */
|
|
131
|
+
override(date2, props) {
|
|
132
|
+
this.exceptions()[date2] = { override: props };
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/** Force-move one occurrence to another date. */
|
|
136
|
+
reschedule(fromDate, toDate) {
|
|
137
|
+
const map = this.payload.overrideDates ?? {};
|
|
138
|
+
map[fromDate] = toDate;
|
|
139
|
+
this.payload.overrideDates = map;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
exceptions() {
|
|
143
|
+
const map = this.payload.exceptions ?? {};
|
|
144
|
+
this.payload.exceptions = map;
|
|
145
|
+
return map;
|
|
146
|
+
}
|
|
147
|
+
/** Explicit id, else a slug of the title, else throw. */
|
|
148
|
+
resolveId() {
|
|
149
|
+
const title = this.payload.title;
|
|
150
|
+
const id = this.explicitId ?? (title ? slug(title) : void 0);
|
|
151
|
+
if (!id) {
|
|
152
|
+
throw new Error("an event needs an .id(...), or a .title(...) to derive its id from");
|
|
153
|
+
}
|
|
154
|
+
return id;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var EventBuilder = class extends BaseBuilder {
|
|
158
|
+
constructor(unit, interval) {
|
|
159
|
+
super();
|
|
160
|
+
this.unit = unit;
|
|
161
|
+
this.interval = interval;
|
|
162
|
+
}
|
|
163
|
+
unit;
|
|
164
|
+
interval;
|
|
165
|
+
selectorType;
|
|
166
|
+
fields = {};
|
|
167
|
+
on(...selectors) {
|
|
168
|
+
if (selectors.length > 1 || typeof selectors[0] === "string") {
|
|
169
|
+
if (!selectors.every((s) => typeof s === "string")) {
|
|
170
|
+
throw new Error(`.on(...) lists weekdays together with non-weekday selectors`);
|
|
171
|
+
}
|
|
172
|
+
if (this.unit !== "week") {
|
|
173
|
+
throw new Error(`.on("${selectors[0]}") (a weekday) needs every("week")`);
|
|
174
|
+
}
|
|
175
|
+
const indices = selectors.map((w) => {
|
|
176
|
+
if (!(w in WEEKDAYS)) throw new Error(`unknown weekday "${w}"`);
|
|
177
|
+
return WEEKDAYS[w];
|
|
178
|
+
});
|
|
179
|
+
const unique = [...new Set(indices)].sort((a, b) => a - b);
|
|
180
|
+
this.selectorType = "weekly";
|
|
181
|
+
this.fields.dayOfWeek = unique.length === 1 ? unique[0] : unique;
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
const selector = selectors[0];
|
|
185
|
+
if (typeof selector === "number") {
|
|
186
|
+
if (this.unit !== "month") {
|
|
187
|
+
throw new Error(`.on(${selector}) (a day of month) needs every("month")`);
|
|
188
|
+
}
|
|
189
|
+
this.selectorType = "monthly";
|
|
190
|
+
this.fields.day = selector;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
const sel = selector;
|
|
194
|
+
if (this.unit !== "year") throw new Error(`.on(<selector>) needs every("year")`);
|
|
195
|
+
this.selectorType = sel.type;
|
|
196
|
+
Object.assign(this.fields, sel.fields);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
/** Bound the recurrence: numbers β year range, date strings β date range. */
|
|
200
|
+
between(start, end) {
|
|
201
|
+
if (typeof start === "number") {
|
|
202
|
+
this.fields.startYear = start;
|
|
203
|
+
this.fields.endYear = end;
|
|
204
|
+
} else {
|
|
205
|
+
this.fields.startDate = start;
|
|
206
|
+
this.fields.endDate = end;
|
|
207
|
+
}
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
/** Upper bound only: a number β endYear, a date string β endDate. */
|
|
211
|
+
until(end) {
|
|
212
|
+
if (typeof end === "number") this.fields.endYear = end;
|
|
213
|
+
else this.fields.endDate = end;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
exceptYears(...years) {
|
|
217
|
+
this.fields.excludeYears = years;
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
exceptMonths(...months) {
|
|
221
|
+
this.fields.excludeMonths = months;
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
exceptDates(...dates) {
|
|
225
|
+
this.fields.excludeDates = dates;
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
build() {
|
|
229
|
+
if (!this.selectorType) {
|
|
230
|
+
throw new Error(`every("${this.unit}") needs a position \u2014 call .on(...)`);
|
|
231
|
+
}
|
|
232
|
+
const config = {
|
|
233
|
+
type: this.selectorType,
|
|
234
|
+
id: this.resolveId(),
|
|
235
|
+
...this.fields,
|
|
236
|
+
...this.payload
|
|
237
|
+
};
|
|
238
|
+
if (this.interval > 1) {
|
|
239
|
+
if (this.selectorType !== "weekly" && this.selectorType !== "monthly") {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`every(${this.interval}, \u2026) is not supported for "${this.selectorType}" yet`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
config.interval = this.interval;
|
|
245
|
+
}
|
|
246
|
+
return config;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var RelativeBuilder = class extends BaseBuilder {
|
|
250
|
+
constructor(anchor) {
|
|
251
|
+
super();
|
|
252
|
+
this.anchor = anchor;
|
|
253
|
+
}
|
|
254
|
+
anchor;
|
|
255
|
+
days = 0;
|
|
256
|
+
weeks = 0;
|
|
257
|
+
plus(offset) {
|
|
258
|
+
this.days += offset.days ?? 0;
|
|
259
|
+
this.weeks += offset.weeks ?? 0;
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
minus(offset) {
|
|
263
|
+
this.days -= offset.days ?? 0;
|
|
264
|
+
this.weeks -= offset.weeks ?? 0;
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
build() {
|
|
268
|
+
const offset = {};
|
|
269
|
+
if (this.days) offset.days = this.days;
|
|
270
|
+
if (this.weeks) offset.weeks = this.weeks;
|
|
271
|
+
return {
|
|
272
|
+
type: "relative",
|
|
273
|
+
id: this.resolveId(),
|
|
274
|
+
anchor: this.anchor,
|
|
275
|
+
offset,
|
|
276
|
+
...this.payload
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var FixedBuilder = class extends BaseBuilder {
|
|
281
|
+
constructor(dateStr) {
|
|
282
|
+
super();
|
|
283
|
+
this.dateStr = dateStr;
|
|
284
|
+
}
|
|
285
|
+
dateStr;
|
|
286
|
+
build() {
|
|
287
|
+
const [year, month, day] = this.dateStr.split("-").map(Number);
|
|
288
|
+
if (!year || !month || !day) {
|
|
289
|
+
throw new Error(`once("${this.dateStr}") needs a YYYY-MM-DD date`);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
type: "fixed",
|
|
293
|
+
id: this.resolveId(),
|
|
294
|
+
year,
|
|
295
|
+
month,
|
|
296
|
+
day,
|
|
297
|
+
...this.payload
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
function every(a, b) {
|
|
302
|
+
if (typeof a === "number") {
|
|
303
|
+
const unit = PLURAL[b];
|
|
304
|
+
if (!unit) throw new Error(`unknown unit "${b}"`);
|
|
305
|
+
return new EventBuilder(unit, a);
|
|
306
|
+
}
|
|
307
|
+
return new EventBuilder(a, 1);
|
|
308
|
+
}
|
|
309
|
+
function from(anchor) {
|
|
310
|
+
return new RelativeBuilder(anchor);
|
|
311
|
+
}
|
|
312
|
+
function once(dateStr) {
|
|
313
|
+
return new FixedBuilder(dateStr);
|
|
314
|
+
}
|
|
315
|
+
function weekly(weekday, ...moreWeekdays) {
|
|
316
|
+
return every("week").on(weekday, ...moreWeekdays);
|
|
317
|
+
}
|
|
318
|
+
function monthly(day) {
|
|
319
|
+
return every("month").on(day);
|
|
320
|
+
}
|
|
321
|
+
function yearly(month, day) {
|
|
322
|
+
return every("year").on(date(month, day));
|
|
323
|
+
}
|
|
324
|
+
function slug(value) {
|
|
325
|
+
return value.toLowerCase().replace(/Δ/g, "d").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "event";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
exports.EventBuilder = EventBuilder;
|
|
329
|
+
exports.FixedBuilder = FixedBuilder;
|
|
330
|
+
exports.RelativeBuilder = RelativeBuilder;
|
|
331
|
+
exports.date = date;
|
|
332
|
+
exports.every = every;
|
|
333
|
+
exports.from = from;
|
|
334
|
+
exports.monthly = monthly;
|
|
335
|
+
exports.nth = nth;
|
|
336
|
+
exports.once = once;
|
|
337
|
+
exports.weekly = weekly;
|
|
338
|
+
exports.yearly = yearly;
|