Skip to content

@absoluteui/helpers/date

Date-only values, calendar arithmetic, fiscal periods, weeks, intervals, formatting, and parsing.

import { ... } from '@absoluteui/helpers/date';

Date-only arithmetic that cannot shift by a day: PlainDate values (or 'YYYY-MM-DD' strings) with month-end-aware adding, differences, weeks under any locale’s rules, fiscal years, intervals, calendar grids, and strict parsing and formatting. Reach for it for joining dates, probation periods, leave ranges, and reporting periods. Everything here counts calendar days; for weekends, holidays, and business days, combine it with @absoluteui/helpers/business-calendar as shown below. The Dates and time zones guide explains the value types, and the Business calendars guide the business-day rules.

How do I compute probation end, tenure, and anniversaries?

Section titled “How do I compute probation end, tenure, and anniversaries?”

addMonths clamps to the month end (31 January + 1 month is 28 or 29 February), and dateDiff gives years, months, and days for a tenure label. When the result must be a working day, roll it with adjustBusinessDay.

import { addMonths, dateDiff, differenceInYears, formatPlainDate } from '@absoluteui/helpers/date';
import { adjustBusinessDay, createBusinessCalendar, fixedHoliday } from '@absoluteui/helpers/business-calendar';
const joined = '2026-01-31';
const probationEnds = addMonths(joined, 6);
formatPlainDate(probationEnds); // => '2026-07-31'
formatPlainDate(addMonths(joined, 1)); // => '2026-02-28' // clamped to the month end
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Independence Day', 8, 15)] });
formatPlainDate(adjustBusinessDay(addMonths('2025-02-15', 6), 'following', cal)); // => '2025-08-18' // 15 Aug 2025 is a Friday holiday, then the weekend
dateDiff('2021-04-12', '2026-10-05'); // => { years: 5, months: 5, days: 23 }
differenceInYears('2026-04-11', '2021-04-12'); // => 4 // the fifth anniversary is tomorrow

Pass the fiscal start month; years are named by the calendar year in which they end (India’s April 2026 - March 2027 is FY2027) unless you choose naming: 'start'.

import { endOfFiscalYear, fiscalQuarterLabel, fiscalYearLabel, formatPlainDate, startOfFiscalQuarter } from '@absoluteui/helpers/date';
const india = { startMonth: 4 };
fiscalYearLabel('2026-10-05', india); // => 'FY2027'
fiscalYearLabel('2026-10-05', { ...india, style: 'span' }); // => 'FY2026-27'
fiscalQuarterLabel('2026-10-05', india); // => 'Q3'
formatPlainDate(startOfFiscalQuarter('2026-10-05', india)); // => '2026-10-01'
formatPlainDate(endOfFiscalYear('2026-10-05', { startMonth: 10 })); // => '2027-09-30' // US federal

How do I build a month view that marks weekends and holidays?

Section titled “How do I build a month view that marks weekends and holidays?”

calendarMonthGrid gives the rows of a month (with neighbouring days to fill them) starting on the locale’s first weekday. Ask the business calendar about each cell for the weekend, holiday, or half-day marker and an accessible reason.

import { calendarMonthGrid, formatPlainDate, getWeekInfo } from '@absoluteui/helpers/date';
import { createBusinessCalendar, describeBusinessDay, fixedHoliday, getBusinessDay } from '@absoluteui/helpers/business-calendar';
const week = getWeekInfo('en-IN');
const cal = createBusinessCalendar({ weekend: week.weekend, holidays: [fixedHoliday('Gandhi Jayanti', 10, 2)] });
const grid = calendarMonthGrid(2026, 10, { firstDayOfWeek: week.firstDay, fixedWeeks: false });
const cells = grid.flat().filter((d) => d.inMonth).map((d) => {
const info = getBusinessDay(d.date, cal);
return { date: formatPlainDate(d.date), working: info.working, label: describeBusinessDay(info) };
});
week.weekend; // => [7] // Sunday only in India
cells[1]; // => { date: '2026-10-02', working: false, label: 'Holiday: Gandhi Jayanti' }
cells[2]; // => { date: '2026-10-03', working: true, label: '' } // Saturday is a working day here
cells.filter((c) => !c.working).length; // => 5 // four Sundays and one holiday

How do I combine overlapping leave and count the days off?

Section titled “How do I combine overlapping leave and count the days off?”

mergeIntervals joins overlapping and back-to-back ranges; intervalLengthDays counts calendar days, while countBusinessDays counts only the working days the employee is actually away.

import { formatPlainDate, intervalGaps, intervalLengthDays, mergeIntervals } from '@absoluteui/helpers/date';
import { countBusinessDays, createBusinessCalendar, fixedHoliday } from '@absoluteui/helpers/business-calendar';
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
const requests = [
{ start: '2026-12-21', end: '2026-12-24' },
{ start: '2026-12-24', end: '2026-12-28' },
{ start: '2026-12-29', end: '2026-12-31' },
];
const [away] = mergeIntervals(requests);
away === undefined ? undefined : [away.start, away.end].map(formatPlainDate); // => ['2026-12-21', '2026-12-31']
away === undefined ? 0 : intervalLengthDays(away); // => 11 // calendar days
away === undefined ? 0 : countBusinessDays(away.start, away.end, { inclusive: 'both', calendar: cal }); // => 8 // without the weekend and Christmas
intervalGaps(requests, { start: '2026-12-01', end: '2026-12-31' }).length; // => 1 // 1-20 December is still free

How do I parse and display dates the user types?

