Calendar Helpers

Timestamp includes helpers for calendar-style workflows without depending on a UI framework.

Date math

import { addToDate, addToDateClamped, nextDay, parseTimestamp, prevDay } from '@timestamp-js/core'

const current = parseTimestamp('2026-06-08')!
const tomorrow = nextDay(current)
const yesterday = prevDay(current)
const nextMonth = addToDate(current, { month: 1 })
const lastYear = addToDate(current, { year: -1 })

const monthEnd = parseTimestamp('2026-01-31')!
const billingDate = addToDateClamped(monthEnd, { month: 1 }) // 2026-02-28

These helpers default to Gregorian. When date fields belong to another calendar, pass that calendar as the final argument where the helper accepts one, for example nextDay(timestamp, calendar), addToDate(timestamp, { month: 1 }, calendar), or getEndOfMonth(timestamp, calendar).

Lists

import { createDayList, createIntervalList, parseTimestamp } from '@timestamp-js/core'

const start = parseTimestamp('2026-06-01')!
const end = parseTimestamp('2026-06-07')!

const days = createDayList(start, end)
const intervals = createIntervalList(start, 0, 60, 24, start)

createDayList() also accepts a CalendarSystem as its final argument for adapter-native day generation. The createCalendarDayList() helper below is often clearer in adapter-specific code.

Adapter calendar lists

Use the calendar-aware helpers when date fields belong to an optional calendar adapter rather than the built-in Gregorian calendar.

import {
  createCalendarDayList,
  getCalendarEndOfMonth,
  getCalendarEndOfWeek,
  parseCalendarTimestamp,
  getCalendarStartOfMonth,
  getCalendarStartOfWeek,
} from '@timestamp-js/core'
import { indianNationalCalendar } from '@timestamp-js/calendar-saka'

const visible = parseCalendarTimestamp('1946-01-15', indianNationalCalendar)!
const weekdays = [0, 1, 2, 3, 4, 5, 6]

const weekStart = getCalendarStartOfWeek(visible, weekdays, indianNationalCalendar)
const weekEnd = getCalendarEndOfWeek(visible, weekdays, indianNationalCalendar)
const weekDays = createCalendarDayList(weekStart, weekEnd, visible, indianNationalCalendar)

weekStart.date // '1946-01-11'
weekEnd.date // '1946-01-17'
weekDays.map((day) => day.date)
// [
//   '1946-01-11',
//   '1946-01-12',
//   '1946-01-13',
//   '1946-01-14',
//   '1946-01-15',
//   '1946-01-16',
//   '1946-01-17',
// ]

const monthStart = getCalendarStartOfMonth(visible, indianNationalCalendar)
const monthEnd = getCalendarEndOfMonth(visible, indianNationalCalendar)
const monthDays = createCalendarDayList(monthStart, monthEnd, visible, indianNationalCalendar)

monthStart.date // '1946-01-01'
monthEnd.date // '1946-01-31'
monthDays.length // 31

Islamic civil ranges

The same helpers work with Islamic civil dates. The input and output dates stay in Islamic civil year/month/day fields, while the adapter supplies stable serial-day comparison under the hood.

import {
  createCalendarDayList,
  getCalendarEndOfMonth,
  getCalendarEndOfWeek,
  getCalendarStartOfMonth,
  getCalendarStartOfWeek,
  parseCalendarTimestamp,
} from '@timestamp-js/core'
import { islamicCivilCalendar } from '@timestamp-js/calendar-islamic'

const visible = parseCalendarTimestamp('1445-09-15', islamicCivilCalendar)!
const weekdays = [0, 1, 2, 3, 4, 5, 6]

const weekStart = getCalendarStartOfWeek(visible, weekdays, islamicCivilCalendar)
const weekEnd = getCalendarEndOfWeek(visible, weekdays, islamicCivilCalendar)
const weekDays = createCalendarDayList(weekStart, weekEnd, visible, islamicCivilCalendar)

weekStart.date // '1445-09-14'
weekEnd.date // '1445-09-20'
weekDays.map((day) => day.date)
// [
//   '1445-09-14',
//   '1445-09-15',
//   '1445-09-16',
//   '1445-09-17',
//   '1445-09-18',
//   '1445-09-19',
//   '1445-09-20',
// ]

const monthStart = getCalendarStartOfMonth(visible, islamicCivilCalendar)
const monthEnd = getCalendarEndOfMonth(visible, islamicCivilCalendar)
const monthDays = createCalendarDayList(monthStart, monthEnd, visible, islamicCivilCalendar)

