Skip to content

@absoluteui/helpers/business-calendar

Weekends, holidays, business days, business hours, SLA deadlines, and business-day differences.

import { ... } from '@absoluteui/helpers/business-calendar';

Weekends, holidays, half days, and working hours as one immutable calendar object, with the questions HR, payroll, and support screens ask: is this a business day, how many working days are in this leave request, when is the pay date, when is this ticket due. Every count says which mode it uses (calendar days, weekdays, or business days) and which ends it includes. The Business calendars guide explains the holiday rules, the three counting modes, and SLA deadlines in depth; the recipes below combine them into common tasks.

How do I count a leave request and explain the days that do not count?

Section titled “How do I count a leave request and explain the days that do not count?”

Leave includes both ends, so use inclusive: 'both'. List the non-working days in the range with getBusinessDay and describeBusinessDay, so the form can say why a 9-day request costs fewer days.

import { eachDayOfInterval, formatPlainDate } from '@absoluteui/helpers/date';
import { createBusinessCalendar, describeBusinessDay, diffDays, fixedHoliday, getBusinessDay } from '@absoluteui/helpers/business-calendar';
const cal = createBusinessCalendar({
holidays: [fixedHoliday('Christmas Day', 12, 25), fixedHoliday('Boxing Day', 12, 26, { observed: 'next-weekday' })],
halfDays: [fixedHoliday('Christmas Eve', 12, 24)],
});
const request = { start: '2026-12-21', end: '2026-12-29' };
diffDays(request.start, request.end, { mode: 'calendar', inclusive: 'both', calendar: cal }); // => 9
diffDays(request.start, request.end, { mode: 'business', inclusive: 'both', calendar: cal }); // => 4.5
const notes = [...eachDayOfInterval(request)]
.map((d) => ({ date: formatPlainDate(d), info: getBusinessDay(d, cal) }))
.filter((d) => d.info.businessDayValue < 1)
.map((d) => `${d.date}: ${describeBusinessDay(d.info)}`);
notes; // => ['2026-12-24: Half day: Christmas Eve', '2026-12-25: Holiday: Christmas Day', '2026-12-26: Holiday: Boxing Day', '2026-12-27: Weekend', '2026-12-28: Holiday: Boxing Day']

Boxing Day falls on a Saturday in 2026 and is observed on Monday 28th. Both dates carry its name (the Saturday entry has movedTo set), but only the Monday costs a working day.

How do I set pay dates and payroll cut-offs?

Section titled “How do I set pay dates and payroll cut-offs?”

“Paid on the 25th” usually means “or the business day before if the 25th is not one”: that is modifiedPreceding. Cut-offs count back in business days with a negative addBusinessDays, and lastBusinessDayOfMonth covers “paid on the last working day”.

import { formatPlainDate, plainDate } from '@absoluteui/helpers/date';
import { addBusinessDays, adjustBusinessDay, createBusinessCalendar, fixedHoliday, lastBusinessDayOfMonth } from '@absoluteui/helpers/business-calendar';
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
const payDate = (year: number, month: number) => adjustBusinessDay(plainDate(year, month, 25), 'modifiedPreceding', cal);
formatPlainDate(payDate(2026, 10)); // => '2026-10-23' // Sunday 25th, so Friday
formatPlainDate(payDate(2026, 12)); // => '2026-12-24' // Christmas Day
formatPlainDate(addBusinessDays(payDate(2026, 12), -3, cal)); // => '2026-12-21' // cut-off: three business days earlier
const last = lastBusinessDayOfMonth(2026, 10, cal);
last === undefined ? undefined : formatPlainDate(last); // => '2026-10-30'

How do I give each office its own calendar?

Section titled “How do I give each office its own calendar?”

Keep one calendar per country with regional holidays tagged, scope it per office with withTags, and merge in company-wide days with mergeCalendars. weekendForLocale supplies the local weekend when you do not know it.

import { createBusinessCalendar, fixedHoliday, isBusinessDay, mergeCalendars, weekendForLocale, withTags } from '@absoluteui/helpers/business-calendar';
const india = createBusinessCalendar({
weekend: weekendForLocale('en-IN'),
timeZone: 'Asia/Kolkata',
holidays: [
fixedHoliday('Gandhi Jayanti', 10, 2),
fixedHoliday('Maharashtra Day', 5, 1, { tags: ['IN-MH'] }),
fixedHoliday('Kannada Rajyotsava', 11, 1, { tags: ['IN-KA'], observed: 'next-weekday' }),
],
});
const company = createBusinessCalendar({ holidays: [fixedHoliday('Founders Day', 3, 16)] });
const bengaluru = mergeCalendars([withTags(india, ['IN-KA']), company], { name: 'Bengaluru office' });
const mumbai = mergeCalendars([withTags(india, ['IN-MH']), company], { name: 'Mumbai office' });
india.weekend; // => [7]
isBusinessDay('2026-05-01', bengaluru); // => true
isBusinessDay('2026-05-01', mumbai); // => false // Maharashtra Day
isBusinessDay('2026-11-02', bengaluru); // => false // Rajyotsava on Sunday, observed Monday
isBusinessDay('2026-03-16', mumbai); // => false // Founders Day everywhere

How do I use holidays that come from a server?

Section titled “How do I use holidays that come from a server?”

Providers must be synchronous, so load the data first and then create the calendar. Returned holidays are used as given (no observed shift), and results are cached per year.

import { createBusinessCalendar, isHoliday, type ProvidedHoliday } from '@absoluteui/helpers/business-calendar';
// e.g. const byYear = await api.get('/holidays?country=IN&from=2026&to=2027');
const byYear: Readonly<Record<number, readonly ProvidedHoliday[]>> = {
2026: [{ date: '2026-11-08', name: 'Diwali' }, { date: '2026-03-04', name: 'Holi' }],
};
const cal = createBusinessCalendar({ weekend: [7], providers: [{ holidaysFor: (year) => byYear[year] ?? [] }] });
isHoliday('2026-11-08', cal)?.name; // => 'Diwali'
isHoliday('2027-11-08', cal); // => undefined // no data loaded for 2027

How do I report working days per month of a fiscal year?

Section titled “How do I report working days per month of a fiscal year?”

businessDaysIn accepts a month, quarter, calendar year, or fiscal period, and periodRange gives its dates. countHolidays separates holidays on working days from those on weekends, which cost no working time.

import { addMonths, formatPlainDate } from '@absoluteui/helpers/date';
import { businessDaysIn, countHolidays, createBusinessCalendar, fixedHoliday, periodRange } from '@absoluteui/helpers/business-calendar';
const cal = createBusinessCalendar({
fiscalYearStartMonth: 4,
holidays: [fixedHoliday('Independence Day', 8, 15), fixedHoliday('Gandhi Jayanti', 10, 2), fixedHoliday('Christmas Day', 12, 25), fixedHoliday('Republic Day', 1, 26)],
});
const [start, end] = periodRange({ fiscalYear: 2027 }, cal);
[formatPlainDate(start), formatPlainDate(end)]; // => ['2026-04-01', '2027-03-31']
const months = Array.from({ length: 12 }, (_, i) => addMonths(start, i));
const perMonth = months.map((m) => businessDaysIn({ year: m.year, month: m.month }, cal));
perMonth.slice(6, 9); // => [21, 21, 22] // Oct (Gandhi Jayanti), Nov, Dec (Christmas)
perMonth.reduce((a, b) => a + b, 0) === businessDaysIn({ fiscalYear: 2027 }, cal); // => true
countHolidays(start, end, { inclusive: 'both', calendar: cal }); // => { total: 4, onWeekdays: 3, onWeekends: 1 } // 15 Aug 2026 is a Saturday
  • Invalid definitions fail at creation with an HLP_BIZCAL_* code, never later during a count.
  • A calendar with no working days cannot move to one: nextBusinessDay throws HLP_BIZCAL_NO_WORKING_DAYS.
  • There are no “business months”: weeks, months, and years are calendar units (differenceInMonths in @absoluteui/helpers/date); business modes exist only for days and hours.
  • Without a calendar argument you get the default: Saturday and Sunday off, no holidays, UTC.