Section titled “How do I parse and display dates the user types?”

parseDate is strict: the text must match the pattern exactly and be a real date, so 31/02/2026 is rejected instead of rolling into March. Display with formatDateIntl or formatDateRange, which never shift the date by a time zone.

import { formatDateIntl, formatDateRange, formatPlainDate, parseDate } from '@absoluteui/helpers/date';
const typed = parseDate('05/10/2026', 'dd/MM/yyyy');
typed === undefined ? undefined : formatPlainDate(typed); // => '2026-10-05'
parseDate('31/02/2026', 'dd/MM/yyyy'); // => undefined
formatDateIntl('2026-10-05', { locale: 'de', dateStyle: 'long' }); // => '5. Oktober 2026'
formatDateRange('2026-12-21', '2026-12-31', { locale: 'en' }); // => 'Dec 21 – 31, 2026'
  • Date strings must be YYYY-MM-DD; anything else throws HLP_INVALID_DATE instead of being guessed.
  • today() and isToday() use the runtime’s zone unless you pass one; pass the user’s zone so “today” does not flip at UTC midnight.
  • differenceInDays and addDays count calendar days. A “3-day SLA” usually means business days: use diffDays or addBusinessDays from the business calendar.
import { addDays, differenceInDays, formatPlainDate, toPlainDate } from '@absoluteui/helpers/date';
import { addBusinessDays } from '@absoluteui/helpers/business-calendar';
toPlainDate('10/05/2026'); // => throws HLP_INVALID_DATE
differenceInDays('2026-10-12', '2026-10-09'); // => 3 // Friday to Monday
formatPlainDate(addDays('2026-10-09', 3)); // => '2026-10-12' // Monday
formatPlainDate(addBusinessDays('2026-10-09', 3)); // => '2026-10-14' // Wednesday

addMonths

Adds calendar months, clamping to the target month’s last day (Jan 31 + 1 month = Feb 28/29).

function addMonths(date: DateInput, months: number): PlainDate;

Example:

addMonths('2024-01-31', 1) // => '2024-02-29'

addQuarters

Adds quarters (3 months each, month-end clamped).

function addQuarters(date: DateInput, quarters: number): PlainDate;

Example:

addQuarters('2026-11-30', 1) // => '2027-02-28'

addWeeks

Adds weeks (negative subtracts).

function addWeeks(date: DateInput, weeks: number): PlainDate;

Example:

addWeeks('2026-01-01', 2) // => '2026-01-15'

addYears

Adds years; Feb 29 clamps to Feb 28 in non-leap years.

function addYears(date: DateInput, years: number): PlainDate;

Example:

addYears('2024-02-29', 1) // => '2025-02-28'

age

Age in whole years on asOf (default: today in the runtime zone). Throws HLP_DATE_AGE_BEFORE_BIRTH when asOf precedes birthDate.

function age(birthDate: DateInput, asOf?: DateInput, options?: AgeOptions): number;

Example:

age('2000-02-29', '2025-02-28') // => 25 // 'mar1' → 24

assertIsoWeekday

Validates an ISO weekday option (1 = Monday … 7 = Sunday).

function assertIsoWeekday(value: number, property?: string): asserts value is IsoWeekday;

Example:

const weekday: number = 7; // e.g. from a form field
assertIsoWeekday(weekday, 'weekday'); // narrows to IsoWeekday
weekdayName(weekday, { locale: 'en' }); // => 'Sunday'
assertIsoWeekday(0, 'weekday'); // => throws HLP_DATE_INVALID_WEEKDAY

clampDate

Clamps date into the closed range [start, end].

function clampDate(date: DateInput, range: {
readonly start: DateInput;
readonly end: DateInput;
}): PlainDate;

Example:

clampDate('2026-12-25', { start: '2026-01-01', end: '2026-06-30' }) // => '2026-06-30'

closestTo

Candidate closest to date (ties: the first such candidate), or undefined for none.

function closestTo(date: DateInput, candidates: Iterable<DateInput>): PlainDate | undefined;

Example:

closestTo('2026-01-10', ['2026-01-01', '2026-01-12']) // => '2026-01-12'

dateDiff

Years, months, and days from from to to such that addDays(addMonths(from, years * 12 + months), days) equals to. When to is earlier, every field is the negation of dateDiff(to, from).

function dateDiff(from: DateInput, to: DateInput): DateDiff;

Example:

dateDiff('2024-01-31', '2024-03-01') // => { years: 0, months: 1, days: 1 }

dayOfYear

Day of the year, 1-366.

function dayOfYear(date: DateInput): number;

Example:

dayOfYear('2026-02-01') // => 32

differenceInDays

Calendar days from earlier to later (negative when later is earlier).

function differenceInDays(later: DateInput, earlier: DateInput): number;

Example:

differenceInDays('2026-03-01', '2026-02-01') // => 28

differenceInMonths

Complete calendar months, month-end aware (Jan 31 -> Feb 28 is one month).

function differenceInMonths(later: DateInput, earlier: DateInput): number;

Example:

differenceInMonths('2026-02-28', '2026-01-31') // => 1

differenceInQuarters

Complete quarters (3 months).

function differenceInQuarters(later: DateInput, earlier: DateInput): number;

Example:

differenceInQuarters('2026-12-31', '2026-01-01') // => 3

differenceInWeeks

Complete weeks (truncated toward zero).

function differenceInWeeks(later: DateInput, earlier: DateInput): number;

Example:

differenceInWeeks('2026-01-15', '2026-01-01') // => 2

differenceInYears

