@absoluteui/helpers/business-calendar
Weekends, holidays, business days, business hours, SLA deadlines, and business-day differences.
import { ... } from '@absoluteui/helpers/business-calendar';How to use
Section titled “How to use”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 }); // => 9diffDays(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 FridayformatPlainDate(payDate(2026, 12)); // => '2026-12-24' // Christmas DayformatPlainDate(addBusinessDays(payDate(2026, 12), -3, cal)); // => '2026-12-21' // cut-off: three business days earlierconst 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); // => trueisBusinessDay('2026-05-01', mumbai); // => false // Maharashtra DayisBusinessDay('2026-11-02', bengaluru); // => false // Rajyotsava on Sunday, observed MondayisBusinessDay('2026-03-16', mumbai); // => false // Founders Day everywhereHow 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 2027How 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); // => truecountHolidays(start, end, { inclusive: 'both', calendar: cal }); // => { total: 4, onWeekdays: 3, onWeekends: 1 } // 15 Aug 2026 is a SaturdayWhat goes wrong?
Section titled “What goes wrong?”- 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:
nextBusinessDaythrowsHLP_BIZCAL_NO_WORKING_DAYS. - There are no “business months”: weeks, months, and years are calendar units (
differenceInMonthsin@absoluteui/helpers/date); business modes exist only for days and hours. - Without a
calendarargument 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_FISCALnextBusinessDay('2026-10-05', createBusinessCalendar({ weekend: [1, 2, 3, 4, 5, 6, 7] })); // => throws HLP_BIZCAL_NO_WORKING_DAYSisBusinessDay('2026-12-25'); // => true // the default calendar knows no holidaysAPI reference
Section titled “API reference”Functions
Section titled “Functions”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 < 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 skippedaddBusinessDays('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 → MondayadjustBusinessDay('2026-10-31', 'modifiedFollowing'); // => '2026-10-30' // Monday is November, so FridayadjustBusinessDay('2026-10-28', 'modifiedFollowing'); // => '2026-10-28' // business days staybusinessDayAt
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 UTCbusinessDayAt(instant, indiaCal).date; // => '2026-10-03' // already Saturday 01:30 in KolkatabusinessDayAt(instant, indiaCal).working; // => falsebusinessDayAt(instant).date; // => '2026-10-02' // the default calendar uses UTCbusinessDayOfMonth
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') // => 3Example:
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Gandhi Jayanti', 10, 2)] });businessDayOfMonth('2026-10-05', cal); // => 2businessDayOfMonth('2026-10-02', cal); // => undefined // holidaybusinessDaysIn
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 }) // => 22Example:
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 EvebusinessDaysIn({ year: 2026, month: 12 }, cal, { halfDayValue: 1 }); // => 22countBusinessDays
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 }); // => 5countBusinessDays('2026-12-21', '2026-12-25', { inclusive: 'both', calendar: cal }); // => 3.5 // Christmas off, Christmas Eve halfcountBusinessDays('2026-12-21', '2026-12-25', { inclusive: 'both', calendar: cal, halfDayValue: 1 }); // => 4countHolidays
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; // => 2countHolidays('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' }) // => 5countWeekends
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' }) // => 9Example:
countWeekends('2026-10-01', '2026-10-31', { inclusive: 'both', calendar: createBusinessCalendar({ weekend: [5, 6] }) }) // => 10 // five Fridays and five SaturdaysdiffBreakdown
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 dayExample:
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 Saturdayb.businessDays; // => 3.5 // 5 weekdays − Christmas − half of Christmas Eveb.businessHours; // => 28 // 3 × 8 h + 4 h on Christmas EvediffDays
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') // => 4Example:
diffDays('2026-09-28', '2026-10-02', { mode: 'business', inclusive: 'both' }) // => 5 // Mon..Fri leaveExample:
diffDays('2026-10-02', '2026-09-28', { mode: 'weekdays' }) // => -4Example:
// 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 }); // => 7diffDays('2026-12-25', '2027-01-01', { mode: 'weekdays', calendar: cal }); // => 5diffDays('2026-12-25', '2027-01-01', { mode: 'business', calendar: cal }); // => 4diffDays('2026-12-25', '2027-01-01', { mode: 'business', inclusive: 'both', calendar: cal }); // => 4 // New Year is a holiday toofirstBusinessDayOfMonth
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 SundayExample:
const cal = createBusinessCalendar({ holidays: [fixedHoliday('New Year', 1, 1)] });firstBusinessDayOfMonth(2027, 1, cal); // => '2027-01-04' // Friday 1st is a holidaygetBusinessDay
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 dayholidaysOn
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 FridayholidaysOn('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 // ThursdayisBusinessDay('2026-12-25', cal); // => false // holidayisBusinessDay('2026-12-26', cal); // => false // SaturdayisBusinessDay('2026-12-19', cal); // => true // working Saturday exceptionisHalfDay
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 // ThursdayisBusinessDay('2026-12-24', cal); // => true // half days are business daysisHalfDay('2027-12-24', cal); // => false // the observed Christmas Day (a full holiday) winsisHoliday
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 FridayisHoliday('2027-12-25', cal)?.movedTo; // => '2027-12-24'isHoliday('2027-12-27', cal); // => undefinedisWeekday
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 // FridayExample:
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });isWeekday('2026-12-25', cal); // => true // a holiday is still a weekdayisBusinessDay('2026-12-25', cal); // => falseisWeekend
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 // SaturdayExample:
isWeekend('2026-10-02', createBusinessCalendar({ weekend: [5, 6] })) // => true // FridaylastBusinessDayOfMonth
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 SaturdayExample:
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 31stlistBusinessDays
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 // => 5Example:
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'] // descendinglistHolidays
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 → MondayExample:
const cal = createBusinessCalendar({ holidays: [fixedHoliday('Christmas Day', 12, 25)] });nextBusinessDay('2026-12-24', cal); // => '2026-12-28' // skips the holiday and the weekendnextBusinessDay('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 inperiodRange({ 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 → FridayExample:
const cal = createBusinessCalendar({ holidays: [fixedHoliday('New Year', 1, 1)] });previousBusinessDay('2027-01-04', cal); // => '2026-12-31' // skips the weekend and New Year (Friday)Interfaces
Section titled “Interfaces”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; // => trueDiffDaysOptions
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 SaturdayListBusinessDaysOptions
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';Describe
Section titled “Describe”Functions
Section titled “Functions”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 daydescribeBusinessDay(getBusinessDay('2026-12-25', cal), { labels: { holiday: 'Feiertag: {name}' } }); // => 'Feiertag: Christmas Day'Constants
Section titled “Constants”DEFAULT_BUSINESS_DAY_LABELS
English default labels.
const DEFAULT_BUSINESS_DAY_LABELS: BusinessDayLabels;Example:
DEFAULT_BUSINESS_DAY_LABELS.weekend // => 'Weekend'Interfaces
Section titled “Interfaces”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>;}Engine
Section titled “Engine”Functions
Section titled “Functions”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 SaturdaygetBusinessDay('2026-10-02', cal).hours; // => [['09:00', '13:00']] // Friday hoursExample:
createBusinessCalendar({ fiscalYearStartMonth: 13 }); // => throws HLP_BIZCAL_INVALID_FISCALdefaultBusinessCalendar
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 // SaturdayisBusinessDay('2026-12-25', defaultBusinessCalendar()); // => true // no holidays by defaultdefaultBusinessCalendar().timeZone; // => 'UTC'isBusinessCalendar
True for values created by createBusinessCalendar.
function isBusinessCalendar(value: unknown): value is BusinessCalendar;Example:
isBusinessCalendar(createBusinessCalendar()); // => trueisBusinessCalendar({ kind: 'BusinessCalendar', weekend: [6, 7] }); // => false // not created by createBusinessCalendarmergeCalendars
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 MondayisHoliday('2026-11-02', team)?.name; // => 'Kannada Rajyotsava' // Sunday 1st observed on MondayweekendForLocale
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 applylistHolidays('2026-01-01', '2026-12-31', withTags(india, ['IN-MH'], { includeUntagged: false })).map((h) => h.name); // => ['Maharashtra Day']Interfaces
Section titled “Interfaces”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 // SundayBusinessDayException
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 workingisWeekend('2026-10-24', cal); // => true // the weekday pattern is unchangedgetBusinessDay('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 scopeisHoliday('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']]Functions
Section titled “Functions”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:00addBusinessHours(Date.UTC(2026, 9, 2, 15), 2, cal); // => Date.UTC(2026, 9, 2, 17) // ends exactly at 17:00addBusinessHours(Date.UTC(2026, 9, 5, 10), -2, cal); // => Date.UTC(2026, 9, 2, 16) // back into FridaydiffBusinessHours
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-11diffBusinessHours(Date.UTC(2026, 9, 5, 11), Date.UTC(2026, 9, 2, 15), cal); // => -4diffBusinessHours(Date.UTC(2026, 9, 2, 15), Date.UTC(2026, 9, 2, 15, 30), cal, { unit: 'milliseconds' }); // => 1800000isWorkingTime
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 LondonisWorkingTime(Date.UTC(2026, 9, 5, 16, 30), cal); // => false // 17:30 in LondonisWorkingTime(Date.UTC(2026, 11, 25, 10), cal); // => false // holidaynextWorkingTime
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:00nextWorkingTime(Date.UTC(2026, 9, 5, 10), cal); // => Date.UTC(2026, 9, 5, 10) // already working timeExample:
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 ISTslaDeadline
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 MondayslaDeadline(Date.UTC(2026, 9, 2, 16), { businessHours: 4 }, cal); // => Date.UTC(2026, 9, 5, 12) // 1 h Friday + 3 h MondayslaDeadline(Date.UTC(2026, 11, 24, 16), { businessHours: 4 }, cal); // => Date.UTC(2026, 11, 28, 12) // skips Christmas and the weekendworkingIntervals
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'); // => [] // SaturdayExample:
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+1Interfaces
Section titled “Interfaces”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 };Internal
Section titled “Internal”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'];Related types
Section titled “Related types”Inclusivity
Which endpoints count as inside a range.
type Inclusivity = 'both' | 'none' | 'start' | 'end';Functions
Section titled “Functions”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); // => falsedefineHolidays
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_RULEeasterHoliday
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 AprilExample:
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 // FridayisHoliday('2027-12-31', cal)?.name; // => 'New Year' // 1 January 2028 is a SaturdayholidayRange
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 }); // => 9diffDays('2026-12-24', '2027-01-01', { mode: 'weekdays', inclusive: 'both', calendar: cal }); // => 7diffDays('2026-12-24', '2027-01-01', { mode: 'business', inclusive: 'both', calendar: cal }); // => 0nextBusinessDay('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 MayisHoliday('2026-05-25', cal)?.name; // => 'Memorial Day'nextBusinessDay('2026-05-22', cal); // => '2026-05-26' // Friday → TuesdaynthWeekdayHoliday
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 NovemberisHoliday('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 // FridayisBusinessDay('2027-06-18', cal); // => true // does not recurInterfaces
Section titled “Interfaces”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 // TuesdayHolidayRuleBase
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 originalHolidayKind
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 24thisBusinessDay('2027-12-24', cal); // => falseisBusinessDay('2027-12-27', cal); // => true // Monday stays a business dayObservedShiftMap
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 → Mondayconst cal = createBusinessCalendar({ holidays: [fixedHoliday('Independence Day', 8, 15, { observed: map })] });isHoliday('2027-08-16', cal)?.observed; // => true // Sunday 15th observed on Monday 16th