import { createBusinessCalendar, isBusinessDay, nextBusinessDay } from '@absoluteui/helpers/business-calendar';
createBusinessCalendar({ fiscalYearStartMonth: 0 }); // => throws HLP_BIZCAL_INVALID_FISCAL
nextBusinessDay('2026-10-05', createBusinessCalendar({ weekend: [1, 2, 3, 4, 5, 6, 7] })); // => throws HLP_BIZCAL_NO_WORKING_DAYS
isBusinessDay('2026-12-25'); // => true // the default calendar knows no holidays

addBusinessDays

Moves n business days (negative moves back); half days count as one business day. The result is always a business day for n != 0. Runs in O(log n) range counts, never day by day. Inverse: diffDays(d, addBusinessDays(d, n), { mode: 'business', inclusive: 'end', halfDayValue: 1 }) === n for n > 0 (use inclusive: 'start' for n &lt; 0).

function addBusinessDays(date: DateInput, n: number, calendar?: BusinessCalendar, options?: AddBusinessDaysOptions): PlainDate;

Example:

addBusinessDays('2026-10-02', 1) // => '2026-10-05'

Example:

addBusinessDays('2026-10-05', -1) // => '2026-10-02'

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25), fixedHoliday('New Year', 1, 1)] });
addBusinessDays('2026-12-23', 5, cal); // => '2026-12-31' // Christmas and the weekend are skipped
addBusinessDays('2026-12-26', 0, cal); // => '2026-12-28' // zero: 'following'
addBusinessDays('2026-12-26', 0, cal, { zero: 'none' }); // => '2026-12-26'

adjustBusinessDay

Rolls a non-business date by a convention; business days are returned unchanged.

function adjustBusinessDay(date: DateInput, convention: BusinessDayConvention, calendar?: BusinessCalendar): PlainDate;

Example:

adjustBusinessDay('2026-10-31', 'following'); // => '2026-11-02' // Saturday → Monday
adjustBusinessDay('2026-10-31', 'modifiedFollowing'); // => '2026-10-30' // Monday is November, so Friday
adjustBusinessDay('2026-10-28', 'modifiedFollowing'); // => '2026-10-28' // business days stay

businessDayAt

getBusinessDay for the calendar date of an instant in the calendar’s time zone.

function businessDayAt(instant: InstantInput, calendar?: BusinessCalendar): BusinessDayInfo;

Example:

const indiaCal = createBusinessCalendar({ timeZone: 'Asia/Kolkata' });
const instant = Date.UTC(2026, 9, 2, 20); // Friday 20:00 UTC
businessDayAt(instant, indiaCal).date; // => '2026-10-03' // already Saturday 01:30 in Kolkata
businessDayAt(instant, indiaCal).working; // => false
businessDayAt(instant).date; // => '2026-10-02' // the default calendar uses UTC

businessDayOfMonth

1-based index of a business day within its month; undefined for non-business days.

function businessDayOfMonth(date: DateInput, calendar?: BusinessCalendar): number | undefined;

Example:

businessDayOfMonth('2026-10-05') // => 3

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Gandhi Jayanti', 10, 2)] });
businessDayOfMonth('2026-10-05', cal); // => 2
businessDayOfMonth('2026-10-02', cal); // => undefined // holiday

businessDaysIn

Business days in a period, half days weighted (halfDayValue, default the calendar’s).

function businessDaysIn(period: BusinessPeriod, calendar?: BusinessCalendar, options?: {
readonly halfDayValue?: number;
}): number;

Example:

businessDaysIn({ year: 2026, month: 10 }) // => 22

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)], halfDays: [fixedHoliday('Christmas Eve', 12, 24)] });
businessDaysIn({ year: 2026, month: 12 }, cal); // => 21.5 // 23 weekdays − Christmas − half of Christmas Eve
businessDaysIn({ year: 2026, month: 12 }, cal, { halfDayValue: 1 }); // => 22

countBusinessDays

Business days between two dates (never negative), half days weighted.

function countBusinessDays(a: DateInput, b: DateInput, options?: CountOptions & {
readonly halfDayValue?: number;
}): number;

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)], halfDays: [fixedHoliday('Christmas Eve', 12, 24)] });
countWeekdays('2026-12-21', '2026-12-25', { inclusive: 'both', calendar: cal }); // => 5
countBusinessDays('2026-12-21', '2026-12-25', { inclusive: 'both', calendar: cal }); // => 3.5 // Christmas off, Christmas Eve half
countBusinessDays('2026-12-21', '2026-12-25', { inclusive: 'both', calendar: cal, halfDayValue: 1 }); // => 4

countHolidays

Counts full-holiday dates between two dates (never negative); see HolidayCount.

function countHolidays(a: DateInput, b: DateInput, options?: CountOptions): HolidayCount;

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('Republic Day', 1, 26), fixedHoliday('Independence Day', 8, 15), fixedHoliday('Christmas Day', 12, 25)],
});
countHolidays('2026-01-01', '2026-12-31', { inclusive: 'both', calendar: cal }).onWeekdays; // => 2
countHolidays('2026-08-01', '2026-08-31', { inclusive: 'both', calendar: cal }); // => { total: 1, onWeekdays: 0, onWeekends: 1 }

countWeekdays

Weekdays (non-weekend days, holidays included) between two dates (never negative).

function countWeekdays(a: DateInput, b: DateInput, options?: CountOptions): number;

Example:

countWeekdays('2026-09-28', '2026-10-02', { inclusive: 'both' }) // => 5

countWeekends

Weekend days between two dates (never negative; argument order does not matter).

function countWeekends(a: DateInput, b: DateInput, options?: CountOptions): number;

Example:

countWeekends('2026-10-01', '2026-10-31', { inclusive: 'both' }) // => 9

Example:

countWeekends('2026-10-01', '2026-10-31', { inclusive: 'both', calendar: createBusinessCalendar({ weekend: [5, 6] }) }) // => 10 // five Fridays and five Saturdays

diffBreakdown

Calendar days, weekdays, weekend days, holidays, half days, business days, and nominal business hours of a range in one call (see DiffBreakdown).

function diffBreakdown(a: DateInput, b: DateInput, calendar?: BusinessCalendar, options?: {
readonly inclusive?: Inclusivity;
readonly halfDayValue?: number;
}): DiffBreakdown;

Example:

diffBreakdown('2026-09-28', '2026-10-02', undefined, { inclusive: 'both' }).businessHours; // => 40 // Mon..Fri, 8 h a day

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('Christmas Day', 12, 25), fixedHoliday('Boxing Day', 12, 26)],
halfDays: [fixedHoliday('Christmas Eve', 12, 24)],
});
const b = diffBreakdown('2026-12-21', '2026-12-27', cal, { inclusive: 'both' }); // Mon..Sun
[b.calendarDays, b.weekdays, b.weekendDays]; // => [7, 5, 2]
[b.holidays, b.holidaysOnWeekends, b.halfDays, b.nonWorkingWeekdays]; // => [1, 1, 1, 1] // Boxing Day is a Saturday
b.businessDays; // => 3.5 // 5 weekdays − Christmas − half of Christmas Eve
b.businessHours; // => 28 // 3 × 8 h + 4 h on Christmas Eve

diffDays

Days from a to b: calendar days, weekdays (non-weekend), or business days (weekdays minus holidays, plus working exceptions, half days weighted). Negative when b is before a. Week, month, quarter, and year differences are always calendar-based (B1): use differenceInMonths and friends from @absoluteui/helpers/date.

function diffDays(a: DateInput, b: DateInput, options?: DiffDaysOptions): number;

Example:

diffDays('2026-09-28', '2026-10-02') // => 4

Example:

diffDays('2026-09-28', '2026-10-02', { mode: 'business', inclusive: 'both' }) // => 5 // Mon..Fri leave

Example:

diffDays('2026-10-02', '2026-09-28', { mode: 'weekdays' }) // => -4

Example:

// Fri 25 Dec 2026 to Fri 1 Jan 2027, with Christmas and New Year as holidays.
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25), fixedHoliday('New Year', 1, 1)] });
diffDays('2026-12-25', '2027-01-01', { mode: 'calendar', calendar: cal }); // => 7
diffDays('2026-12-25', '2027-01-01', { mode: 'weekdays', calendar: cal }); // => 5
diffDays('2026-12-25', '2027-01-01', { mode: 'business', calendar: cal }); // => 4
diffDays('2026-12-25', '2027-01-01', { mode: 'business', inclusive: 'both', calendar: cal }); // => 4 // New Year is a holiday too

firstBusinessDayOfMonth

First business day of a month, or undefined when there is none.

function firstBusinessDayOfMonth(year: number, month: number, calendar?: BusinessCalendar): PlainDate | undefined;

Example:

firstBusinessDayOfMonth(2026, 11) // => '2026-11-02' // 1 November is a Sunday

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('New Year', 1, 1)] });
firstBusinessDayOfMonth(2027, 1, cal); // => '2027-01-04' // Friday 1st is a holiday

getBusinessDay

Full information about a date: working, weekend, holidays, exception, half day, hours, and an accessible reason (reasonCode + reason text).

function getBusinessDay(date: DateInput, calendar?: BusinessCalendar): BusinessDayInfo;

Example:

getBusinessDay('2026-10-03').reasonCode // => 'weekend'

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
const info = getBusinessDay('2026-12-25', cal);
[info.working, info.weekend, info.reasonCode, info.reason]; // => [false, false, 'holiday', 'Christmas Day']
getBusinessDay('2026-12-28', cal).reasonCode; // => null // an ordinary working day

holidaysOn

Every holiday entry on a date (several rules may coincide), including shifted originals (movedTo) and half days.

function holidaysOn(date: DateInput, calendar?: BusinessCalendar): readonly ResolvedHoliday[];

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25, { observed: 'nearest-weekday' })] });
holidaysOn('2026-12-25', cal).map((h) => h.name); // => ['Christmas Day']
holidaysOn('2027-12-25', cal).map((h) => h.movedTo); // => ['2027-12-24'] // Saturday, observed Friday
holidaysOn('2026-12-28', cal); // => []

isBusinessDay

True for business days: working weekdays that are not holidays, plus working exceptions. Half days are business days.

function isBusinessDay(date: DateInput, calendar?: BusinessCalendar): boolean;

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('Christmas Day', 12, 25)],
exceptions: [{ date: '2026-12-19', working: true, reason: 'Year-end close' }],
});
isBusinessDay('2026-12-24', cal); // => true // Thursday
isBusinessDay('2026-12-25', cal); // => false // holiday
isBusinessDay('2026-12-26', cal); // => false // Saturday
isBusinessDay('2026-12-19', cal); // => true // working Saturday exception

isHalfDay

True for working half days.

function isHalfDay(date: DateInput, calendar?: BusinessCalendar): boolean;

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('Christmas Day', 12, 25, { observed: 'nearest-weekday' })],
halfDays: [fixedHoliday('Christmas Eve', 12, 24)],
});
isHalfDay('2026-12-24', cal); // => true // Thursday
isBusinessDay('2026-12-24', cal); // => true // half days are business days
isHalfDay('2027-12-24', cal); // => false // the observed Christmas Day (a full holiday) wins

isHoliday

The full (non-half-day) holiday on a date, preferring one that is in effect there over a weekend original that was shifted elsewhere; undefined when there is none. Half days are reported by isHalfDay.

function isHoliday(date: DateInput, calendar?: BusinessCalendar): ResolvedHoliday | undefined;

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25, { observed: 'nearest-weekday' })] });
isHoliday('2027-12-24', cal)?.observed; // => true // Christmas on Saturday, observed Friday
isHoliday('2027-12-25', cal)?.movedTo; // => '2027-12-24'
isHoliday('2027-12-27', cal); // => undefined

isWeekday

True when the date is not a weekend day (it may still be a holiday).

function isWeekday(date: DateInput, calendar?: BusinessCalendar): boolean;

Example:

isWeekday('2026-10-02') // => true // Friday

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
isWeekday('2026-12-25', cal); // => true // a holiday is still a weekday
isBusinessDay('2026-12-25', cal); // => false

isWeekend

True when the weekday pattern makes the date a weekend day (holidays and exceptions do not matter). Default calendar: Saturday/Sunday.

function isWeekend(date: DateInput, calendar?: BusinessCalendar): boolean;

Example:

isWeekend('2026-10-03') // => true // Saturday

Example:

isWeekend('2026-10-02', createBusinessCalendar({ weekend: [5, 6] })) // => true // Friday

lastBusinessDayOfMonth

Last business day of a month, or undefined when there is none.

function lastBusinessDayOfMonth(year: number, month: number, calendar?: BusinessCalendar): PlainDate | undefined;

Example:

lastBusinessDayOfMonth(2026, 10) // => '2026-10-30' // 31 October is a Saturday

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('New Year', 1, 1, { observed: 'nearest-weekday' })] });
lastBusinessDayOfMonth(2027, 12, cal); // => '2027-12-30' // New Year 2028 (Saturday) is observed on Friday 31st

listBusinessDays

Lazily yields the business days from from to to inclusive, in that direction (descending when to is earlier).

function listBusinessDays(from: DateInput, to: DateInput, calendar?: BusinessCalendar, options?: ListBusinessDaysOptions): Generator<PlainDate, void, undefined>;

Example:

[...listBusinessDays('2026-10-01', '2026-10-07')].length // => 5

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
[...listBusinessDays('2026-12-23', '2026-12-29', cal)]; // => ['2026-12-23', '2026-12-24', '2026-12-28', '2026-12-29']
[...listBusinessDays('2026-12-31', '2026-12-01', cal, { limit: 2 })]; // => ['2026-12-31', '2026-12-30'] // descending

listHolidays

Holiday entries between two dates (inclusive, either order), ascending by date. Ranges may span at most 1000 years.

