
Costs
Subtract business days API: direction, request fields, and response shape
How to design a subtract business days API: putting direction in its own field, the request fields it must carry, and what a response should echo back.
A backward count over an interface has one design decision that matters more than the rest: whether the direction lives in the sign of the number or in a field of its own.
The rest of this page follows from that choice. It sets out the fields a request must carry, what a response must return to be worth trusting, how dates should travel, and the test vectors that expose a reversed rest-day rule. No service is named and none is recommended.
What to take away
- Put the direction in a separate field and reject a negative count. A sign is easy to lose in transit and silently reverses the answer.
- A response is only as useful as what it echoes backthe calendar version, the rest-day pattern and the convention it applied.
- Send date-only values as date-only text. A moment with an offset can arrive on the previous day.
The request
Field / Type
operationsubtract· Direction as a field, not a signanchorYYYY-MM-DD· The fixed date being counted back fromcount- Non-negative integer · Working days to move; zero is a valid request
weekend- Seven characters, Monday to Sunday,
1for rest · Monday to Friday is an assumption, not a default calendar{ id, version }· Which closure list, and which revision of itanchor_counts- Boolean · Whether the anchor day is day one when it is a working day
on_rest_daypreviousornext· What to do when the anchor is not a working day
The last two rows are the ones most interfaces omit, and they decide the answer. "Five business days before" is as ambiguous as "five business days after". An interface that resolves the ambiguity silently cannot be compared with anything.
Required request fields
- operationsubtract, direction as a field
- anchorYYYY-MM-DD date counted back from
- countnon-negative working days, zero valid
- weekendseven chars, Monday to Sunday
- calendarid and version of closure list
- anchor_countsis anchor day one
- on_rest_dayprevious or next
Why not a negative count
A client that clamps to zero, a serializer that drops the sign, or a form field that rejects a minus turns a backward count into a forward one. The result is a date after the anchor, returned with complete confidence.
Nothing about a forward date looks wrong until someone notices the deadline behind it. A separate operation field makes the reversal impossible without a visible change of value.
If you inherit an interface that uses a sign, reject zero-signed and positive values on the subtract endpoint and have the response repeat the operation it performed.
The response
result: 2027-03-05
anchor: 2027-03-15
skipped: [ 2027-03-13, 2027-03-14, 2027-03-10, 2027-03-07, 2027-03-06 ]
applied: { weekend: "0000011", calendar: { id, version }, anchor_counts: false, on_rest_day: previous }
The skipped list is the working shown. Anyone can check it against the calendar in seconds. It is also where a wrong closure list becomes visible: a date in the list that should not be closed, or a closed date missing from it. The applied block is what makes the answer reproducible after the calendar has changed.
Test vectors
Saturday-Sunday rest days, March 2027, nothing closed unless stated. A backward count treats a rest-day anchor as if it were the following Monday, which is the mirror of the forward rule.
Test vectors, March 2027
Anchor
- Monday 8th
- 5
- Saturday 6th
- 1
- Sunday 7th
- 1
- Friday 12th
- 3
- Monday 15th
- 5
- Monday 15th
- 5
- Saturday 6th
- 0
Count
- Monday 8th
- none
- Saturday 6th
- none
- Sunday 7th
- none
- Friday 12th
- none
- Monday 15th
- none
- Monday 15th
- Wednesday 10th
- Saturday 6th
- none
Closures
- Monday 8th
- Monday 1st
- Saturday 6th
- Friday 5th
- Sunday 7th
- Friday 5th
- Friday 12th
- Tuesday 9th
- Monday 15th
- Monday 8th
- Monday 15th
- Friday 5th
- Saturday 6th
- Saturday 6th
Expected
- Monday 8th
- Saturday 6th
- Sunday 7th
- Friday 12th
- Monday 15th
- Monday 15th
- Saturday 6th
The sixth row is the one to watch. A closure mid-window moved the result three calendar days, not one, because it pushed the landing day across a weekend. An implementation that subtracts closures after counting, rather than skipping them while counting, gets this row wrong.
The last row is a rest-day anchor with a zero count. The correct answer is the anchor, unchanged, which is not a working day. If the caller wanted a working day they should have asked for on_rest_day handling with a count of zero, and the interface should offer that rather than guess.
How a date should travel
Send 2027-03-15, not 2027-03-15T00:00:00-05:00. Local midnight in one zone is the previous evening in UTC. A server that normalizes to UTC before reading the date part will count back from the 14th. A date has no zone and should not be given one.
Do not build dates from separate numeric parts on the client without checking the library's month numbering. Some interfaces, including the JavaScript Date object, count months from zero, so the third month is 2, and a date assembled with 3 is in April. A text date sidesteps the problem entirely.
The general contract for a date crossing between a sheet and a service, serials on one side and text on the other, is under spreadsheet and API tools.
Versioning and repeatability
The same request against the same calendar version must return the same result forever. When the closure list changes, the version changes, and old results stay attached to the old version.
A caller who wants to know whether a previously computed date still holds sends the same request against the new version and compares. That deliberate act shows a visible diff, and a deadline change should work that way.
Backward errors make you late, so this direction matters more than the forward one. That asymmetry is under subtracting business days.
The interface must match formula-level behavior, including the rest-day rule, covered under the subtraction formulas. The forward interface it mirrors is under adding business days.
Where the same interface runs service clocks, the three-state model needs pause events the day-level request does not carry.
Where the endpoint rule may already be settled
For a legal deadline the counting method is often fixed by the procedural rules rather than by the interface's own convention, including how a period ending on a closed day is treated.
The federal civil rules in the United States state their method in Rule 6 on computing and extending time. Where such a rule governs a caller's deadline, the service has to be configurable to match it, which is another argument for making every convention an explicit parameter rather than a default.
Common questions
Should the interface accept an anchor that is a closure?
Yes, and say so in the response. Whether a deadline that lands on a closed day moves earlier or later is the receiving body's rule, and the interface should apply the on_rest_day value the caller asked for rather than a default.
What if the caller sends a count larger than the calendar covers?
Refuse. Counting back past the earliest date in the closure list treats that region as fully open, and the result is plausible and unchecked. Return an error naming the calendar's coverage.
Is the skipped list really necessary?
It is the only part of the response a non-specialist can verify. Without it, a wrong closure list produces a date that looks exactly like a right one.
Can one endpoint do both directions?
It can, with operation as the switch. It should not do it with a sign.