Complete years (Feb 29 -> Feb 28 of a common year counts as a year, like addYears).

function differenceInYears(later: DateInput, earlier: DateInput): number;

Example:

differenceInYears('2026-09-28', '2000-09-29') // => 25

endOfFiscalQuarter

Last day of the fiscal quarter containing date.

function endOfFiscalQuarter(date: DateInput, options?: FiscalYearOptions): PlainDate;

Example:

endOfFiscalQuarter('2026-09-29', { startMonth: 4 }) // => '2026-09-30'

endOfFiscalYear

Last day of the fiscal year containing date.

function endOfFiscalYear(date: DateInput, options?: FiscalYearOptions): PlainDate;

Example:

endOfFiscalYear('2026-09-29', { startMonth: 4 }) // => '2027-03-31'

endOfMonth

Last day of the month.

function endOfMonth(date: DateInput): PlainDate;

Example:

endOfMonth('2024-02-10') // => '2024-02-29'

endOfQuarter

Last day of the calendar quarter.

function endOfQuarter(date: DateInput): PlainDate;

Example:

endOfQuarter('2026-09-29') // => '2026-09-30'

endOfWeek

Last day of the week containing date.

function endOfWeek(date: DateInput, options?: WeekOptions): PlainDate;

Example:

endOfWeek('2026-09-30') // => '2026-10-04' // Sunday

endOfYear

December 31 of the year.

function endOfYear(date: DateInput): PlainDate;

Example:

endOfYear('2026-09-29') // => '2026-12-31'

fiscalQuarterLabel

Fiscal-quarter label Q1-Q4.

function fiscalQuarterLabel(date: DateInput, options?: FiscalYearOptions): string;

Example:

fiscalQuarterLabel('2026-09-29', { startMonth: 4 }) // => 'Q2'

fiscalQuarterOf

Fiscal quarter 1-4, counted from the fiscal start month.

function fiscalQuarterOf(date: DateInput, options?: FiscalYearOptions): 1 | 2 | 3 | 4;

Example:

fiscalQuarterOf('2026-09-29', { startMonth: 4 }) // => 2

fiscalYearLabel

Fiscal-year label such as FY2027 or FY2026-27.

function fiscalYearLabel(date: DateInput, options?: FiscalLabelOptions): string;

Example:

fiscalYearLabel('2026-09-29', { startMonth: 4 }) // => 'FY2027'

fiscalYearOf

Fiscal year number (see FiscalYearOptions for the naming convention).

function fiscalYearOf(date: DateInput, options?: FiscalYearOptions): number;

Example:

fiscalYearOf('2026-09-29', { startMonth: 4 }) // => 2027

isAfter

a is strictly after b.

function isAfter(a: DateInput, b: DateInput): boolean;

Example:

isAfter('2026-01-02', '2026-01-01') // => true

isBefore

a is strictly before b.

function isBefore(a: DateInput, b: DateInput): boolean;

Example:

isBefore('2026-01-01', '2026-01-02') // => true

isBetween

start &lt;= date &lt;= end by default; inclusive controls the endpoints. Throws HLP_DATE_INVALID_INTERVAL when end is before start.

function isBetween(date: DateInput, start: DateInput, end: DateInput, options?: {
readonly inclusive?: Inclusivity;
}): boolean;

Example:

isBetween('2026-01-31', '2026-01-01', '2026-01-31', { inclusive: 'start' }) // => false

isFuture

date is after today in timeZone.

function isFuture(date: DateInput, timeZone?: string): boolean;

Example:

isFuture('2999-01-01') // => true

isoWeek

ISO 8601 week number (1-53).

function isoWeek(date: DateInput): number;

Example:

isoWeek('2026-09-29') // => 40

isoWeeksInYear

ISO weeks in a year (53 when January 1 is a Thursday, or a Wednesday in a leap year).

function isoWeeksInYear(year: number): number;

Example:

isoWeeksInYear(2026) // => 53

isoWeekYear

ISO 8601 week-numbering year (differs from the calendar year around January 1).

function isoWeekYear(date: DateInput): number;

Example:

isoWeekYear('2027-01-01') // => 2026

isPast

date is before today in timeZone.

function isPast(date: DateInput, timeZone?: string): boolean;

Example:

isPast('2000-01-01') // => true

isSameDay

Same calendar day.

function isSameDay(a: DateInput, b: DateInput): boolean;

Example:

isSameDay('2026-01-01', plainDate(2026, 1, 1)) // => true

isSameMonth

Same month of the same year.

function isSameMonth(a: DateInput, b: DateInput): boolean;

Example:

isSameMonth('2026-09-01', '2026-09-30') // => true

isSameQuarter

Same calendar quarter of the same year.

function isSameQuarter(a: DateInput, b: DateInput): boolean;

Example:

isSameQuarter('2026-07-01', '2026-09-30') // => true

isSameWeek

Same week (weeks start on firstDayOfWeek, default Monday).

function isSameWeek(a: DateInput, b: DateInput, options?: WeekOptions): boolean;

Example:

isSameWeek('2026-09-28', '2026-10-04') // => true

isSameYear

Same calendar year.

function isSameYear(a: DateInput, b: DateInput): boolean;

Example:

isSameYear('2026-01-01', '2026-12-31') // => true

isToday

date is today in timeZone (default: runtime zone).

function isToday(date: DateInput, timeZone?: string): boolean;

Example:

isToday(today('Asia/Kolkata'), 'Asia/Kolkata') // => true

maxDate

Latest date, or undefined for none.

