feat(season): easter-offset + nth-weekday datumregler för marknads-högtider (MIKRO A)
CI / Typecheck, test & build (push) Failing after 42s

This commit is contained in:
Sven (AAMOS AI)
2026-08-14 21:46:17 +07:00
parent cd6a9eb556
commit 19af7e0720
2 changed files with 36 additions and 2 deletions
+3 -1
View File
@@ -4,7 +4,9 @@ import { createdAt, updatedAt } from "./_shared.js";
type DateRule =
| { kind: "fixed"; monthDay: string }
| { kind: "range"; startMonthDay: string; endMonthDay: string }
| { kind: "computed"; algorithm: "midsummer" | "easter" | "advent" | "custom" };
| { kind: "computed"; algorithm: "midsummer" | "easter" | "advent" | "custom" }
| { kind: "easter-offset"; days: number }
| { kind: "nth-weekday"; month: number; weekday: number; n: number | "last" };
/**
* Datadriven Season & Events Engine (spec §28): midsommar, jul, påsk, kräftskiva,
+33 -1
View File
@@ -54,7 +54,9 @@ export interface SeasonEventRuleInput {
dateRule:
| { kind: "fixed"; monthDay: string }
| { kind: "range"; startMonthDay: string; endMonthDay: string }
| { kind: "computed"; algorithm: "midsummer" | "easter" | "advent" | "custom" };
| { kind: "computed"; algorithm: "midsummer" | "easter" | "advent" | "custom" }
| { kind: "easter-offset"; days: number }
| { kind: "nth-weekday"; month: number; weekday: number; n: number | "last" };
leadDays: number;
}
@@ -83,6 +85,19 @@ export function isEventActive(rule: SeasonEventRuleInput, today: Date): boolean
if (end < start) end = Date.UTC(year + 1, (em ?? 1) - 1, ed ?? 1);
return t >= start && t <= end;
}
case "easter-offset": {
const target = new Date(easterSunday(year).getTime() + rule.dateRule.days * DAY);
return activeAround(target, 1);
}
case "nth-weekday": {
const target = nthWeekdayOfMonth(
year,
rule.dateRule.month,
rule.dateRule.weekday,
rule.dateRule.n,
);
return activeAround(target, 1);
}
case "computed": {
switch (rule.dateRule.algorithm) {
case "midsummer":
@@ -102,3 +117,20 @@ export function isEventActive(rule: SeasonEventRuleInput, today: Date): boolean
function strip(d: Date): Date {
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
}
/** N:te (eller sista) veckodagen i en månad. month 112, weekday 0=sön..6=lör. */
function nthWeekdayOfMonth(
year: number,
month: number,
weekday: number,
n: number | "last",
): Date {
if (n === "last") {
const last = new Date(Date.UTC(year, month, 0)); // sista dagen i månaden
const back = (last.getUTCDay() - weekday + 7) % 7;
return new Date(Date.UTC(year, month - 1, last.getUTCDate() - back));
}
const first = new Date(Date.UTC(year, month - 1, 1));
const fwd = (weekday - first.getUTCDay() + 7) % 7;
return new Date(Date.UTC(year, month - 1, 1 + fwd + (n - 1) * 7));
}