@catlabtech/mycal-core 0.1.1 → 0.1.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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/business-days.d.ts +7 -0
- package/dist/business-days.js +106 -0
- package/dist/filter.d.ts +18 -0
- package/dist/filter.js +75 -0
- package/dist/hijri.d.ts +36 -0
- package/dist/hijri.js +59 -0
- package/dist/ical.d.ts +2 -0
- package/dist/ical.js +90 -0
- package/dist/index.d.ts +14 -803
- package/dist/index.js +22 -517
- package/dist/long-weekend.d.ts +18 -0
- package/dist/long-weekend.js +127 -0
- package/dist/replacement.d.ts +2 -0
- package/dist/replacement.js +69 -0
- package/dist/schemas.d.ts +652 -0
- package/dist/schemas.js +136 -0
- package/dist/school.d.ts +10 -0
- package/dist/school.js +44 -0
- package/dist/state-resolver.d.ts +4 -0
- package/dist/state-resolver.js +18 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.js +2 -0
- package/dist/validation.d.ts +21 -0
- package/dist/validation.js +94 -0
- package/dist/weekend.d.ts +8 -0
- package/dist/weekend.js +57 -0
- package/package.json +8 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isWeekend, nextWorkingDay } from "./weekend.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the synthetic "Cuti Ganti" (replacement) holiday for a `source` holiday
|
|
4
|
+
* that has been rolled forward to `replacementDate`. Centralised so the
|
|
5
|
+
* weekend-clash and same-day-overlap paths cannot drift apart.
|
|
6
|
+
*/
|
|
7
|
+
function makeReplacement(source, replacementDate, stateCode, idSuffix, now) {
|
|
8
|
+
return {
|
|
9
|
+
id: `${source.id}-${idSuffix}`,
|
|
10
|
+
date: replacementDate,
|
|
11
|
+
name: {
|
|
12
|
+
ms: `Cuti Ganti ${source.name.ms}`,
|
|
13
|
+
en: `Replacement for ${source.name.en}`,
|
|
14
|
+
zh: source.name.zh ? `补假 ${source.name.zh}` : undefined,
|
|
15
|
+
},
|
|
16
|
+
type: "replacement",
|
|
17
|
+
status: source.status,
|
|
18
|
+
states: [stateCode],
|
|
19
|
+
isPublicHoliday: true,
|
|
20
|
+
gazetteLevel: source.gazetteLevel,
|
|
21
|
+
isReplacementFor: source.id,
|
|
22
|
+
source: source.source,
|
|
23
|
+
createdAt: now,
|
|
24
|
+
updatedAt: now,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function calculateReplacementHolidays(holidays, state) {
|
|
28
|
+
const stateHolidays = holidays.filter((h) => h.status !== "cancelled" &&
|
|
29
|
+
(h.states.includes("*") || h.states.includes(state.code)));
|
|
30
|
+
const holidayDates = new Set(stateHolidays.map((h) => h.date));
|
|
31
|
+
const replacementDates = new Set();
|
|
32
|
+
const replacements = [];
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
// 1. Holidays that fall on a weekend roll forward to the next working day.
|
|
35
|
+
for (const holiday of stateHolidays) {
|
|
36
|
+
if (holiday.type === "replacement")
|
|
37
|
+
continue;
|
|
38
|
+
if (!isWeekend(holiday.date, state))
|
|
39
|
+
continue;
|
|
40
|
+
const occupied = new Set([...holidayDates, ...replacementDates]);
|
|
41
|
+
const replacementDate = nextWorkingDay(holiday.date, state, occupied);
|
|
42
|
+
replacementDates.add(replacementDate);
|
|
43
|
+
replacements.push(makeReplacement(holiday, replacementDate, state.code, "replacement", now));
|
|
44
|
+
}
|
|
45
|
+
// 2. Two holidays on the same date: the federal (gazette "P") holiday takes the
|
|
46
|
+
// day, so the state-level (gazette "N") holiday is the one rolled forward.
|
|
47
|
+
const dateGroups = new Map();
|
|
48
|
+
for (const h of stateHolidays) {
|
|
49
|
+
if (h.type === "replacement")
|
|
50
|
+
continue;
|
|
51
|
+
const existing = dateGroups.get(h.date) ?? [];
|
|
52
|
+
dateGroups.set(h.date, [...existing, h]);
|
|
53
|
+
}
|
|
54
|
+
for (const [, group] of dateGroups) {
|
|
55
|
+
if (group.length <= 1)
|
|
56
|
+
continue;
|
|
57
|
+
const stateHoliday = group.find((h) => h.gazetteLevel === "N");
|
|
58
|
+
if (!stateHoliday)
|
|
59
|
+
continue;
|
|
60
|
+
// Skip if the weekend pass already produced a replacement for it.
|
|
61
|
+
if (replacements.some((r) => r.isReplacementFor === stateHoliday.id))
|
|
62
|
+
continue;
|
|
63
|
+
const occupied = new Set([...holidayDates, ...replacementDates]);
|
|
64
|
+
const replacementDate = nextWorkingDay(stateHoliday.date, state, occupied);
|
|
65
|
+
replacementDates.add(replacementDate);
|
|
66
|
+
replacements.push(makeReplacement(stateHoliday, replacementDate, state.code, "overlap-replacement", now));
|
|
67
|
+
}
|
|
68
|
+
return replacements;
|
|
69
|
+
}
|