function maxDate(dates: Iterable<DateInput>): PlainDate | undefined;

Example:

maxDate(['2026-02-01', '2026-01-01']) // => '2026-02-01'

minDate

Earliest date, or undefined for none.

function minDate(dates: Iterable<DateInput>): PlainDate | undefined;

Example:

minDate(['2026-02-01', '2026-01-01']) // => '2026-01-01'

quarterOf

Calendar quarter (1-4; Q1 = January to March).

function quarterOf(date: DateInput): 1 | 2 | 3 | 4;

Example:

quarterOf('2026-09-29') // => 3

startOfFiscalQuarter

First day of the fiscal quarter containing date.

function startOfFiscalQuarter(date: DateInput, options?: FiscalYearOptions): PlainDate;

Example:

startOfFiscalQuarter('2026-09-29', { startMonth: 4 }) // => '2026-07-01'

startOfFiscalYear

First day of the fiscal year containing date.

function startOfFiscalYear(date: DateInput, options?: FiscalYearOptions): PlainDate;

Example:

startOfFiscalYear('2026-09-29', { startMonth: 4 }) // => '2026-04-01'

startOfMonth

First day of the month.

function startOfMonth(date: DateInput): PlainDate;

Example:

startOfMonth('2026-09-29') // => '2026-09-01'

startOfQuarter

First day of the calendar quarter.

function startOfQuarter(date: DateInput): PlainDate;

Example:

startOfQuarter('2026-09-29') // => '2026-07-01'

startOfWeek

First day of the week containing date.

function startOfWeek(date: DateInput, options?: WeekOptions): PlainDate;

Example:

startOfWeek('2026-09-30', { firstDayOfWeek: 7 }) // => '2026-09-27' // Sunday

startOfYear

January 1 of the year.

function startOfYear(date: DateInput): PlainDate;

Example:

startOfYear('2026-09-29') // => '2026-01-01'

subDays

Subtracts days.

function subDays(date: DateInput, days: number): PlainDate;

Example:

subDays('2026-03-01', 1) // => '2026-02-28'

subMonths

Subtracts months (month-end clamped).

function subMonths(date: DateInput, months: number): PlainDate;

Example:

subMonths('2026-03-31', 1) // => '2026-02-28'

subQuarters

Subtracts quarters.

function subQuarters(date: DateInput, quarters: number): PlainDate;

Example:

subQuarters('2026-05-31', 1) // => '2026-02-28'

subWeeks

Subtracts weeks.

function subWeeks(date: DateInput, weeks: number): PlainDate;

Example:

subWeeks('2026-01-15', 2) // => '2026-01-01'

subYears

Subtracts years.

function subYears(date: DateInput, years: number): PlainDate;

Example:

subYears('2028-02-29', 1) // => '2027-02-28'

weekOfMonth

1-based row of date in its month’s calendar (weeks start on firstDayOfWeek).

function weekOfMonth(date: DateInput, options?: WeekOptions): number;

Example:

weekOfMonth('2026-09-29') // => 5

weekOfYear

Week number and week-year under any week rule (default ISO: Monday, 4 minimal days; US: { firstDayOfWeek: 7, minimalDays: 1 }).

function weekOfYear(date: DateInput, options?: WeekNumberingOptions): {
readonly week: number;
readonly year: number;
};

Example:

weekOfYear('2027-01-01') // => { week: 53, year: 2026 }

weeksInWeekYear

Number of weeks (52 or 53) in a week-year under a week rule.

function weeksInWeekYear(year: number, options?: WeekNumberingOptions): number;

Example:

weeksInWeekYear(2026) // => 53

AgeOptions

interface AgeOptions {
readonly leapDayBirthday?: 'feb28' | 'mar1';
}

DateDiff

Age-style breakdown returned by dateDiff.

interface DateDiff {
readonly years: number;
readonly months: number;
readonly days: number;
}

FiscalLabelOptions

interface FiscalLabelOptions extends FiscalYearOptions {
readonly prefix?: string;
readonly style?: 'year' | 'span';
}

FiscalYearOptions

Fiscal-year options. Naming convention: a fiscal year is labelled by the calendar year in which it ends (default naming: 'end'; US federal FY2027 = Oct 2026 - Sep 2027, India April 2026 - March 2027 = FY2027). naming: 'start' labels by the starting year. With startMonth: 1 both conventions give the calendar year. Quarters are numbered from the fiscal start: Q1 is the first three months of the fiscal year.

interface FiscalYearOptions {
readonly startMonth?: number;
readonly naming?: 'end' | 'start';
}

WeekNumberingOptions

interface WeekNumberingOptions extends WeekOptions {
readonly minimalDays?: number;
}

WeekOptions

interface WeekOptions {
readonly firstDayOfWeek?: IsoWeekday;
}

Inclusivity

Which endpoints count as inside a range.

type Inclusivity = 'both' | 'none' | 'start' | 'end';

formatDate

Formats a date or date-time with a pattern (token table at the top of this module).

function formatDate(value: DateInput | PlainDateTime, pattern: string, options?: PatternOptions): string;

Example:

formatDate('2026-09-29', "EEE, d MMM yyyy 'Q'Q") // => 'Tue, 29 Sep 2026 Q3'

formatDateIntl

Formats a date or wall-clock date-time with Intl.DateTimeFormat (cached). The value is rendered in UTC internally, so the calendar date never shifts. Default { dateStyle: 'medium' }.

function formatDateIntl(value: DateInput | PlainDateTime, options?: DateIntlOptions): string;