function listHolidays(from: DateInput, to: DateInput, calendar?: BusinessCalendar): ResolvedHoliday[];

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('New Year', 1, 1), easterHoliday('Good Friday', -2), fixedHoliday('Christmas Day', 12, 25)],
});
const holidays = listHolidays('2026-01-01', '2026-12-31', cal);
holidays.map((h) => h.name); // => ['New Year', 'Good Friday', 'Christmas Day']
holidays.map((h) => h.date); // => ['2026-01-01', '2026-04-03', '2026-12-25']

nextBusinessDay

The first business day after date (or on it with includeSelf). Throws HLP_BIZCAL_NO_WORKING_DAYS when the calendar has no business day in reach.

function nextBusinessDay(date: DateInput, calendar?: BusinessCalendar, options?: NextBusinessDayOptions): PlainDate;

Example:

nextBusinessDay('2026-10-02') // => '2026-10-05' // Friday → Monday

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
nextBusinessDay('2026-12-24', cal); // => '2026-12-28' // skips the holiday and the weekend
nextBusinessDay('2026-12-24', cal, { includeSelf: true }); // => '2026-12-24'

nthBusinessDayOfMonth

The n-th business day of a month (n >= 1), or counting from the end (-1 = last); undefined when the month has fewer.

function nthBusinessDayOfMonth(year: number, month: number, n: number, calendar?: BusinessCalendar): PlainDate | undefined;

Example:

nthBusinessDayOfMonth(2026, 10, 1) // => '2026-10-01'

Example:

nthBusinessDayOfMonth(2026, 10, -1) // => '2026-10-30'

periodRange

First and last date (inclusive) of a BusinessPeriod.

function periodRange(period: BusinessPeriod, calendar?: BusinessCalendar): readonly [PlainDate, PlainDate];

Example:

const cal = createBusinessCalendar({ fiscalYearStartMonth: 4 });
periodRange({ fiscalYear: 2027 }, cal); // => ['2026-04-01', '2027-03-31'] // named by the year it ends in
periodRange({ year: 2026, month: 2 }); // => ['2026-02-01', '2026-02-28']

previousBusinessDay

The last business day before date (or on it with includeSelf).

function previousBusinessDay(date: DateInput, calendar?: BusinessCalendar, options?: NextBusinessDayOptions): PlainDate;

Example:

previousBusinessDay('2026-10-05') // => '2026-10-02' // Monday → Friday

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('New Year', 1, 1)] });
previousBusinessDay('2027-01-04', cal); // => '2026-12-31' // skips the weekend and New Year (Friday)

AddBusinessDaysOptions

Options for addBusinessDays.

interface AddBusinessDaysOptions {
readonly zero?: 'following' | 'preceding' | 'none';
}

CountOptions

Options for the count* functions.

interface CountOptions {
readonly inclusive?: Inclusivity;
readonly calendar?: BusinessCalendar;
}

DiffBreakdown

Breakdown of a range (all fields signed like diffDays). Identities: weekdays + weekendDays === calendarDays and businessDays === weekdays - nonWorkingWeekdays + workingWeekendDays - (1 - halfDayValue) x halfDays. A holiday on a weekend is counted in holidaysOnWeekends and never subtracted twice. businessHours is the nominal scheduled wall-clock time (DST-free); use diffBusinessHours for elapsed time between instants.

interface DiffBreakdown {
readonly calendarDays: number;
readonly weekdays: number;
readonly weekendDays: number;
readonly holidays: number;
readonly holidaysOnWeekends: number;
readonly halfDays: number;
readonly nonWorkingWeekdays: number;
readonly workingWeekendDays: number;
readonly businessDays: number;
readonly businessHours: number;
}

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });
const b: DiffBreakdown = diffBreakdown('2026-12-21', '2026-12-28', cal); // [Mon 21, Mon 28)
[b.calendarDays, b.weekdays, b.weekendDays, b.holidays, b.businessDays]; // => [7, 5, 2, 1, 4]
b.weekdays + b.weekendDays === b.calendarDays; // => true

DiffDaysOptions

Options for diffDays.

interface DiffDaysOptions {
readonly mode?: DiffMode;
readonly inclusive?: Inclusivity;
readonly calendar?: BusinessCalendar;
readonly halfDayValue?: number;
}

HolidayCount

Holiday dates in a range: onWeekdays (in effect on working weekdays) and onWeekends (falling on weekend days, shifted or not). A date with several holidays counts once.

interface HolidayCount {
readonly total: number;
readonly onWeekdays: number;
readonly onWeekends: number;
}

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('Republic Day', 1, 26), fixedHoliday('Independence Day', 8, 15), fixedHoliday('Christmas Day', 12, 25)],
});
const count: HolidayCount = countHolidays('2026-01-01', '2026-12-31', { inclusive: 'both', calendar: cal });
count; // => { total: 3, onWeekdays: 2, onWeekends: 1 } // 15 August 2026 is a Saturday

ListBusinessDaysOptions

Options for listBusinessDays.

interface ListBusinessDaysOptions {
readonly limit?: number;
}

NextBusinessDayOptions

Options for nextBusinessDay / previousBusinessDay.

interface NextBusinessDayOptions {
readonly includeSelf?: boolean;
}

BusinessDayConvention

Business-day conventions (ISDA): 'following', 'modifiedFollowing' (following unless that leaves the month, then preceding), 'preceding', 'modifiedPreceding', 'none'.

type BusinessDayConvention = 'following' | 'modifiedFollowing' | 'preceding' | 'modifiedPreceding' | 'none';

Example:

const c: BusinessDayConvention = 'modifiedFollowing';

BusinessPeriod

A period for businessDaysIn: a calendar year, month, or quarter, or a fiscal year or fiscal quarter per the calendar’s fiscalYearStartMonth and fiscalYearNaming.

type BusinessPeriod = number | {
readonly year: number;
readonly month: number;
} | {
readonly year: number;
readonly quarter: number;
} | {
readonly fiscalYear: number;
readonly quarter?: number;
};

Example:

const q: BusinessPeriod = { fiscalYear: 2027, quarter: 1 };
periodRange(q, createBusinessCalendar({ fiscalYearStartMonth: 4 })); // => ['2026-04-01', '2026-06-30']
periodRange({ year: 2026, quarter: 4 }); // => ['2026-10-01', '2026-12-31']

DiffMode

What diffDays counts.

type DiffMode = 'calendar' | 'weekdays' | 'business';

describeBusinessDay

Plain-text description of why a day is special: “Weekend”, “Holiday: Diwali”, “Half day: Christmas Eve”, “Working day: Compensatory Saturday”; '' for an ordinary working day.

function describeBusinessDay(info: BusinessDayInfo, options?: DescribeBusinessDayOptions): string;

Example:

const cal = createBusinessCalendar({
holidays: [fixedHoliday('Christmas Day', 12, 25)],
halfDays: [fixedHoliday('Christmas Eve', 12, 24)],
exceptions: [{ date: '2026-12-19', working: true, reason: 'Year-end close' }],
});
describeBusinessDay(getBusinessDay('2026-12-25', cal)); // => 'Holiday: Christmas Day'
describeBusinessDay(getBusinessDay('2026-12-24', cal)); // => 'Half day: Christmas Eve'
describeBusinessDay(getBusinessDay('2026-12-26', cal)); // => 'Weekend'
describeBusinessDay(getBusinessDay('2026-12-19', cal)); // => 'Working day: Year-end close'
describeBusinessDay(getBusinessDay('2026-12-21', cal)); // => '' // ordinary working day
describeBusinessDay(getBusinessDay('2026-12-25', cal), { labels: { holiday: 'Feiertag: {name}' } }); // => 'Feiertag: Christmas Day'

