Timezone‑aware RRULE stacking engine for Node/TypeScript.
RRStack lets you compose a prioritized stack of time‑based rules (using the battle‑tested rrule library) to compute whether a given instant is active or blackout, enumerate active/blackout segments over a window, classify ranges as active/blackout/partial, derive effective active bounds, and query point‑in‑time events (recurring or one‑time) that respect the coverage cascade. It handles real‑world timezone behavior, including DST transitions, by computing coverage in the rule’s IANA timezone.
npm install @karmaniverous/rrstack
# or
yarn add @karmaniverous/rrstack
# or
pnpm add @karmaniverous/rrstack
import { RRStack } from '@karmaniverous/rrstack';
// 1) Define rules (JSON serializable)
const rules = [
// Daily 05:00–06:00 active
{
effect: 'active' as const,
duration: { hours: 1 },
options: {
freq: 'daily',
byhour: [5],
byminute: [0],
bysecond: [0],
},
label: 'daily-05',
},
// Blackout 05:30–05:45 (overrides active during that slice)
{
effect: 'blackout' as const,
duration: { minutes: 15 },
options: {
freq: 'daily',
byhour: [5],
byminute: [30],
bysecond: [0],
},
label: 'blk-0530-15m',
},
];
// 2) Create a stack
const stack = new RRStack({
timezone: 'America/Chicago',
rules,
});
// 3) Point query: active?
const t = Date.now();
const isActive = stack.isActiveAt(t); // boolean
// 4) Enumerate segments over a window (half-open [from, to))
const from = Date.UTC(2024, 0, 2, 5, 0, 0);
const to = Date.UTC(2024, 0, 2, 6, 0, 0);
for (const seg of stack.getSegments(from, to)) {
// { start: number; end: number; status: 'active' | 'blackout' }
}
// 5) Classify a window
const status = stack.classifyRange(from, to); // 'active' | 'blackout' | 'partial'
// 6) Effective bounds (open-sided detection)
const bounds = stack.getEffectiveBounds(); // { start?: number; end?: number; empty: boolean }
// 7) Persist / restore
const json = stack.toJson();
const same = new RRStack(json);
// 8) Events — zero-duration instants (recurring or one-time)
const eventRules = [
// Recurring daily event at 05:00
{
effect: 'event' as const,
options: {
freq: 'daily' as const,
byhour: [5],
byminute: [0],
bysecond: [0],
},
label: 'daily-5am',
},
// One-time event (no freq, just a timestamp)
{
effect: 'event' as const,
options: { starts: Date.UTC(2024, 0, 15, 12, 0, 0) },
label: 'launch-party',
},
];
const eventStack = new RRStack({
timezone: 'America/Chicago',
rules: eventRules,
});
// Enumerate events in a window
for (const evt of eventStack.getEvents(from, to)) {
// { at: number; label?: string }
}
// Next upcoming event (from now, default 366-day look-ahead)
const next = eventStack.nextEvent(); // { at, label } | undefined
For symbol‑level documentation (methods, parameters, and return types with code signatures), visit the hosted API Reference.
update()).BSD‑3‑Clause © Jason Williscroft
Built for you with ❤️ on Bali! Find more great tools & templates on my GitHub Profile: https://github.com/karmaniverous