Example:

formatDateIntl('2026-09-29', { locale: 'de', dateStyle: 'long' }) // => '29. September 2026'

formatDateRange

Formats a range with Intl.DateTimeFormat#formatRange (shared parts collapsed, “Jan 1 - 5, 2026”), falling back to start - end. Throws HLP_DATE_INVALID_INTERVAL when end &lt; start.

function formatDateRange(start: DateInput | PlainDateTime, end: DateInput | PlainDateTime, options?: DateIntlOptions): string;

Example:

formatDateRange('2026-01-01', '2026-01-05', { locale: 'en' }) // => 'Jan 1 – 5, 2026'

formatRelative

Describes value relative to base (default now) with the best unit, seconds to years (months and years use average Gregorian lengths).

function formatRelative(value: InstantInput, base?: InstantInput, options?: RelativeOptions): string;

Example:

formatRelative(Date.UTC(2026, 8, 29, 9), Date.UTC(2026, 8, 29, 12), { locale: 'en' }) // => '3 hours ago'

Example:

formatRelative(Date.now() - 3 * 3_600_000) // e.g. '3 hours ago' (relative to now, in the runtime locale)

formatRelativeDate

Describes a date relative to base (default today in the runtime zone) in calendar units: days under a week, then weeks, complete months, and complete years.

function formatRelativeDate(date: DateInput, base?: DateInput, options?: RelativeOptions): string;

Example:

formatRelativeDate('2026-09-28', '2026-09-29') // => 'yesterday'

parseDate

Strictly parses a date with a pattern; time tokens are validated and discarded.

function parseDate(text: string, pattern: string, options?: ParsePatternOptions): PlainDate | undefined;

Example:

parseDate('Sep 29, 2026', 'MMM d, yyyy', { locale: 'en' }) // => '2026-09-29'

parseDateTime

Strictly parses a date-time with a pattern (same tokens as formatDate). Returns undefined on any mismatch, impossible value, or inconsistent field (for example a weekday that does not match the date). The year is required; a missing month or day defaults to 1. h/hh require a.

function parseDateTime(text: string, pattern: string, options?: ParsePatternOptions): PlainDateTime | undefined;

Example:

parseDateTime('29/09/2026 14:05', 'dd/MM/yyyy HH:mm') // => '2026-09-29T14:05:00'

ParsePatternOptions

interface ParsePatternOptions extends PatternOptions {
readonly referenceYear?: number;
}

PatternOptions

interface PatternOptions {
readonly locale?: string;
}

RelativeOptions

interface RelativeOptions {
readonly locale?: string;
readonly numeric?: 'auto' | 'always';
readonly style?: 'long' | 'short' | 'narrow';
}

DateIntlOptions

Intl.DateTimeFormat options for wall-clock values (the zone is fixed internally).

type DateIntlOptions = Omit<Intl.DateTimeFormatOptions, 'timeZone' | 'timeZoneName'> & {
readonly locale?: string;
};

calendarMonthGrid

Weeks (rows of 7 days) covering a month, including adjacent-month days to fill the rows.

function calendarMonthGrid(year: number, month: number, options?: CalendarGridOptions): CalendarDay[][];

Example:

const grid = calendarMonthGrid(2026, 9, { firstDayOfWeek: 7 }); // Sunday-first
grid[0]?.[0]?.date; // => '2026-08-30'
grid[0]?.[0]?.inMonth; // => false
grid.length; // => 6 // fixed height by default
calendarMonthGrid(2026, 9, { firstDayOfWeek: 7, fixedWeeks: false }).length; // => 5

containsDate

True when date lies inside the closed interval.

function containsDate(interval: DateIntervalInput, date: DateInput): boolean;

Example:

containsDate({ start: '2026-01-01', end: '2026-01-31' }, '2026-01-31') // => true

dateInterval

Creates a validated, frozen closed interval; throws HLP_DATE_INVALID_INTERVAL when end &lt; start.

function dateInterval(start: DateInput, end: DateInput): DateInterval;

Example:

dateInterval('2026-01-01', '2026-01-31')

eachDayOfInterval

Lazily yields every step-th day of the interval (both endpoints included).

function eachDayOfInterval(interval: DateIntervalInput, options?: IterateOptions & {
readonly step?: number;
}): Generator<PlainDate, void, undefined>;

Example:

[...eachDayOfInterval({ start: '2026-01-30', end: '2026-02-02' })].length // => 4

eachMonthOfInterval

Lazily yields the first day of every month that overlaps the interval.

function eachMonthOfInterval(interval: DateIntervalInput, options?: IterateOptions): Generator<PlainDate, void, undefined>;

Example:

[...eachMonthOfInterval({ start: '2026-01-15', end: '2026-03-01' })] // => ['2026-01-01', '2026-02-01', '2026-03-01']

eachWeekOfInterval

Lazily yields the first day of every week that overlaps the interval (the first item may precede start).

function eachWeekOfInterval(interval: DateIntervalInput, options?: IterateOptions & WeekOptions): Generator<PlainDate, void, undefined>;

Example:

[...eachWeekOfInterval({ start: '2026-09-30', end: '2026-10-12' })] // => ['2026-09-28', '2026-10-05', '2026-10-12'] // Mondays

eachYearOfInterval

Lazily yields January 1 of every year that overlaps the interval.

function eachYearOfInterval(interval: DateIntervalInput, options?: IterateOptions): Generator<PlainDate, void, undefined>;

Example:

[...eachYearOfInterval({ start: '2025-06-01', end: '2026-01-01' })] // => ['2025-01-01', '2026-01-01']