DEFAULT_BUSINESS_DAY_LABELS

English default labels.

const DEFAULT_BUSINESS_DAY_LABELS: BusinessDayLabels;

Example:

DEFAULT_BUSINESS_DAY_LABELS.weekend // => 'Weekend'

BusinessDayLabels

Label catalog. {name} is replaced with the holiday names (joined with Intl.ListFormat) or the exception reason.

interface BusinessDayLabels {
readonly weekend: string;
readonly holiday: string;
readonly halfDay: string;
readonly halfDayUnnamed: string;
readonly nonWorking: string;
readonly nonWorkingUnnamed: string;
readonly working: string;
readonly workingUnnamed: string;
}

Example:

const de: Partial<BusinessDayLabels> = { weekend: 'Wochenende', holiday: 'Feiertag: {name}' };

DescribeBusinessDayOptions

Options for describeBusinessDay.

interface DescribeBusinessDayOptions {
readonly locale?: string;
readonly labels?: Partial<BusinessDayLabels>;
}

createBusinessCalendar

Creates an immutable, validated business calendar. Invalid definitions throw ValidationError (HLP_BIZCAL_*; details name the property path, never the value); list sizes are bounded (LimitError). Holidays are expanded lazily per year and cached (200 years, LRU) on the instance.

function createBusinessCalendar(definition?: BusinessCalendarDefinition): BusinessCalendar;

Example:

const cal = createBusinessCalendar({
weekend: [6, 7],
holidays: [fixedHoliday('New Year', 1, 1, { observed: 'nearest-weekday' }), easterHoliday('Good Friday', -2)],
workingHours: { default: [['09:00', '13:00'], ['14:00', '18:00']], 5: [['09:00', '13:00']] },
timeZone: 'Asia/Kolkata',
});
isHoliday('2027-03-26', cal)?.name; // => 'Good Friday'
isBusinessDay('2027-12-31', cal); // => false // New Year 2028 falls on a Saturday
getBusinessDay('2026-10-02', cal).hours; // => [['09:00', '13:00']] // Friday hours

Example:

createBusinessCalendar({ fiscalYearStartMonth: 13 }); // => throws HLP_BIZCAL_INVALID_FISCAL

defaultBusinessCalendar

The shared default calendar: Saturday/Sunday weekend, no holidays, 09:00-17:00, UTC. Used when a function’s calendar argument is omitted.

function defaultBusinessCalendar(): BusinessCalendar;

Example:

isBusinessDay('2026-09-26', defaultBusinessCalendar()); // => false // Saturday
isBusinessDay('2026-12-25', defaultBusinessCalendar()); // => true // no holidays by default
defaultBusinessCalendar().timeZone; // => 'UTC'

isBusinessCalendar

True for values created by createBusinessCalendar.

function isBusinessCalendar(value: unknown): value is BusinessCalendar;

Example:

isBusinessCalendar(createBusinessCalendar()); // => true
isBusinessCalendar({ kind: 'BusinessCalendar', weekend: [6, 7] }); // => false // not created by createBusinessCalendar

mergeCalendars

Combines calendars: the union of holidays, half days, providers, and exceptions (the first calendar wins when two define an exception for the same date). Weekend, hours, time zone, fiscal settings, and half-day settings come from the first calendar unless overrides sets them. Each input’s tag scope is applied to its own holidays before merging.

function mergeCalendars(calendars: readonly BusinessCalendar[], overrides?: BusinessCalendarDefinition): BusinessCalendar;

Example:

const company = createBusinessCalendar({ name: 'Company', holidays: [fixedHoliday('Founders Day', 3, 16)] });
const regional = createBusinessCalendar({ name: 'Karnataka', holidays: [fixedHoliday('Kannada Rajyotsava', 11, 1, { observed: 'next-weekday' })] });
const team = mergeCalendars([company, regional], { name: 'Bengaluru team' });
team.name; // => 'Bengaluru team'
isBusinessDay('2026-03-16', team); // => false // Founders Day, a Monday
isHoliday('2026-11-02', team)?.name; // => 'Kannada Rajyotsava' // Sunday 1st observed on Monday

weekendForLocale

Weekend days of a locale (CLDR via Intl.Locale#getWeekInfo, with an embedded fallback).

function weekendForLocale(locale?: string): readonly IsoWeekday[];

Example:

weekendForLocale('ar-SA') // => [5, 6]

Example:

weekendForLocale('en-IN') // => [7]

withTags

A copy of the calendar whose holidays are limited to rules tagged with one of tags (untagged rules stay unless includeUntagged: false). Replaces any previous scope.

function withTags(calendar: BusinessCalendar, tags: readonly string[], options?: {
readonly includeUntagged?: boolean;
}): BusinessCalendar;

Example:

const india = createBusinessCalendar({
weekend: [7],
holidays: [
fixedHoliday('Republic Day', 1, 26),
fixedHoliday('Maharashtra Day', 5, 1, { tags: ['IN-MH'] }),
fixedHoliday('Kannada Rajyotsava', 11, 1, { tags: ['IN-KA'] }),
],
});
const karnataka = withTags(india, ['IN-KA']);
listHolidays('2026-01-01', '2026-12-31', karnataka).map((h) => h.name); // => ['Republic Day', 'Kannada Rajyotsava']
isBusinessDay('2026-05-01', karnataka); // => true // Maharashtra Day does not apply
listHolidays('2026-01-01', '2026-12-31', withTags(india, ['IN-MH'], { includeUntagged: false })).map((h) => h.name); // => ['Maharashtra Day']

BusinessCalendar

Immutable, validated business calendar. Create with createBusinessCalendar.

interface BusinessCalendar {
readonly kind: 'BusinessCalendar';
readonly name: string;
readonly weekend: readonly IsoWeekday[];
readonly workingWeekdays: readonly IsoWeekday[];
readonly firstDayOfWeek: IsoWeekday;
readonly timeZone: string;
readonly fiscalYearStartMonth: number;
readonly fiscalYearNaming: 'end' | 'start';
readonly halfDayValue: number;
readonly workingHours: Readonly<Record<`${IsoWeekday}`, readonly TimeRange[]>>;
readonly holidays: readonly HolidayRule[];
readonly halfDays: readonly HolidayRule[];
readonly exceptions: readonly BusinessDayException[];
readonly providers: readonly HolidayProvider[];
readonly tagScope: TagScope | undefined;
readonly definition: BusinessCalendarDefinition;
}

Example:

const cal: BusinessCalendar = createBusinessCalendar({ timeZone: 'Asia/Kolkata' });
cal.weekend; // => [6, 7]
cal.timeZone; // => 'Asia/Kolkata'

BusinessCalendarDefinition

Input for createBusinessCalendar. Every field is optional.

interface BusinessCalendarDefinition {
readonly name?: string;
readonly locale?: string;
readonly weekend?: readonly IsoWeekday[];
readonly firstDayOfWeek?: IsoWeekday;
readonly holidays?: readonly HolidayRule[];
readonly halfDays?: readonly HolidayRule[];
readonly halfDayValue?: number;
readonly halfDayHours?: readonly TimeRange[];
readonly exceptions?: readonly BusinessDayException[];
readonly workingHours?: WorkingHoursDefinition;
readonly timeZone?: string;
readonly fiscalYearStartMonth?: number;
readonly fiscalYearNaming?: 'end' | 'start';
readonly providers?: readonly HolidayProvider[];
readonly tagScope?: TagScope | undefined;
}

Example:

const def: BusinessCalendarDefinition = { weekend: [5, 6], timeZone: 'Asia/Riyadh' };
const cal = createBusinessCalendar(def);
cal.workingWeekdays; // => [1, 2, 3, 4, 7]
isBusinessDay('2026-10-04', cal); // => true // Sunday

BusinessDayException

Overrides the weekday pattern and holidays for one date (a compensatory working Saturday, a stocktaking closure). At most one exception per date.

interface BusinessDayException {
readonly date: DateInput;
readonly working: boolean;
readonly hours?: readonly TimeRange[];
readonly halfDay?: boolean;
readonly reason?: string;
}

Example:

const ex: BusinessDayException = { date: '2026-10-24', working: true, reason: 'Compensatory working day' };
const cal = createBusinessCalendar({ exceptions: [ex] });
isBusinessDay('2026-10-24', cal); // => true // a Saturday made working
isWeekend('2026-10-24', cal); // => true // the weekday pattern is unchanged
getBusinessDay('2026-10-24', cal).reason; // => 'Compensatory working day'

BusinessDayInfo

Everything about one date in a calendar.

interface BusinessDayInfo {
readonly date: PlainDate;
readonly weekday: IsoWeekday;
readonly working: boolean;
readonly weekend: boolean;
readonly holidays: readonly ResolvedHoliday[];
readonly exception: BusinessDayException | null;
readonly halfDay: boolean;
readonly hours: readonly TimeRange[];
readonly reasonCode: BusinessDayReasonCode | null;
readonly reason: string | null;
readonly businessDayValue: number;
}

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)], halfDays: [fixedHoliday('Christmas Eve', 12, 24)] });
getBusinessDay('2026-12-25', cal).reasonCode; // => 'holiday'
getBusinessDay('2026-12-26', cal).reasonCode; // => 'weekend'
const eve = getBusinessDay('2026-12-24', cal);
[eve.working, eve.halfDay, eve.businessDayValue, eve.hours]; // => [true, true, 0.5, [['09:00', '13:00']]]