monthStart.date // '1445-09-01'
monthEnd.date // '1445-09-30'
monthDays.length // 30

Calendar interop

Use conversion helpers when a UI works with one calendar system but storage, APIs, or another UI surface expects another calendar.

import {
  convertCalendarDate,
  convertCalendarTimestamp,
  getCalendarDatePartsIdentity,
  isCalendarEndOfMonth,
  isCalendarStartOfMonth,
  isOutsideCalendarMonth,
  parseCalendarTimestampSafe,
  toCalendarTimestamp,
  toGregorianTimestamp,
} from '@timestamp-js/core'
import { islamicCivilCalendar } from '@timestamp-js/calendar-islamic'

const gregorian = parseCalendarTimestampSafe('2024-03-11')!
const hijri = toCalendarTimestamp(gregorian, islamicCivilCalendar)
const stored = toGregorianTimestamp(hijri, islamicCivilCalendar)

hijri.date // '1445-09-01'
stored.date // '2024-03-11'

convertCalendarTimestamp(hijri, islamicCivilCalendar).date // '2024-03-11'
convertCalendarDate('1445-09-01', islamicCivilCalendar) // '2024-03-11'

const identity = getCalendarDatePartsIdentity(
  { year: 1445, month: 9, day: 1 },
  islamicCivilCalendar,
)

identity.nativeDate // '1445-09-01'
identity.gregorianDate // '2024-03-11'
identity.epochDay // stable cross-calendar comparison key

isCalendarStartOfMonth(hijri, islamicCivilCalendar) // true
isCalendarEndOfMonth(hijri, islamicCivilCalendar) // false
isOutsideCalendarMonth(
  parseCalendarTimestampSafe('1445-10-01', islamicCivilCalendar)!,
  hijri,
  islamicCivilCalendar,
) // true

parseCalendarTimestampSafe() is intended for component model watchers and form-like workflows. It returns a fallback instead of throwing when a value is empty, stale, outside the adapter-supported range, or temporarily invalid while a user is editing.

Labels

Use Intl.DateTimeFormat-backed helpers for month and weekday labels.

import { getMonthNames, getWeekdayNames } from '@timestamp-js/core'

const weekdays = getWeekdayNames('long', 'en-US')
const months = getMonthNames('long', 'en-US')

For adapter calendar labels, use the calendar-aware helpers so Timestamp can combine the adapter’s defaultLocale, defaultWeekdays, and intlCalendar metadata with Intl.DateTimeFormat.

import {
  formatCalendarDateLabel,
  getCalendarMonthName,
  getCalendarMonthNames,
  getCalendarWeekdayNames,
  parseCalendarTimestamp,
} from '@timestamp-js/core'
import { islamicCivilCalendar } from '@timestamp-js/calendar-islamic'

const visible = parseCalendarTimestamp('1445-09-15', islamicCivilCalendar)!

getCalendarMonthName(islamicCivilCalendar, visible.month, 'long', 'en-US', visible.year)
// 'Ramadan'

getCalendarMonthNames(islamicCivilCalendar, 'short', 'en-US', visible.year)
// ['Muh.', 'Saf.', 'Rab. I', ...]

getCalendarWeekdayNames(islamicCivilCalendar, 'short', 'en-US')
// ['Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']

formatCalendarDateLabel(
  visible,
  islamicCivilCalendar,
  {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  },
  'en-US',
)
// 'Ramadan 15, 1445 AH'

If an adapter does not publish an intlCalendar, month labels fall back to Month 1, Month 2, and so on. Date labels fall back to the adapter-native YYYY-MM-DD string.

Use compact masks when you only need fixed numeric output and do not need localized names.

import { formatTimestamp, parseTimestamp } from '@timestamp-js/core'

const timestamp = parseTimestamp('2036-06-08T09:30:15.250Z')!

formatTimestamp(timestamp, 'YYYY-MM-DD') // '2036-06-08'
formatTimestamp(timestamp, 'HH:mm:ss.SSS') // '09:30:15.250'

Identifiers

Identifiers give stable numeric comparisons without converting to native Date objects repeatedly.

import {
  getCalendarDayIdentifier,
  getDayIdentifier,
  getDayTimeIdentifier,
  parseTimestamp,
} from '@timestamp-js/core'

const timestamp = parseTimestamp('2026-06-08 09:30')!

getDayIdentifier(timestamp)
getDayTimeIdentifier(timestamp)
getCalendarDayIdentifier(timestamp)