temporal-business-days
v0.1.1
Published
Business-day math (skip weekends and holidays) built natively on the Temporal API — no legacy Date wrapping.
Maintainers
Readme
temporal-business-days
Business-day math — skipping weekends and holidays — built natively on the
Temporal API. Works with
Temporal.PlainDate directly instead of wrapping the legacy Date object.
- Configurable weekends (default Saturday/Sunday)
- Holidays as either an explicit date list or a predicate function
- Immutable: every function returns a new
Temporal.PlainDate, nothing is mutated - No runtime dependencies; ships ESM and CommonJS with full type declarations
Requirements
This package does not bundle a Temporal implementation. It expects a global
Temporal object to already exist — either natively or from a polyfill you set
up yourself.
If your environment doesn't have native Temporal yet (most do not at the time of writing — see support below), install the polyfill and install it onto the global scope:
npm install temporal-business-days
npm install @js-temporal/polyfillimport { Temporal } from '@js-temporal/polyfill'
globalThis.Temporal = TemporalImporting the polyfill for its side effects alone (import '@js-temporal/polyfill')
does not register the global — you have to assign globalThis.Temporal
yourself, as shown above. Do this once, early in your application's startup,
before calling into this library. If Temporal is missing when a function runs,
it throws a ReferenceError explaining how to fix it rather than failing with a
cryptic error.
The polyfill is a (types) peer dependency
@js-temporal/polyfill is declared as an optional peer dependency. Even though
nothing from it ends up in the runtime bundle, its TypeScript types are
required at compile time — the published type declarations import Temporal
from @js-temporal/polyfill directly, so that the Temporal.PlainDate you pass
in and the one this library expects are guaranteed to be the same type.
The practical consequence: TypeScript users need @js-temporal/polyfill
installed for the types to resolve, even if you are on an environment with
native Temporal and never load the polyfill at runtime. In that case it's a
types-only install:
npm install -D @js-temporal/polyfillJavaScript users without TypeScript can ignore this entirely.
Usage
import {
isBusinessDay,
addBusinessDays,
businessDaysBetween,
nextBusinessDay,
previousBusinessDay,
} from 'temporal-business-days'
const today = Temporal.Now.plainDateISO()
isBusinessDay(today)
// true on weekdays, false on weekends — default Mon–Fri week, no holidays
addBusinessDays(today, 5)
// a Temporal.PlainDate five business days from now
businessDaysBetween(
Temporal.PlainDate.from('2026-06-22'),
Temporal.PlainDate.from('2026-06-26'),
)
// 4Options
Every function takes the same optional BusinessDayOptions as its last argument,
so behavior is consistent across the whole API.
interface BusinessDayOptions {
// Days of the week that are non-working, using ISO numbering
// (1 = Monday … 7 = Sunday). Defaults to [6, 7] (Saturday, Sunday).
weekend?: WeekendDay[]
// Extra non-working days on top of weekends — either an explicit list of
// dates, or a predicate that returns true for a holiday. Defaults to none.
holidays?: Temporal.PlainDate[] | ((date: Temporal.PlainDate) => boolean)
}// A Friday–Saturday weekend, as used in parts of the Middle East.
isBusinessDay(date, { weekend: [5, 6] })
// An explicit holiday list.
isBusinessDay(date, { holidays: [Temporal.PlainDate.from('2026-12-25')] })
// Or any predicate.
isBusinessDay(date, { holidays: (d) => d.month === 12 && d.day === 25 })API
isBusinessDay(date, options?) => boolean
True when date is neither a weekend day nor a holiday under the given options.
addBusinessDays(date, days, options?) => Temporal.PlainDate
Returns the date days business days away, skipping weekends and holidays.
Negative days moves backwards. The starting date is never counted, whether or
not it is itself a business day, so addBusinessDays(friday, 1) lands on the
following Monday. days of 0 returns the date unchanged; a non-integer days
throws a RangeError.
businessDaysBetween(start, end, options?) => number
Counts business days from the day after start through end, inclusive.
Returns 0 when start and end are equal, and a negative number when end
is before start. It is the inverse of addBusinessDays: when end is itself
a business day, addBusinessDays(start, businessDaysBetween(start, end)) lands
back on end.
nextBusinessDay(date, options?) => Temporal.PlainDate
The nearest business day strictly after date.
previousBusinessDay(date, options?) => Temporal.PlainDate
The nearest business day strictly before date.
Plugging in a holiday source
The holidays predicate is the integration point for a region-aware holiday
library. For example, with dach-holidays
providing German/Austrian/Swiss holidays, wire its check straight in:
import { isHoliday } from 'dach-holidays'
import { addBusinessDays, isBusinessDay } from 'temporal-business-days'
// Holidays for Berlin (DE-BE). dach-holidays' isHoliday takes a string | Date,
// so convert the PlainDate to its ISO string (e.g. "2026-12-25") first.
const options = {
holidays: (date: Temporal.PlainDate) => isHoliday(date.toString(), 'DE-BE'),
}
isBusinessDay(Temporal.PlainDate.from('2026-12-25'), options)
// false — Christmas Day is a public holiday in Berlin
addBusinessDays(Temporal.PlainDate.from('2026-12-23'), 2, options)
// skips the 24th/25th if they are holidays as well as the weekendBecause the predicate is just a function, the same pattern works for any source — a database lookup, a hand-written rule, or another holiday package.
Temporal support
Temporal is a recent addition to JavaScript, so native availability is still uneven:
- Firefox — shipped (2025)
- Chrome — shipped (early 2026)
- Safari — in progress; check MDN or caniuse for current status
- Node.js — no native support yet
Until your target environments ship it, use the polyfill as described in Requirements. The browser/runtime picture is changing quickly, so the links above are the source of truth rather than this list.
License
MIT