TagScope

Restricts holidays to rules carrying one of tags; untagged rules apply unless includeUntagged: false.

interface TagScope {
readonly tags: readonly string[];
readonly includeUntagged?: boolean;
}

Example:

const scope: TagScope = { tags: ['IN-KA'] };
const cal = createBusinessCalendar({
tagScope: scope,
holidays: [fixedHoliday('Maharashtra Day', 5, 1, { tags: ['IN-MH'] }), fixedHoliday('Kannada Rajyotsava', 11, 1, { tags: ['IN-KA'] })],
});
isBusinessDay('2026-05-01', cal); // => true // IN-MH is out of scope
isHoliday('2026-11-01', cal)?.name; // => 'Kannada Rajyotsava'

BusinessDayReasonCode

Why a day is special. The UI localizes 'weekend'; the others come with reason text.

type BusinessDayReasonCode = 'weekend' | 'holiday' | 'exception' | 'half-day';

Example:

const code: BusinessDayReasonCode = 'holiday';

WorkingHoursDefinition

Working hours: default for every working day plus per-ISO-weekday overrides (5 = Friday). An empty list means a working day without scheduled hours.

type WorkingHoursDefinition = Readonly<Partial<Record<'default' | `${IsoWeekday}`, readonly TimeRange[]>>>;

Example:

const hours: WorkingHoursDefinition = { default: [['09:00', '13:00'], ['14:00', '18:00']], 5: [['09:00', '13:00']] };
const cal = createBusinessCalendar({ workingHours: hours });
cal.workingHours['1']; // => [['09:00', '13:00'], ['14:00', '18:00']]
cal.workingHours['5']; // => [['09:00', '13:00']]

addBusinessHours

Adds working time to an instant (negative subtracts), skipping non-working time. A result that lands exactly on the end of an interval stays there (17:00, not the next 09:00). Zero returns the instant unchanged.

function addBusinessHours(instant: InstantInput, hours: number, calendar?: BusinessCalendar): number;

Example:

const cal = createBusinessCalendar({ timeZone: 'UTC' });
addBusinessHours(Date.UTC(2026, 9, 2, 15), 4, cal); // => Date.UTC(2026, 9, 5, 11) // Friday 15:00 → Monday 11:00
addBusinessHours(Date.UTC(2026, 9, 2, 15), 2, cal); // => Date.UTC(2026, 9, 2, 17) // ends exactly at 17:00
addBusinessHours(Date.UTC(2026, 9, 5, 10), -2, cal); // => Date.UTC(2026, 9, 2, 16) // back into Friday

diffBusinessHours

Working time between two instants (real elapsed time inside working intervals, DST-correct), negative when end is before start. Ranges may span at most 100 years.

function diffBusinessHours(start: InstantInput, end: InstantInput, calendar?: BusinessCalendar, options?: DiffBusinessHoursOptions): number;

Example:

const cal = createBusinessCalendar({ timeZone: 'UTC', workingHours: { default: [['09:00', '17:00']] } });
diffBusinessHours(Date.UTC(2026, 9, 2, 15), Date.UTC(2026, 9, 5, 11), cal); // => 4 // Fri 15-17 + Mon 9-11
diffBusinessHours(Date.UTC(2026, 9, 5, 11), Date.UTC(2026, 9, 2, 15), cal); // => -4
diffBusinessHours(Date.UTC(2026, 9, 2, 15), Date.UTC(2026, 9, 2, 15, 30), cal, { unit: 'milliseconds' }); // => 1800000

isWorkingTime

True when an instant falls inside working hours of a business day in the calendar’s zone.

function isWorkingTime(instant: InstantInput, calendar?: BusinessCalendar): boolean;

Example:

const cal = createBusinessCalendar({ timeZone: 'Europe/London', holidays: [fixedHoliday('Christmas Day', 12, 25)] });
isWorkingTime(Date.UTC(2026, 9, 5, 10), cal); // => true // Monday 11:00 in London
isWorkingTime(Date.UTC(2026, 9, 5, 16, 30), cal); // => false // 17:30 in London
isWorkingTime(Date.UTC(2026, 11, 25, 10), cal); // => false // holiday

nextWorkingTime

The instant itself when it is working time, otherwise the start of the next working interval. Throws HLP_BIZCAL_NO_WORKING_DAYS when there is no working time within ten years.

function nextWorkingTime(instant: InstantInput, calendar?: BusinessCalendar): number;

Example:

const cal = createBusinessCalendar({ timeZone: 'UTC' });
nextWorkingTime(Date.UTC(2026, 9, 3, 12), cal); // => Date.UTC(2026, 9, 5, 9) // Saturday noon → Monday 09:00
nextWorkingTime(Date.UTC(2026, 9, 5, 10), cal); // => Date.UTC(2026, 9, 5, 10) // already working time

Example:

const kolkata = createBusinessCalendar({ timeZone: 'Asia/Kolkata' });
new Date(nextWorkingTime(Date.UTC(2026, 9, 5, 12), kolkata)).toISOString(); // => '2026-10-06T03:30:00.000Z' // 17:30 Monday → Tuesday 09:00 IST

slaDeadline