intervalGaps

Uncovered sub-intervals of within (days not in any of intervals), ascending.

function intervalGaps(intervals: Iterable<DateIntervalInput>, within: DateIntervalInput): DateInterval[];

Example:

const booked = [{ start: '2026-01-03', end: '2026-01-04' }];
intervalGaps(booked, { start: '2026-01-01', end: '2026-01-06' }); // => [{ start: '2026-01-01', end: '2026-01-02' }, { start: '2026-01-05', end: '2026-01-06' }]

intervalIntersection

Shared days of two intervals, or undefined when they are disjoint.

function intervalIntersection(a: DateIntervalInput, b: DateIntervalInput): DateInterval | undefined;

Example:

intervalIntersection({ start: '2026-01-01', end: '2026-01-10' }, { start: '2026-01-05', end: '2026-02-01' }) // => { start: '2026-01-05', end: '2026-01-10' }

Example:

intervalIntersection({ start: '2026-01-01', end: '2026-01-04' }, { start: '2026-01-05', end: '2026-01-06' }) // => undefined

intervalLengthDays

Days in the interval, counting both endpoints.

function intervalLengthDays(interval: DateIntervalInput): number;

Example:

intervalLengthDays({ start: '2026-01-01', end: '2026-01-31' }) // => 31

intervalsOverlap

True when two closed intervals share at least one day. With inclusive: false, intervals that only touch at an endpoint (a.end === b.start) do not count.

function intervalsOverlap(a: DateIntervalInput, b: DateIntervalInput, options?: {
readonly inclusive?: boolean;
}): boolean;

Example:

intervalsOverlap({ start: '2026-01-01', end: '2026-01-05' }, { start: '2026-01-05', end: '2026-01-09' }) // => true

mergeIntervals

Sorts and merges overlapping intervals in O(n log n). Adjacent intervals (one ends the day before the next starts) merge too unless mergeAdjacent: false.

function mergeIntervals(intervals: Iterable<DateIntervalInput>, options?: {
readonly mergeAdjacent?: boolean;
}): DateInterval[];

Example:

const leave = [{ start: '2026-01-01', end: '2026-01-03' }, { start: '2026-01-04', end: '2026-01-06' }];
mergeIntervals(leave); // => [{ start: '2026-01-01', end: '2026-01-06' }]
mergeIntervals(leave, { mergeAdjacent: false }).length; // => 2

CalendarDay

One cell of calendarMonthGrid.

interface CalendarDay {
readonly date: PlainDate;
readonly inMonth: boolean;
readonly isToday: boolean;
readonly weekday: IsoWeekday;
}

CalendarGridOptions

interface CalendarGridOptions extends WeekOptions {
readonly fixedWeeks?: boolean;
readonly today?: DateInput;
}

DateInterval

Closed date interval: start and end are both included.

interface DateInterval {
readonly start: PlainDate;
readonly end: PlainDate;
}

DateIntervalInput

Interval input: dates as PlainDate or 'YYYY-MM-DD'.

interface DateIntervalInput {
readonly start: DateInput;
readonly end: DateInput;
}

IterateOptions

interface IterateOptions {
readonly limit?: number;
}

getWeekInfo

Week conventions for a locale (default: the runtime locale): first day, weekend days, and minimal days of week 1. Uses Intl.Locale#getWeekInfo() (or the older weekInfo accessor) when present, otherwise an embedded CLDR table. Locales without a region are maximized (ar -> ar-EG).

function getWeekInfo(locale?: string, options?: GetWeekInfoOptions): WeekInfo;

Example:

getWeekInfo('ar-SA') // => { firstDay: 7, weekend: [5, 6], minimalDays: 1 }

monthName

Localized month name for month 1-12.

function monthName(month: number, options?: MonthNameOptions): string;

Example:

monthName(9, { locale: 'ru', context: 'format' }) // => 'сентября'

monthNames

Twelve localized month names (January first).

function monthNames(locale?: string, style?: NameStyle, context?: 'standalone' | 'format'): string[];

Example:

monthNames('de', 'short').slice(0, 3) // => ['Jan', 'Feb', 'Mär']

Example:

monthNames('en').length // => 12

weekdayName

Localized weekday name.

function weekdayName(weekday: IsoWeekday, options?: NameOptions): string;

Example:

weekdayName(1, { locale: 'fr', style: 'long' }) // => 'lundi'

weekdayNames

Seven localized weekday names starting at firstDay (default 1, Monday).

function weekdayNames(locale?: string, firstDay?: IsoWeekday, style?: NameStyle): string[];

Example:

weekdayNames('en-US', 7, 'short') // => ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

GetWeekInfoOptions

interface GetWeekInfoOptions {
readonly useNative?: boolean;
}

MonthNameOptions

interface MonthNameOptions extends NameOptions {
readonly context?: 'standalone' | 'format';
}

NameOptions

interface NameOptions {
readonly locale?: string;
readonly style?: NameStyle;
}

WeekInfo

Locale week conventions (CLDR weekData).

interface WeekInfo {
readonly firstDay: IsoWeekday;
readonly weekend: readonly IsoWeekday[];
readonly minimalDays: number;
}

NameStyle

Width of a localized name.

type NameStyle = 'long' | 'short' | 'narrow';

addDays

Adds (or subtracts) days.

function addDays(date: DateInput, days: number): PlainDate;

Example:

addDays('2026-12-31', 1) // => '2027-01-01'

comparePlainDates

Orders two dates: -1, 0, or 1.

