Headless Core
The trud-calendar-core package provides all calendar logic with zero dependencies and zero React dependency. Use it to build calendar UIs in any framework — Vue, Svelte, Angular, vanilla JS, or server-side.
Installation
Section titled “Installation”npm install trud-calendar-coreWhat’s included
Section titled “What’s included”All TypeScript interfaces and type definitions:
import type { CalendarEvent, CalendarView, CalendarState, CalendarAction, CalendarConfig, CalendarSlots, CalendarLocale, CalendarLabels, DateString, DateTimeString, PositionedEvent, EventSegment, RecurrenceRule, RecurrenceDay, TimedEventSegment, UndoStack, VirtualRange, WallToUtcOptions,} from "trud-calendar-core";Date utilities
Section titled “Date utilities”Pure functions for date math — no side effects, fully tree-shakeable:
import { parseDate, toDateString, toDateTimeString, addDays, addMonths, addWeeks, startOfWeek, startOfMonth, endOfMonth, isSameDay, isSameMonth, isToday, isBefore, isAfter, rangesOverlap, dateInRange, daysBetween, eachDayOfRange, getWeekDays, getMonthViewRange, getWeekViewRange, getVisibleRange, getTimeOfDay, getDurationHours, getHourLabels,} from "trud-calendar-core";Formatting
Section titled “Formatting”Locale-aware formatting via the Intl API (memoized formatters):
import { formatToolbarTitle, formatWeekdayShort, formatWeekdayNarrow, formatDayNumber, formatTime, formatTimeRange, formatAgendaDate, formatMonthDay,} from "trud-calendar-core";
formatTime("2026-03-13T14:30:00", "en-US"); // "2:30 PM"formatTime("2026-03-13T14:30:00", "de-DE"); // "14:30"formatAgendaDate("2026-03-13", "es-ES"); // "viernes, 13 de marzo"formatToolbarTitle("2026-03-13", "month", "ja-JP"); // "2026年3月"Event utilities
Section titled “Event utilities”Sorting, filtering, layout algorithms:
import { sortEvents, filterEventsInRange, getEventsForDay, isMultiDayEvent, partitionEvents, segmentMultiDayEvent, segmentTimedMultiDayEvent, getEventSegments, buildOverlapGroups, assignColumns, computeTimePositions, groupEventsByDate,} from "trud-calendar-core";Timezones
Section titled “Timezones”Zero-dependency IANA timezone support built on Intl.DateTimeFormat:
import { getTimeZoneOffset, wallTimeToUtc, utcToWallTime, convertWallTime, listTimeZones, isValidTimeZone, getTimeZoneAbbreviation, getBrowserTimeZone, eventWallToDisplay, displayWallToEvent,} from "trud-calendar-core";import type { WallToUtcOptions } from "trud-calendar-core";
// Offset (minutes east of UTC) at a specific instant.getTimeZoneOffset("2026-01-15T12:00:00Z", "America/New_York"); // -300getTimeZoneOffset("2026-07-15T12:00:00Z", "America/New_York"); // -240 (EDT)
// Wall-clock ⇄ UTC. Default DST handling:// - gap (spring-forward, wall doesn't exist) → shift forward// - overlap (fall-back, wall happens twice) → earlier instantwallTimeToUtc("2026-03-13T09:00:00", "America/New_York"); // "2026-03-13T13:00:00Z"wallTimeToUtc("2026-03-08T02:30:00", "America/New_York", { invalid: "throw" });wallTimeToUtc("2026-11-01T01:30:00", "America/New_York", { ambiguous: "later" });
// Direct cross-zone conversion (preserves the absolute instant).convertWallTime("2026-01-15T09:00:00", "America/New_York", "Europe/Berlin");// → "2026-01-15T15:00:00"
// Pickers and validation.listTimeZones(); // IANA list + "UTC"isValidTimeZone("Mars/Olympus"); // falsegetTimeZoneAbbreviation("Asia/Tokyo"); // "GMT+9"getBrowserTimeZone(); // e.g., "America/Argentina/Buenos_Aires"See Timezones for the full guide, including DST edge cases and how event.timeZone plus displayTimeZone interact.
Recurrence
Section titled “Recurrence”RFC 5545 RRULE expansion engine:
import { expandRecurringEvents, generateOccurrences,} from "trud-calendar-core";
// Expand all recurring events in a date rangeconst expanded = expandRecurringEvents(events, "2026-03-01", "2026-03-31");
// Generate occurrence dates for a single ruleconst dates = generateOccurrences( { freq: "weekly", byDay: ["MO", "WE", "FR"] }, "2026-03-01", // start date "2026-03-01", // range start "2026-03-31", // range end);See Recurrence for detailed documentation.
Undo/Redo
Section titled “Undo/Redo”A generic, framework-agnostic undo stack:
import { createUndoStack, pushState, undo, redo, canUndo, canRedo,} from "trud-calendar-core";import type { UndoStack } from "trud-calendar-core";
let stack = createUndoStack(initialEvents); // UndoStack<CalendarEvent[]>stack = pushState(stack, newEvents); // snapshot current statestack = undo(stack); // go backstack = redo(stack); // go forwardcanUndo(stack); // booleancanRedo(stack); // booleanThe stack is immutable — each operation returns a new UndoStack. Default max history is 30 states.
Virtualization
Section titled “Virtualization”Viewport-based event filtering for large datasets:
import { filterVisibleEvents, scrollToViewportRange,} from "trud-calendar-core";import type { VirtualRange } from "trud-calendar-core";
// Filter PositionedEvent[] to only those visible in the viewportconst visible = filterVisibleEvents(positioned, viewportTop, viewportBottom, overscan);
// Convert scroll position to hour rangeconst range: VirtualRange = scrollToViewportRange( scrollTop, // current scroll offset containerHeight, // visible container height totalHeight, // total scrollable height dayStartHour, // e.g., 0 dayEndHour, // e.g., 24);// range.startHour, range.endHourState management
Section titled “State management”A reducer for calendar navigation state:
import { calendarReducer, createInitialState,} from "trud-calendar-core";
const state = createInitialState("2026-03-13", "month");// { currentDate: "2026-03-13", view: "month" }
const next = calendarReducer(state, { type: "NAVIGATE_NEXT" });// { currentDate: "2026-04-13", view: "month" }Actions: NAVIGATE_PREV, NAVIGATE_NEXT, NAVIGATE_TODAY, SET_DATE, SET_VIEW.
Constants
Section titled “Constants”import { DEFAULT_LABELS, DEFAULT_LOCALE, DEFAULT_VIEW, VIEWS, // ["month", "week", "day", "agenda"] DEFAULT_DAY_START_HOUR, // 0 DEFAULT_DAY_END_HOUR, // 24 HOURS_IN_DAY, // 24 MINUTES_IN_HOUR, // 60 MINUTES_IN_DAY, // 1440} from "trud-calendar-core";iCal Export
Section titled “iCal Export”Export events to standard .ics format:
import { eventsToICal, downloadICal } from "trud-calendar-core";
// Get .ics stringconst icsContent = eventsToICal(events, "My Calendar");
// Trigger browser downloaddownloadICal(events, "my-events.ics");Supports all-day events, recurrence rules (RRULE), and exception dates (EXDATE).
Example: Build a calendar in vanilla JS
Section titled “Example: Build a calendar in vanilla JS”import { getVisibleRange, filterEventsInRange, computeTimePositions, eachDayOfRange, formatTime, formatWeekdayShort,} from "trud-calendar-core";
// Get the visible date range for a week view starting Mondayconst range = getVisibleRange("2026-03-13", "week", 1);const days = eachDayOfRange(range.start, range.end);
// Filter eventsconst visible = filterEventsInRange(myEvents, range.start, range.end);
// Compute time-grid positions for a dayconst dayEvents = visible.filter(e => e.start.startsWith("2026-03-13"));const positioned = computeTimePositions(dayEvents, 8, 20);
// Renderfor (const { event, top, height, column, totalColumns } of positioned) { const el = document.createElement("div"); el.textContent = `${formatTime(event.start, "en-US")} ${event.title}`; el.style.position = "absolute"; el.style.top = `${top}%`; el.style.height = `${height}%`; el.style.left = `${(column / totalColumns) * 100}%`; el.style.width = `${100 / totalColumns}%`; container.appendChild(el);}