Deadline for an SLA started at start. { businessHours } adds working time (addBusinessHours). { businessDays: n } is due at the end of the last working interval of the n-th business day after the start’s local date (n = 0: that date, or the next business day); a business day without hours is due at the end of the day.

function slaDeadline(start: InstantInput, target: SlaTarget, calendar?: BusinessCalendar): number;

Example:

const cal = createBusinessCalendar({ timeZone: 'UTC', holidays: [fixedHoliday('Christmas Day', 12, 25)] });
slaDeadline(Date.UTC(2026, 9, 2, 16), { businessDays: 1 }, cal); // => Date.UTC(2026, 9, 5, 17) // Friday → end of Monday
slaDeadline(Date.UTC(2026, 9, 2, 16), { businessHours: 4 }, cal); // => Date.UTC(2026, 9, 5, 12) // 1 h Friday + 3 h Monday
slaDeadline(Date.UTC(2026, 11, 24, 16), { businessHours: 4 }, cal); // => Date.UTC(2026, 11, 28, 12) // skips Christmas and the weekend

workingIntervals

Working hours of a date as instants in the calendar’s time zone (empty on non-working days).

function workingIntervals(date: DateInput, calendar?: BusinessCalendar): readonly InstantInterval[];

Example:

workingIntervals('2026-10-05'); // => [{ start: Date.UTC(2026, 9, 5, 9), end: Date.UTC(2026, 9, 5, 17) }]
workingIntervals('2026-10-03'); // => [] // Saturday

Example:

const london = createBusinessCalendar({ timeZone: 'Europe/London', workingHours: { default: [['09:00', '12:30'], ['13:30', '17:30']] } });
workingIntervals('2026-10-05', london); // => [{ start: Date.UTC(2026, 9, 5, 8), end: Date.UTC(2026, 9, 5, 11, 30) }, { start: Date.UTC(2026, 9, 5, 12, 30), end: Date.UTC(2026, 9, 5, 16, 30) }] // BST is UTC+1

DiffBusinessHoursOptions

Options for diffBusinessHours.

interface DiffBusinessHoursOptions {
readonly unit?: 'hours' | 'milliseconds';
}

InstantInterval

A half-open instant interval [start, end) in epoch milliseconds.

interface InstantInterval {
readonly start: number;
readonly end: number;
}

Example:

const i: InstantInterval = { start: Date.UTC(2026, 0, 5, 9), end: Date.UTC(2026, 0, 5, 17) };

SlaTarget

An SLA target: working hours, or business days (due at the end of the working day).

type SlaTarget = {
readonly businessHours: number;
} | {
readonly businessDays: number;
};

Example:

const target: SlaTarget = { businessHours: 8 };

TimeRange

A working-time interval as ['HH:mm', 'HH:mm']; start inclusive, end exclusive, '24:00' allowed as an end.

type TimeRange = readonly [start: string, end: string];

Example:

const morning: TimeRange = ['09:00', '13:00'];

Inclusivity

Which endpoints count as inside a range.

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

customHoliday

Dates computed per year by the application (lunar or regional calendars).

function customHoliday(name: string, dates: (year: number) => readonly DateInput[], options?: HolidayRuleOptions): CustomHolidayRule;

Example:

// Dates from the application's own lunar table or server.
const festival: Readonly<Record<number, readonly string[]>> = { 2026: ['2026-10-20'], 2027: ['2027-11-09'] };
const cal = createBusinessCalendar({ weekend: [7], holidays: [customHoliday('Harvest festival', (year) => festival[year] ?? [])] });
isHoliday('2026-10-20', cal)?.name; // => 'Harvest festival'
isBusinessDay('2027-11-09', cal); // => false

defineHolidays

Validates a list of rules up front and returns it frozen (errors name the index).

function defineHolidays(rules: readonly HolidayRule[]): readonly HolidayRule[];

Example:

const US = defineHolidays([fixedHoliday('New Year', 1, 1), nthWeekdayHoliday('Labor Day', 9, 1, 1)]);
isHoliday('2026-09-07', createBusinessCalendar({ holidays: US }))?.name; // => 'Labor Day'

Example:

defineHolidays([{ kind: 'fixed', name: 'Typo', month: 13, day: 1 }]); // => throws HLP_BIZCAL_INVALID_RULE

easterHoliday

Days relative to Easter Sunday.

function easterHoliday(name: string, offsetDays?: number, options?: HolidayRuleOptions & {
readonly method?: 'western' | 'orthodox';
}): EasterHolidayRule;

Example:

const cal = createBusinessCalendar({ holidays: [easterHoliday('Good Friday', -2), easterHoliday('Easter Monday', 1)] });
listHolidays('2026-04-01', '2026-04-30', cal).map((h) => h.date); // => ['2026-04-03', '2026-04-06'] // Easter Sunday is 5 April

Example:

const cal = createBusinessCalendar({ holidays: [easterHoliday('Orthodox Easter Monday', 1, { method: 'orthodox' })] });
isHoliday('2026-04-13', cal)?.name; // => 'Orthodox Easter Monday'

easterSunday

Easter Sunday for a year (1..275759). 'western' uses the anonymous Gregorian algorithm (Meeus/Jones/Butcher); 'orthodox' uses the Julian computus (Meeus) converted to the Gregorian calendar. Both are proleptic before their historical adoption.

function easterSunday(year: number, method?: 'western' | 'orthodox'): PlainDate;

Example:

easterSunday(2026) // => '2026-04-05'

Example:

easterSunday(2026, 'orthodox') // => '2026-04-12'

fixedHoliday

Same month and day every year.

function fixedHoliday(name: string, month: number, day: number, options?: HolidayRuleOptions): FixedHolidayRule;

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('New Year', 1, 1, { observed: 'nearest-weekday' })] });
isBusinessDay('2027-01-01', cal); // => false // Friday
isHoliday('2027-12-31', cal)?.name; // => 'New Year' // 1 January 2028 is a Saturday

holidayRange

Every date from start to end, inclusive.

function holidayRange(name: string, start: DateInput, end: DateInput, options?: Omit<HolidayRuleOptions, 'fromYear' | 'toYear'>): RangeHolidayRule;

Example:

const cal = createBusinessCalendar({ holidays: [holidayRange('Winter shutdown', '2026-12-24', '2027-01-01')] });
diffDays('2026-12-24', '2027-01-01', { mode: 'calendar', inclusive: 'both', calendar: cal }); // => 9
diffDays('2026-12-24', '2027-01-01', { mode: 'weekdays', inclusive: 'both', calendar: cal }); // => 7
diffDays('2026-12-24', '2027-01-01', { mode: 'business', inclusive: 'both', calendar: cal }); // => 0
nextBusinessDay('2026-12-23', cal); // => '2027-01-04'

lastWeekdayHoliday

Last given weekday of a month.

function lastWeekdayHoliday(name: string, month: number, weekday: IsoWeekday, options?: HolidayRuleOptions): NthWeekdayHolidayRule;

Example:

const cal = createBusinessCalendar({ holidays: [lastWeekdayHoliday('Memorial Day', 5, 1)] }); // last Monday of May
isHoliday('2026-05-25', cal)?.name; // => 'Memorial Day'
nextBusinessDay('2026-05-22', cal); // => '2026-05-26' // Friday → Tuesday

nthWeekdayHoliday

n-th weekday of a month (n 1..5, or negative from the end).