function comparePlainDates(a: DateInput, b: DateInput): -1 | 0 | 1;

Example:

['2026-02-01', '2026-01-01'].sort(comparePlainDates)

daysBetween

Calendar days from a to b (negative when b is earlier), endpoints exclusive of a.

function daysBetween(a: DateInput, b: DateInput): number;

Example:

daysBetween('2026-01-01', '2026-01-31') // => 30

daysInMonth

Days in month (1-12) of year; 0 for an invalid month.

function daysInMonth(year: number, month: number): number;

Example:

daysInMonth(2024, 2) // => 29

daysInYear

365 or 366.

function daysInYear(year: number): number;

Example:

daysInYear(2024) // => 366

formatPlainDate

ISO 8601 YYYY-MM-DD (extended years as +/-YYYYYY).

function formatPlainDate(date: PlainDate): string;

Example:

formatPlainDate(plainDate(2026, 9, 29)) // => '2026-09-29'

fromEpochDay

Date for a count of days since 1970-01-01.

function fromEpochDay(epochDay: number): PlainDate;

Example:

fromEpochDay(0) // => '1970-01-01'

isLeapYear

True for Gregorian leap years.

function isLeapYear(year: number): boolean;

Example:

isLeapYear(2024) // => true

isoWeekday

ISO weekday (1 = Monday … 7 = Sunday).

function isoWeekday(date: DateInput): IsoWeekday;

Example:

isoWeekday('2026-09-29') // => 2 // Tuesday

isPlainDate

Type guard for PlainDate-shaped values (including core’s).

function isPlainDate(value: unknown): value is PlainDate;

Example:

isPlainDate(plainDate(2026, 1, 1)) // => true

parsePlainDate

Parses YYYY-MM-DD strictly. Returns undefined for anything else, including impossible dates (2026-02-30) and date-times.

function parsePlainDate(text: string): PlainDate | undefined;

Example:

parsePlainDate('2026-09-29')

plainDate

Creates a validated, frozen date-only value.

function plainDate(year: number, month: number, day: number): PlainDate;

Example:

plainDate(2026, 2, 28)

plainDateFromInstant

The calendar date of an instant (epoch ms or Date) as observed in timeZone. Throws HLP_TZ_INVALID_ZONE for an unknown zone.

function plainDateFromInstant(instant: number | Date, timeZone?: string): PlainDate;

Example:

plainDateFromInstant(Date.UTC(2026, 0, 1, 20), 'Asia/Kolkata') // => '2026-01-02'

today

Today’s date in timeZone (default: the runtime’s zone).

function today(timeZone?: string): PlainDate;

Example:

today('Asia/Kolkata') // e.g. '2026-09-30' (the current date in Kolkata)

toEpochDay

Days since 1970-01-01 for a date.

function toEpochDay(date: PlainDate): number;

Example:

toEpochDay(plainDate(1970, 1, 2)) // => 1

toPlainDate

Normalizes PlainDate | 'YYYY-MM-DD'; throws HLP_INVALID_DATE for bad strings.

function toPlainDate(input: DateInput): PlainDate;

Example:

toPlainDate('2026-09-29')

MAX_YEAR

Supported year range (matches @absoluteui/core).

const MAX_YEAR = 275759;

Example:

plainDate(MAX_YEAR, 1, 1)

PlainDate

interface PlainDate {
readonly kind: 'PlainDate';
readonly year: number;
readonly month: number;
readonly day: number;
}

DateInput

Anything accepted where a date-only value is expected. Strings must be YYYY-MM-DD.

type DateInput = PlainDate | string;

Example:

const d: DateInput = '2026-09-29';

IsoWeekday

ISO weekday: 1 = Monday … 7 = Sunday.

type IsoWeekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;

Example:

const saturday: IsoWeekday = 6;

comparePlainDateTimes

Orders two date-times: -1, 0, or 1.

function comparePlainDateTimes(a: PlainDateTime, b: PlainDateTime): -1 | 0 | 1;

Example:

const a = plainDateTime('2026-10-05', plainTime(9, 0));
const b = plainDateTime('2026-10-05', plainTime(17, 30));
comparePlainDateTimes(a, b); // => -1
comparePlainDateTimes(b, a); // => 1
[b, a].sort(comparePlainDateTimes); // => [a, b]

comparePlainTimes

Orders two times: -1, 0, or 1.

function comparePlainTimes(a: PlainTime, b: PlainTime): -1 | 0 | 1;

Example:

comparePlainTimes(plainTime(9), plainTime(10)) // => -1

formatInstant

