Migrating from moment-business-days
Every feature of moment-business-days has an equivalent. The translations below are checked against the real moment-business-days in test/moment-business-days.test.ts, with thousands of random working weeks and holiday lists, and the examples run both libraries side by side.
1. Install
npm install temporal-workdays
npm install temporal-polyfill # only where Temporal isn't built in yet (Safari, Node before 26)2. Move the configuration
moment-business-days reads its settings from a global moment locale. Here the settings are a plain object, passed to createWorkCalendar or as the last argument of any function.
// Before
moment.updateLocale('us', {
workingWeekdays: [1, 2, 3, 4, 5],
holidays: ['07-03-2026', '12-25-2026'],
holidayFormat: 'MM-DD-YYYY',
});
// After
const us = createWorkCalendar({
weekend: [6, 7],
holidays: ['2026-07-03', '2026-12-25'],
});
us.isWorkday(Temporal.PlainDate.from('2026-07-03')); // → false| moment locale setting | temporal-workdays option |
|---|---|
workingWeekdays (0 is Sunday) | weekend, the days that are not working days, numbered 1 (Monday) to 7 (Sunday) |
holidays and holidayFormat | holidays as 'YYYY-MM-DD' strings, or 'MM-DD' for every year |
a holiday(date) function on the locale | holidays: (date) => …, which receives a Temporal.PlainDate |
nextBusinessDayLimit, prevBusinessDayLimit | not needed, because there is no search limit |
| one locale per region | one createWorkCalendar(…) per region |
To convert an existing configuration:
// workingWeekdays to weekend
const workingWeekdays = [0, 1, 2, 3, 4]; // Sunday to Thursday
const working = workingWeekdays.map((day) => (day === 0 ? 7 : day));
const weekend = [1, 2, 3, 4, 5, 6, 7].filter((day) => !working.includes(day));
weekend; // → [5, 6]
// holidays in any holidayFormat to ISO dates
const holidays = ['07-03-2026', '12-25-2026']; // holidayFormat: 'MM-DD-YYYY'
const isoHolidays = holidays.map((h) => moment(h, 'MM-DD-YYYY').format('YYYY-MM-DD'));
isoHolidays; // → ['2026-07-03', '2026-12-25']3. Replace the calls
In this table m is a moment and d is a Temporal.PlainDate. A PlainDateTime or ZonedDateTime works too and comes back as the same type.
| moment-business-days | temporal-workdays | Notes |
|---|---|---|
m.isBusinessDay() | isWorkday(d) | |
m.isHoliday() | isHoliday(d) | |
m.businessAdd(n) | addWorkdays(d, n) | moment rounds a fractional n, this throws |
m.businessSubtract(n) | subtractWorkdays(d, n) | |
m.nextBusinessDay() | nextWorkday(d) | no 7-day limit, and d is not changed |
m.prevBusinessDay() | previousWorkday(d) | no 7-day limit, and d is not changed |
a.businessDiff(b) | Math.abs(workdaysBetween(b, a)) | see businessDiff |
a.businessDiff(b, true) | workdaysBetween(b, a) | see businessDiff |
m.monthBusinessDays() | workdaysInMonth(d) | |
m.monthBusinessDays(end) | [...eachWorkday(d.with({ day: 1 }), end)] | |
m.businessDaysIntoMonth() | workdayOfMonth(d) | 0 before the first workday of the month |
m.monthBusinessWeeks() | workdaysByWeek(d.with({ day: 1 }), d.with({ day: d.daysInMonth })) | uses your holidays and weekend |
m.monthBusinessWeeks(true) | workdaysByWeek(d, d.with({ day: d.daysInMonth })) | |
a.businessWeeksBetween(b) | workdaysByWeek(a, b) | |
m.monthNaturalDays() | plain Temporal, see below | |
m.monthNaturalWeeks() | plain Temporal, see below | |
m.businessAdd(n, 'hours') | addWorkingTime(dateTime, { hours: n }, { hours: [['09:00', '17:00']] }) | uses real working hours |
Side by side:
import moment from 'moment-business-days';
import { addWorkdays, workdaysBetween, workdaysInMonth } from 'temporal-workdays';
moment.updateLocale('en', { holidays: ['2026-11-26'], holidayFormat: 'YYYY-MM-DD', workingWeekdays: [1, 2, 3, 4, 5] });
const options = { holidays: ['2026-11-26'] };
moment('2026-11-25').businessAdd(1).format('YYYY-MM-DD'); // → '2026-11-27'
addWorkdays(Temporal.PlainDate.from('2026-11-25'), 1, options).toString(); // → '2026-11-27'
moment('2026-11-30').businessDiff(moment('2026-11-23')); // → 4
workdaysBetween(Temporal.PlainDate.from('2026-11-23'), Temporal.PlainDate.from('2026-11-30'), options); // → 4
moment('2026-11-10').monthBusinessDays().length; // → 20
workdaysInMonth(Temporal.PlainDate.from('2026-11-10'), options).length; // → 20To convert between the two while you migrate:
const fromMoment = Temporal.PlainDate.from(moment('2026-11-06').format('YYYY-MM-DD'));
fromMoment.toString(); // → '2026-11-06'
moment(fromMoment.toString()).format('YYYY-MM-DD'); // → '2026-11-06'Differences
businessDiff
moment-business-days counts the workdays from the earlier date up to, but not including, the later one. workdaysBetween counts the workdays after the earlier date up to and including the later one, which keeps workdaysBetween(d, addWorkdays(d, n)) equal to n. The results are the same when both dates are workdays. When either can be a weekend day or holiday and you need moment's exact numbers, move both dates back one day:
const saturday = Temporal.PlainDate.from('2026-11-07');
const monday = Temporal.PlainDate.from('2026-11-09');
moment('2026-11-09').businessDiff(moment('2026-11-07')); // → 0
workdaysBetween(saturday, monday); // → 1
workdaysBetween(saturday.subtract({ days: 1 }), monday.subtract({ days: 1 })); // → 0Other differences
- Dates are never changed in place.
m.nextBusinessDay()modifiesm, whilenextWorkday(d)returns a new date. - There are no search limits.
nextBusinessDay()stops after 7 days and then returns a day that isn't a business day.nextWorkdayalways finds the next workday, and throws only if the options contain no workdays at all. monthBusinessWeeks()assumes Monday to Friday, ignores holidays and adds an empty week when a month ends on a weekend.workdaysByWeekuses your weekend and holidays, starts each week after the weekend (on Sunday for a Friday and Saturday weekend) and returns no empty weeks.- On a weekend at the start of a month,
businessDaysIntoMonth()counts in the previous month.workdayOfMonthreturns 0. - Invalid input such as a fractional amount, an unknown option or an impossible date throws an error instead of being rounded or ignored.
- Moments are in local time. Here, use
PlainDatefor dates andZonedDateTimefor moments in time.
monthNaturalDays and monthNaturalWeeks
These list every day of the month, so plain Temporal does it:
const february = Temporal.PlainDate.from('2026-02-10');
// monthNaturalDays()
const days = Array.from({ length: february.daysInMonth }, (_, i) => february.with({ day: i + 1 }));
days.length; // → 28
// monthNaturalWeeks(), with weeks from Sunday to Saturday
const weeks = [];
for (const day of days) {
if (weeks.length === 0 || day.dayOfWeek === 7) weeks.push([]);
weeks[weeks.length - 1].push(day);
}
weeks.map((week) => week.length); // → [7, 7, 7, 7]
// moment-business-days adds an empty last week here
const momentWeeks = moment('2026-02-10').monthNaturalWeeks();
momentWeeks.map((week) => week.length); // → [7, 7, 7, 7, 0]