function nthWeekdayHoliday(name: string, month: number, weekday: IsoWeekday, n: number, options?: HolidayRuleOptions): NthWeekdayHolidayRule;

Example:

const cal = createBusinessCalendar({ holidays: [nthWeekdayHoliday('Thanksgiving', 11, 4, 4)] }); // 4th Thursday of November
isHoliday('2026-11-26', cal)?.name; // => 'Thanksgiving'
isHoliday('2027-11-25', cal)?.name; // => 'Thanksgiving'

oneOffHoliday

A single date.

function oneOffHoliday(name: string, date: DateInput, options?: Omit<HolidayRuleOptions, 'fromYear' | 'toYear'>): OneOffHolidayRule;

Example:

const cal = createBusinessCalendar({ holidays: [oneOffHoliday('Office move', '2026-06-19')] });
isBusinessDay('2026-06-19', cal); // => false // Friday
isBusinessDay('2027-06-18', cal); // => true // does not recur

CustomHolidayRule

Dates computed by the application per year (lunar and regional calendars). Must be pure and synchronous; results are cached per year. At most 400 dates per year.

interface CustomHolidayRule extends HolidayRuleBase, HolidayYearBounds {
readonly kind: 'custom';
readonly dates: (year: number) => readonly DateInput[];
}

EasterHolidayRule

Days relative to Easter Sunday (Good Friday = -2, Easter Monday = 1).

interface EasterHolidayRule extends HolidayRuleBase, HolidayYearBounds {
readonly kind: 'easter';
readonly offsetDays?: number;
readonly method?: 'western' | 'orthodox';
}

FixedHolidayRule

Same month and day every year (Feb 29 only in leap years).

interface FixedHolidayRule extends HolidayRuleBase, HolidayYearBounds {
readonly kind: 'fixed';
readonly month: number;
readonly day: number;
}

HolidayProvider

Loads holiday data per year (no country data ships with the package, B4). Must be pure and synchronous; results are cached per calendar and year. Recurring rules returned for a year are expanded for that year only.

interface HolidayProvider {
holidaysFor(year: number): readonly (HolidayRule | ProvidedHoliday)[];
}

Example:

const table: Readonly<Record<number, readonly ProvidedHoliday[]>> = { 2026: [{ date: '2026-11-10', name: 'Foundation Day' }] };
const provider: HolidayProvider = { holidaysFor: (year) => table[year] ?? [] };
const cal = createBusinessCalendar({ providers: [provider] });
isHoliday('2026-11-10', cal)?.name; // => 'Foundation Day'
isBusinessDay('2026-11-10', cal); // => false // Tuesday

HolidayRuleBase

Fields shared by every holiday rule.

interface HolidayRuleBase {
readonly name: string;
readonly id?: string;
readonly tags?: readonly string[];
readonly observed?: ObservedPolicy;
readonly halfDay?: boolean;
readonly halfDayHours?: readonly TimeRange[];
}

HolidayYearBounds

Year bounds for recurring rules (inclusive).

interface HolidayYearBounds {
readonly fromYear?: number;
readonly toYear?: number;
}

NthWeekdayHolidayRule

n-th weekday of a month: n 1..5, or -1..-5 counting from the end (-1 = last).

interface NthWeekdayHolidayRule extends HolidayRuleBase, HolidayYearBounds {
readonly kind: 'nth-weekday';
readonly month: number;
readonly weekday: IsoWeekday;
readonly n: number;
}

OneOffHolidayRule

A single date.

interface OneOffHolidayRule extends HolidayRuleBase {
readonly kind: 'date';
readonly date: DateInput;
}

ProvidedHoliday

An already-resolved holiday returned by a HolidayProvider (no observed shift is applied). A ResolvedHoliday is accepted too.

interface ProvidedHoliday {
readonly date: DateInput;
readonly name: string;
readonly id?: string;
readonly tags?: readonly string[];
readonly halfDay?: boolean;
readonly halfDayHours?: readonly TimeRange[];
}

Example:

const h: ProvidedHoliday = { date: '2026-11-08', name: 'Diwali' };

RangeHolidayRule

Every date from start to end, inclusive (at most 3660 days).

interface RangeHolidayRule extends HolidayRuleBase {
readonly kind: 'range';
readonly start: DateInput;
readonly end: DateInput;
}

ResolvedHoliday

A holiday on a specific date. A shifted holiday produces two entries: the original date (movedTo set; informational, does not change whether that day is worked) and the observed date (observed: true, originalDate set).

interface ResolvedHoliday {
readonly date: PlainDate;
readonly name: string;
readonly kind: HolidayKind | 'provided';
readonly id?: string;
readonly tags: readonly string[];
readonly halfDay: boolean;
readonly observed: boolean;
readonly originalDate?: PlainDate;
readonly movedTo?: PlainDate;
}

Example:

const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25, { observed: 'nearest-weekday' })] });
isHoliday('2027-12-24', cal); // => { date: '2027-12-24', name: 'Christmas Day', kind: 'fixed', tags: [], halfDay: false, observed: true, originalDate: '2027-12-25' }
holidaysOn('2027-12-25', cal)[0]?.movedTo; // => '2027-12-24' // the Saturday original

HolidayKind

Rule kinds.

type HolidayKind = HolidayRule['kind'];

Example:

const k: HolidayKind = 'easter';

HolidayRule

A holiday rule (discriminated by kind).

type HolidayRule = FixedHolidayRule | NthWeekdayHolidayRule | EasterHolidayRule | OneOffHolidayRule | RangeHolidayRule | CustomHolidayRule;

Example:

const rule: HolidayRule = { kind: 'fixed', name: 'New Year', month: 1, day: 1 };

HolidayRuleOptions

Options shared by the rule builders.

type HolidayRuleOptions = Omit<HolidayRuleBase, 'name'> & HolidayYearBounds;

Example:

const options: HolidayRuleOptions = { observed: 'nearest-weekday', tags: ['US'], fromYear: 2020 };
fixedHoliday('Independence Day', 7, 4, options).tags; // => ['US']

ObservedPolicy

Shift of a holiday that falls on a weekend day (per the calendar’s weekend): 'nearest-weekday' (Sat -> Fri, Sun -> Mon for a Sat/Sun weekend; ties go forward), 'next-weekday', 'previous-weekday', or a map from ISO weekday to a day offset ({ 7: 1 }: Sunday -> Monday), which applies on those weekdays whatever the weekend is. When the target already has a holiday (or is a weekend day) the search continues in the same direction, so a substitute day never lands on another holiday.

type ObservedPolicy = 'none' | 'nearest-weekday' | 'next-weekday' | 'previous-weekday' | ObservedShiftMap;

Example:

const policy: ObservedPolicy = 'nearest-weekday';
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25, { observed: policy })] });
isHoliday('2027-12-24', cal)?.name; // => 'Christmas Day' // Saturday 25th is observed on Friday 24th
isBusinessDay('2027-12-24', cal); // => false
isBusinessDay('2027-12-27', cal); // => true // Monday stays a business day

ObservedShiftMap

Day offsets by ISO weekday for custom observed shifts (-7..7).

type ObservedShiftMap = Readonly<Partial<Record<IsoWeekday, number>>>;

Example:

const map: ObservedShiftMap = { 6: -1, 7: 1 }; // Saturday → Friday, Sunday → Monday
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Independence Day', 8, 15, { observed: map })] });
isHoliday('2027-08-16', cal)?.observed; // => true // Sunday 15th observed on Monday 16th