@karmaniverous/rrstack
    Preparing search index...

    @karmaniverous/rrstack

    RRStack

    npm version Node Current docs changelog license

    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.

    • Built on rrule for recurrence logic
    • Uses Luxon for timezone/DST‑correct duration arithmetic
    • Pure library surface (no I/O side effects)
    • JSON persistence and round‑tripping
    • Thoroughly tested (DST edge cases, monthly patterns, segment sweeps, notices)
    npm install @karmaniverous/rrstack
    # or
    yarn add @karmaniverous/rrstack
    # or
    pnpm add @karmaniverous/rrstack
    • ESM and CJS consumers are supported.
    • TypeScript typings are included.
    • Node >= 20.
    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.

    • Real‑world scheduling usually needs more than one RRULE. You have base activation windows, blackout exceptions, occasional reactivations, and point‑in‑time events. RRStack provides a deterministic cascade: later rules override earlier ones at covered instants, and event rules let you attach zero‑duration instants (recurring or one‑time) that respect the cascade.
    • Time zones matter. We compute coverage in the rule’s IANA time zone with DST‑aware duration arithmetic (Luxon).
    • Half‑open intervals everywhere. All coverage follows [start, end). In seconds mode ('s'), ends are rounded up to the next second to avoid boundary false negatives.
    • JSON round‑trip. Store and reload stack configuration (with versioning and notices via update()).
    • Pure library surface. No I/O side effects; suitable for Node, browsers, and workers.

    BSD‑3‑Clause © Jason Williscroft


    Built for you with ❤️ on Bali! Find more great tools & templates on my GitHub Profile: https://github.com/karmaniverous