ISO 8601 UTC string for an instant (Date#toISOString).

function formatInstant(instant: number | Date): string;

Example:

formatInstant(0) // => '1970-01-01T00:00:00.000Z'

formatPlainDateTime

ISO 8601 YYYY-MM-DDTHH:mm:ss[.SSS].

function formatPlainDateTime(value: PlainDateTime): string;

Example:

formatPlainDateTime(plainDateTime('2026-03-08', plainTime(2, 30))) // => '2026-03-08T02:30:00'

formatPlainTime

ISO 8601 HH:mm:ss plus .SSS when milliseconds are non-zero.

function formatPlainTime(time: PlainTime): string;

Example:

formatPlainTime(plainTime(9, 5)) // => '09:05:00'

isPlainDateTime

Type guard for PlainDateTime-shaped values (including core’s).

function isPlainDateTime(value: unknown): value is PlainDateTime;

Example:

isPlainDateTime(plainDateTime('2026-01-01')) // => true

isPlainTime

Type guard for PlainTime-shaped values (including core’s).

function isPlainTime(value: unknown): value is PlainTime;

Example:

isPlainTime(plainTime(12)) // => true

parseInstant

Parses an ISO 8601 instant (date-time with a mandatory Z or +/-HH:MM) to epoch milliseconds. Returns undefined for text without an offset or outside the Date range.

function parseInstant(text: string): number | undefined;

Example:

parseInstant('2026-03-08T07:00:00Z') // => 1772953200000

parseIsoDateTime

Parses YYYY-MM-DDTHH:mm[:ss[.SSS]] with an optional Z or +/-HH:MM suffix. Strict: uppercase T, no spaces, no impossible dates or times. Returns undefined otherwise.

function parseIsoDateTime(text: string): IsoDateTimeParts | undefined;

Example:

parseIsoDateTime('2026-03-08T02:30:00-05:00'); // => { dateTime: '2026-03-08T02:30:00', offset: -18000000 }
parseIsoDateTime('2026-03-08T02:30')?.offset; // => undefined // no offset in the text
parseIsoDateTime('2026-02-30T10:00Z'); // => undefined // impossible date

parsePlainDateTime

Parses a date-time without an offset (YYYY-MM-DDTHH:mm[:ss[.SSS]]). Text with Z or an offset is rejected because it denotes an instant, not a wall-clock time.

function parsePlainDateTime(text: string): PlainDateTime | undefined;

Example:

parsePlainDateTime('2026-03-08T02:30') // => '2026-03-08T02:30:00'

parsePlainTime

Parses HH:mm[:ss[.SSS]] strictly (1-3 fraction digits). Returns undefined otherwise, including 24:00 and leap seconds.

function parsePlainTime(text: string): PlainTime | undefined;

Example:

parsePlainTime('09:30') // => '09:30:00'

plainDateTime

Creates a frozen date-time from a date and a time (default midnight).

function plainDateTime(date: DateInput, time?: PlainTime): PlainDateTime;

Example:

plainDateTime('2026-03-08', plainTime(2, 30))

plainDateTimeToEpochMilliseconds

Wall-clock milliseconds as if the date-time were UTC (“local epoch”). Internal building block for zone conversion; exported for business-calendar arithmetic.

function plainDateTimeToEpochMilliseconds(value: PlainDateTime): number;

Example:

plainDateTimeToEpochMilliseconds(plainDateTime('1970-01-02')) // => 86400000

plainTime

Creates a validated, frozen wall-clock time.

function plainTime(hour: number, minute?: number, second?: number, millisecond?: number): PlainTime;

Example:

plainTime(9, 30) // => '09:30:00'

timeFromMilliseconds

Time for milliseconds since midnight (wraps modulo one day, negative values wrap backwards).

function timeFromMilliseconds(ms: number): PlainTime;

Example:

timeFromMilliseconds(5_400_000) // => '01:30:00'

timeToMilliseconds

Milliseconds since midnight.

function timeToMilliseconds(time: PlainTime): number;

Example:

timeToMilliseconds(plainTime(1)) // => 3600000

IsoDateTimeParts

Parsed ISO 8601 date-time: wall-clock fields plus the offset when one was written.

interface IsoDateTimeParts {
readonly dateTime: PlainDateTime;
readonly offset: number | undefined;
}

PlainDateTime

interface PlainDateTime {
readonly kind: 'PlainDateTime';
readonly date: PlainDate;
readonly time: PlainTime;
}

PlainTime

interface PlainTime {
readonly kind: 'PlainTime';
readonly hour: number;
readonly minute: number;
readonly second: number;
readonly millisecond: number;
}

endOfDayInstant

Last millisecond of a calendar day in a zone (one before the next day’s startOfDayInstant).

function endOfDayInstant(date: DateInput, timeZone?: string): number;

Example:

endOfDayInstant('2026-11-01', 'America/New_York'); // => Date.UTC(2026, 10, 2, 5) - 1 // 23:59:59.999 EST
endOfDayInstant('2026-11-01', 'America/New_York') - startOfDayInstant('2026-11-01', 'America/New_York'); // => 25 * 3_600_000 - 1 // a 25-hour day

startOfDayInstant

First instant (epoch ms) of a calendar day in a zone. DST-safe: when midnight is skipped (for example America/Santiago or Asia/Beirut at their spring transitions) it returns the first instant that exists on that date, as Temporal’s startOfDay does.

function startOfDayInstant(date: DateInput, timeZone?: string): number;

Example:

startOfDayInstant('2026-10-05', 'Asia/Kolkata'); // => Date.UTC(2026, 9, 4, 18, 30) // 00:00 IST
startOfDayInstant('2026-03-29', 'Asia/Beirut'); // => Date.UTC(2026, 2, 28, 22) // midnight is skipped: 01:00 local (+03:00)

toPlainDateTime

Wall-clock date-time of an instant in timeZone (alias of the timezone module’s instantToZoned, named for the date module’s instant conventions).

function toPlainDateTime(instant: InstantInput, timeZone?: string): PlainDateTime;

Example:

toPlainDateTime(Date.UTC(2026, 6, 1, 12), 'Europe/London'); // => '2026-07-01T13:00:00' // BST
toPlainDateTime(Date.UTC(2026, 11, 1, 12), 'Europe/London'); // => '2026-12-01T12:00:00' // GMT

InstantInput

An exact instant: epoch milliseconds or a valid Date.

type InstantInput = number | Date;

Example:

const i: InstantInput = Date.now();