calendaryjs 0.2.4 → 0.3.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/LICENSE +147 -21
- package/README.md +30 -11
- package/dist/builder/index.cjs +16 -2
- package/dist/builder/index.d.cts +12 -4
- package/dist/builder/index.d.ts +12 -4
- package/dist/builder/index.js +16 -3
- package/dist/{events-Bm7R9XFB.d.cts → events-DSiv9j6V.d.cts} +52 -8
- package/dist/{events-Bm7R9XFB.d.ts → events-DSiv9j6V.d.ts} +52 -8
- package/dist/index.cjs +147 -16
- package/dist/index.d.cts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +147 -16
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -84,6 +84,14 @@ function getYearsInRange(from, to) {
|
|
|
84
84
|
function createDate(year, month, day) {
|
|
85
85
|
return new Date(year, month - 1, day);
|
|
86
86
|
}
|
|
87
|
+
function getDaysInMonth(year, month) {
|
|
88
|
+
return new Date(year, month, 0).getDate();
|
|
89
|
+
}
|
|
90
|
+
function resolveDayOfMonth(day, year, month) {
|
|
91
|
+
if (day >= 0) return day;
|
|
92
|
+
const resolved = getDaysInMonth(year, month) + day + 1;
|
|
93
|
+
return resolved >= 1 ? resolved : null;
|
|
94
|
+
}
|
|
87
95
|
function addDays(date, days) {
|
|
88
96
|
const result = new Date(date);
|
|
89
97
|
result.setDate(result.getDate() + days);
|
|
@@ -125,7 +133,7 @@ var constEventHandler = {
|
|
|
125
133
|
validate(event) {
|
|
126
134
|
if (typeof event !== "object" || event === null) return false;
|
|
127
135
|
const e = event;
|
|
128
|
-
return e.type === "const" && typeof e.month === "number" && e.month >= 1 && e.month <= 12 && typeof e.day === "number" && e.day >=
|
|
136
|
+
return e.type === "const" && typeof e.month === "number" && e.month >= 1 && e.month <= 12 && typeof e.day === "number" && e.day >= -31 && e.day <= 31 && e.day !== 0 && typeof e.title === "string" && typeof e.id === "string";
|
|
129
137
|
},
|
|
130
138
|
generate(event, year) {
|
|
131
139
|
if (event.startYear !== void 0 && year < event.startYear) {
|
|
@@ -137,7 +145,9 @@ var constEventHandler = {
|
|
|
137
145
|
if (event.excludeYears?.includes(year)) {
|
|
138
146
|
return [];
|
|
139
147
|
}
|
|
140
|
-
|
|
148
|
+
const day = resolveDayOfMonth(event.day, year, event.month);
|
|
149
|
+
if (day === null) return [];
|
|
150
|
+
return [createDate(year, event.month, day)];
|
|
141
151
|
}
|
|
142
152
|
};
|
|
143
153
|
|
|
@@ -157,14 +167,11 @@ var fixedEventHandler = {
|
|
|
157
167
|
};
|
|
158
168
|
|
|
159
169
|
// src/generators/monthly.ts
|
|
160
|
-
function getDaysInMonth(year, month) {
|
|
161
|
-
return new Date(year, month, 0).getDate();
|
|
162
|
-
}
|
|
163
170
|
var monthlyEventHandler = {
|
|
164
171
|
validate(event) {
|
|
165
172
|
if (typeof event !== "object" || event === null) return false;
|
|
166
173
|
const e = event;
|
|
167
|
-
return e.type === "monthly" && typeof e.day === "number" && e.day >=
|
|
174
|
+
return e.type === "monthly" && typeof e.day === "number" && e.day >= -31 && e.day <= 31 && e.day !== 0 && typeof e.title === "string" && typeof e.id === "string";
|
|
168
175
|
},
|
|
169
176
|
generate(event, year) {
|
|
170
177
|
const dates = [];
|
|
@@ -182,10 +189,11 @@ var monthlyEventHandler = {
|
|
|
182
189
|
continue;
|
|
183
190
|
}
|
|
184
191
|
const daysInMonth = getDaysInMonth(year, month);
|
|
185
|
-
|
|
192
|
+
const day = resolveDayOfMonth(event.day, year, month);
|
|
193
|
+
if (day === null || day > daysInMonth) {
|
|
186
194
|
continue;
|
|
187
195
|
}
|
|
188
|
-
const date = createDate(year, month,
|
|
196
|
+
const date = createDate(year, month, day);
|
|
189
197
|
const dateStr = formatDate(date);
|
|
190
198
|
if (event.excludeDates?.includes(dateStr)) {
|
|
191
199
|
continue;
|
|
@@ -263,6 +271,49 @@ var weeklyEventHandler = {
|
|
|
263
271
|
}
|
|
264
272
|
};
|
|
265
273
|
|
|
274
|
+
// src/generators/daily.ts
|
|
275
|
+
var PHASE_EPOCH = createDate(1970, 1, 1);
|
|
276
|
+
var dailyEventHandler = {
|
|
277
|
+
validate(event) {
|
|
278
|
+
if (typeof event !== "object" || event === null) return false;
|
|
279
|
+
const e = event;
|
|
280
|
+
if (e.type !== "daily") return false;
|
|
281
|
+
if (typeof e.title !== "string" || typeof e.id !== "string") return false;
|
|
282
|
+
if (e.interval !== void 0 && (typeof e.interval !== "number" || e.interval < 1))
|
|
283
|
+
return false;
|
|
284
|
+
return true;
|
|
285
|
+
},
|
|
286
|
+
generate(event, year) {
|
|
287
|
+
const interval = event.interval ?? 1;
|
|
288
|
+
const yearEnd = createDate(year, 12, 31);
|
|
289
|
+
const startLimit = event.startDate ? parseDate(event.startDate) : null;
|
|
290
|
+
const endLimit = event.endDate ? parseDate(event.endDate) : null;
|
|
291
|
+
const anchor = startLimit ?? PHASE_EPOCH;
|
|
292
|
+
const dates = [];
|
|
293
|
+
let current = createDate(year, 1, 1);
|
|
294
|
+
while (current <= yearEnd) {
|
|
295
|
+
if (startLimit && current < startLimit) {
|
|
296
|
+
current = addDays(current, 1);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (endLimit && current > endLimit) break;
|
|
300
|
+
if (interval > 1) {
|
|
301
|
+
const offset = daysBetween(anchor, current);
|
|
302
|
+
if (offset % interval !== 0) {
|
|
303
|
+
current = addDays(current, 1);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const dateStr = formatDate(current);
|
|
308
|
+
if (!event.excludeDates?.includes(dateStr)) {
|
|
309
|
+
dates.push(new Date(current));
|
|
310
|
+
}
|
|
311
|
+
current = addDays(current, 1);
|
|
312
|
+
}
|
|
313
|
+
return dates;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
266
317
|
// src/generators/nth-weekday.ts
|
|
267
318
|
function nthWeekdayInMonth(year, month, dayOfWeek, nth) {
|
|
268
319
|
if (nth === -1) {
|
|
@@ -305,7 +356,7 @@ var nthWeekdayEventHandler = {
|
|
|
305
356
|
const hasAnchored = e.after !== void 0;
|
|
306
357
|
if (hasSimple && !hasAnchored) {
|
|
307
358
|
if (typeof e.nth !== "number" || ![1, 2, 3, 4, -1].includes(e.nth)) return false;
|
|
308
|
-
if (typeof e.month !== "number" || e.month < 1 || e.month > 12)
|
|
359
|
+
if (e.month !== void 0 && (typeof e.month !== "number" || e.month < 1 || e.month > 12))
|
|
309
360
|
return false;
|
|
310
361
|
} else if (!hasAnchored) {
|
|
311
362
|
return false;
|
|
@@ -329,9 +380,17 @@ var nthWeekdayEventHandler = {
|
|
|
329
380
|
}
|
|
330
381
|
return candidate ? [candidate] : [];
|
|
331
382
|
}
|
|
332
|
-
if (event.nth !== void 0
|
|
333
|
-
|
|
334
|
-
|
|
383
|
+
if (event.nth !== void 0) {
|
|
384
|
+
if (event.month !== void 0) {
|
|
385
|
+
const date = nthWeekdayInMonth(year, event.month, event.dayOfWeek, event.nth);
|
|
386
|
+
return date ? [date] : [];
|
|
387
|
+
}
|
|
388
|
+
const dates = [];
|
|
389
|
+
for (let month = 1; month <= 12; month++) {
|
|
390
|
+
const date = nthWeekdayInMonth(year, month, event.dayOfWeek, event.nth);
|
|
391
|
+
if (date) dates.push(date);
|
|
392
|
+
}
|
|
393
|
+
return dates;
|
|
335
394
|
}
|
|
336
395
|
return [];
|
|
337
396
|
}
|
|
@@ -648,6 +707,27 @@ var SearchBuilder = class {
|
|
|
648
707
|
this.options.hasAllCategories = categories;
|
|
649
708
|
return this;
|
|
650
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* Filter by event type(s) — match any (e.g. `"weekly"`, `"daily"`, a plugin type).
|
|
712
|
+
*/
|
|
713
|
+
type(...types) {
|
|
714
|
+
this.options.types = types;
|
|
715
|
+
return this;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Filter by status — match any (e.g. hide cancelled by listing the ones you want).
|
|
719
|
+
*/
|
|
720
|
+
status(...status) {
|
|
721
|
+
this.options.status = status;
|
|
722
|
+
return this;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Filter by source — match any (the plugin / feed / subscription that produced it).
|
|
726
|
+
*/
|
|
727
|
+
source(...source) {
|
|
728
|
+
this.options.source = source;
|
|
729
|
+
return this;
|
|
730
|
+
}
|
|
651
731
|
/**
|
|
652
732
|
* Search by metadata key-value pairs.
|
|
653
733
|
* Supports nested objects and case-insensitive string matching.
|
|
@@ -895,6 +975,15 @@ function executeSearch(events, options) {
|
|
|
895
975
|
return options.hasAllCategories.every((cat) => e.categories.includes(cat));
|
|
896
976
|
});
|
|
897
977
|
}
|
|
978
|
+
if (options.types && options.types.length > 0) {
|
|
979
|
+
results = results.filter((e) => options.types.includes(e.type));
|
|
980
|
+
}
|
|
981
|
+
if (options.status && options.status.length > 0) {
|
|
982
|
+
results = results.filter((e) => e.status !== void 0 && options.status.includes(e.status));
|
|
983
|
+
}
|
|
984
|
+
if (options.source && options.source.length > 0) {
|
|
985
|
+
results = results.filter((e) => e.source !== void 0 && options.source.includes(e.source));
|
|
986
|
+
}
|
|
898
987
|
if (options.metadata) {
|
|
899
988
|
results = results.filter((e) => matchesMetadata(e.metadata, options.metadata));
|
|
900
989
|
}
|
|
@@ -988,6 +1077,7 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
988
1077
|
this.eventTypeHandlers.set("fixed", fixedEventHandler);
|
|
989
1078
|
this.eventTypeHandlers.set("monthly", monthlyEventHandler);
|
|
990
1079
|
this.eventTypeHandlers.set("weekly", weeklyEventHandler);
|
|
1080
|
+
this.eventTypeHandlers.set("daily", dailyEventHandler);
|
|
991
1081
|
this.eventTypeHandlers.set("nth-weekday", nthWeekdayEventHandler);
|
|
992
1082
|
this.eventTypeHandlers.set(
|
|
993
1083
|
"formula",
|
|
@@ -1084,6 +1174,27 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
1084
1174
|
events: c.events
|
|
1085
1175
|
});
|
|
1086
1176
|
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Export events as a portable {@link Collection} — the inverse of {@link load}.
|
|
1179
|
+
* Serializes the plain event configs plus a manifest of the plugins their event
|
|
1180
|
+
* types need, so the result round-trips back through `load()`. Pass it to
|
|
1181
|
+
* `JSON.stringify` for a `.cdy` document.
|
|
1182
|
+
*
|
|
1183
|
+
* @param options.group - export a single group; omit to export every group's events.
|
|
1184
|
+
* @param options.name - the collection name (also the default group id on re-load).
|
|
1185
|
+
* @param options.version - an informational version stamp.
|
|
1186
|
+
*/
|
|
1187
|
+
toCollection(options = {}) {
|
|
1188
|
+
const groups = options.group !== void 0 ? [this.groups.get(options.group)].filter((g) => g != null) : [...this.groups.values()];
|
|
1189
|
+
const events = groups.flatMap((g) => g.events);
|
|
1190
|
+
const usedTypes = new Set(events.map((e) => e.type));
|
|
1191
|
+
const plugins = [...this.plugins.values()].filter((p) => Object.keys(p.eventTypes ?? {}).some((t) => usedTypes.has(t))).map((p) => p.name);
|
|
1192
|
+
const out = { events };
|
|
1193
|
+
if (options.name) out.collection = options.name;
|
|
1194
|
+
if (options.version) out.version = options.version;
|
|
1195
|
+
if (plugins.length) out.plugins = plugins;
|
|
1196
|
+
return out;
|
|
1197
|
+
}
|
|
1087
1198
|
removeGroup(groupId) {
|
|
1088
1199
|
this.groups.delete(groupId);
|
|
1089
1200
|
this.dirty = true;
|
|
@@ -1104,12 +1215,18 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
1104
1215
|
return this;
|
|
1105
1216
|
}
|
|
1106
1217
|
generateEvents(from, to) {
|
|
1218
|
+
if (this.dirty) {
|
|
1219
|
+
this.cache.invalidate();
|
|
1220
|
+
this.dirty = false;
|
|
1221
|
+
}
|
|
1107
1222
|
const cacheKey = `${from}:${to}`;
|
|
1108
|
-
if (
|
|
1223
|
+
if (this.cache.has(cacheKey)) {
|
|
1109
1224
|
return this.cache.get(cacheKey);
|
|
1110
1225
|
}
|
|
1111
1226
|
const events = [];
|
|
1112
|
-
const
|
|
1227
|
+
const inRange = getYearsInRange(from, to);
|
|
1228
|
+
const years = inRange.length ? [inRange[0] - 1, ...inRange, inRange[inRange.length - 1] + 1] : inRange;
|
|
1229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1113
1230
|
for (const group of this.groups.values()) {
|
|
1114
1231
|
if (!group.enabled) continue;
|
|
1115
1232
|
for (const eventConfig of group.events) {
|
|
@@ -1122,6 +1239,10 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
1122
1239
|
let dateStr = formatDate(date);
|
|
1123
1240
|
if (directives.startDate && dateStr < directives.startDate) continue;
|
|
1124
1241
|
if (directives.endDate && dateStr > directives.endDate) continue;
|
|
1242
|
+
const occYear = +dateStr.slice(0, 4);
|
|
1243
|
+
if (directives.startYear !== void 0 && occYear < directives.startYear) continue;
|
|
1244
|
+
if (directives.endYear !== void 0 && occYear > directives.endYear) continue;
|
|
1245
|
+
if (directives.excludeYears?.includes(occYear)) continue;
|
|
1125
1246
|
const exception = directives.exceptions?.[dateStr];
|
|
1126
1247
|
if (exception && "skip" in exception) {
|
|
1127
1248
|
continue;
|
|
@@ -1141,6 +1262,13 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
1141
1262
|
}
|
|
1142
1263
|
}
|
|
1143
1264
|
if (!isDateInRange(dateStr, from, to)) continue;
|
|
1265
|
+
const dedupeKey = JSON.stringify([
|
|
1266
|
+
group.id,
|
|
1267
|
+
eventConfig.id,
|
|
1268
|
+
dateStr
|
|
1269
|
+
]);
|
|
1270
|
+
if (seen.has(dedupeKey)) continue;
|
|
1271
|
+
seen.add(dedupeKey);
|
|
1144
1272
|
const calendarEvent = this.createCalendarEvent(
|
|
1145
1273
|
eventConfig,
|
|
1146
1274
|
group,
|
|
@@ -1159,13 +1287,13 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
1159
1287
|
});
|
|
1160
1288
|
this.cache.set(cacheKey, events);
|
|
1161
1289
|
this.index.index(events);
|
|
1162
|
-
this.dirty = false;
|
|
1163
1290
|
return events;
|
|
1164
1291
|
}
|
|
1165
1292
|
createCalendarEvent(config, group, dateStr, override) {
|
|
1166
1293
|
const base = config;
|
|
1167
1294
|
const event = {
|
|
1168
1295
|
id: generateEventId(base.id, dateStr),
|
|
1296
|
+
type: base.type,
|
|
1169
1297
|
sourceEventId: base.id,
|
|
1170
1298
|
title: base.title,
|
|
1171
1299
|
date: dateStr,
|
|
@@ -1221,13 +1349,16 @@ var CalendaryInstance = class _CalendaryInstance {
|
|
|
1221
1349
|
* Get calendar days for a date range
|
|
1222
1350
|
*/
|
|
1223
1351
|
getDays(options) {
|
|
1224
|
-
const { groups } = options;
|
|
1352
|
+
const { groups, types } = options;
|
|
1225
1353
|
const fromStr = normalizeDateInput(options.from);
|
|
1226
1354
|
const toStr = normalizeDateInput(options.to);
|
|
1227
1355
|
let events = this.generateEvents(fromStr, toStr);
|
|
1228
1356
|
if (groups && groups.length > 0) {
|
|
1229
1357
|
events = events.filter((e) => groups.includes(e.groupId));
|
|
1230
1358
|
}
|
|
1359
|
+
if (types && types.length > 0) {
|
|
1360
|
+
events = events.filter((e) => types.includes(e.type));
|
|
1361
|
+
}
|
|
1231
1362
|
const eventsByDate = /* @__PURE__ */ new Map();
|
|
1232
1363
|
for (const event of events) {
|
|
1233
1364
|
const existing = eventsByDate.get(event.date) || [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "calendaryjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Composable calendar & recurrence engine with pluggable calendar systems.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"typescript"
|
|
39
39
|
],
|
|
40
40
|
"author": "calendaryjs",
|
|
41
|
-
"license": "
|
|
41
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"typescript": "^5.3.2",
|
|
44
44
|
"vitest": "^4.0.18",
|