"The invoice is due on the 20th." Simple, right? Then reality shows up.
Bug 1: the deadline moves a day when the server changes time zone
new Date('2026-11-20') // midnight UTC = Nov 19 in New York JavaScript Date is a moment in time, not a calendar day. If you store deadlines as
Date, a time zone or daylight-saving change can silently move them by a day.
Fix: treat deadlines as plain YYYY-MM-DD strings and do integer day arithmetic.
Bug 2: "every 31st" in February
Does a monthly deadline on the 31st skip February? Most businesses expect it on
the last day of the month instead — Feb 28, or 29 in leap years.
Bug 3: the weekend adjustment that jumps to the next month
"If it's a weekend, move to the next business day" works — until Saturday,
January 31st becomes Monday, February 2nd, and lands in the wrong accounting period.
Finance solved this long ago with modified following: move forward, unless that
changes the month; then move back.
Bug 4: business days that aren't Monday to Friday
In many countries the weekend is Friday and Saturday. And every country, state and
company has its own holidays.
A small library for all of it
I packaged these rules in duedates, a
zero-dependency TypeScript library:
import { createCalendar, nextDue, occurrences } from '@contalert/duedates'; const calendar = createCalendar({ weekend: [6, 7], // ISO weekdays: Saturday, Sunday holidays: ['2026-12-25'], }); // Last day of each quarter, modified following occurrences( { type: 'dayOfMonth', day: 'last', months: [3, 6, 9, 12], adjust: 'modifiedFollowing' }, { from: '2026-11-01', count: 4, calendar }, ); // 5th business day of the month nextDue({ type: 'businessDayOfMonth', n: 5 }, { from: '2026-12-01', calendar }); // { nominal: '2026-12-07', due: '2026-12-07' } It doesn't ship holiday data on purpose: holiday rules change and you know which
ones apply to you. Pass a list, or plug in a holiday library.
npm install @contalert/duedates If you've handled deadline logic in production, I'd love to hear which edge cases
bit you — issues and PRs are welcome.

