"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.