business_days
Count the working days between two dates, add or subtract working days to a date, or list them, skipping weekends and a given holiday list. Deadlines, SLAs and administrative terms.
Install
/tools install business_days /tools install business_days@1.0.0
In a console session, or let the model find it in normal mode: axlr_catalog describes it and axlr_install_tool asks for the same install card. The card reads catalog axlr · business_days 1.0.0 · verified once the index signature, the manifest digest and every file digest check out. Then the model runs it with axlr_run_tool business_days; each run is approved as a local_exec.
About
Working-day arithmetic for deadlines and administrative terms: count the working days in an
inclusive range, add or subtract working days to a date, or list them. Weekends default to
Saturday and Sunday (weekend takes other ISO weekdays, such as [5, 6] for Friday–Saturday)
and holidays is an explicit list, because public holidays differ by country, region and
year and a bundled table would silently go stale.
Limits: no half days; "add 0" returns the start date even when it is not a working day.
Arguments
One JSON object on stdin, validated against the schema before the program starts.
| name | type | required | default | meaning |
|---|---|---|---|---|
operation | string | yes | one of "count", "add", "list" | |
start | string | yes | pattern ^\d{4}-\d{2}-\d{2}$ | |
end | string | no | Inclusive; count and list. pattern ^\d{4}-\d{2}-\d{2}$ | |
days | integer | no | Working days to add (negative subtracts); add | |
holidays | array of string | no | [] | |
weekend | array of integer | no | [6, 7] | ISO weekdays that are not working days |
Input schema (JSON)
{
"type": "object",
"required": [
"operation",
"start"
],
"additionalProperties": false,
"properties": {
"operation": {
"type": "string",
"enum": [
"count",
"add",
"list"
]
},
"start": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"end": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Inclusive; count and list"
},
"days": {
"type": "integer",
"description": "Working days to add (negative subtracts); add"
},
"holidays": {
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
"default": []
},
"weekend": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1,
"maximum": 7
},
"default": [
6,
7
],
"description": "ISO weekdays that are not working days"
}
}
}
Example
Input
{
"operation": "add",
"start": "2026-12-23",
"days": 10,
"holidays": [
"2026-12-25",
"2027-01-01",
"2027-01-06"
]
}
Output (stdout)
{
"result": "2027-01-11"
}
Files
The command the install card registers is python3 business_days.py, run in the workspace with PATH and HOME as its only environment. Each file's SHA-256 is in the signed index; the console checks them before showing the card and again before writing.
business_days.py · sha256 9945f79f9b2d1b76bd8566ed2be5e90f87dbf33ed7353b5abc03d3c13c2d8920
#!/usr/bin/env python3
"""Business-day arithmetic: count working days between dates, add working days to a date,
or list the working days of a range. Weekends and an explicit holiday list are skipped.
Input (JSON on stdin):
operation "count" | "add" | "list"
start "YYYY-MM-DD"
end "YYYY-MM-DD" (count, list; inclusive)
days integer (add; may be negative)
holidays ["YYYY-MM-DD", ...] optional
weekend [6, 7] optional ISO weekdays that are not working days (default Saturday, Sunday)
"""
import datetime as dt
import json
import sys
def fail(message):
print(json.dumps({"error": message}))
sys.exit(2)
def parse(value, field):
try:
return dt.date.fromisoformat(value)
except (TypeError, ValueError):
fail(f"{field} must be a date YYYY-MM-DD")
def main():
try:
request = json.load(sys.stdin)
except ValueError as error:
fail(f"stdin is not JSON: {error}")
operation = request.get("operation")
weekend = set(request.get("weekend", [6, 7]))
holidays = {parse(h, "holidays[]") for h in request.get("holidays", [])}
working = lambda d: d.isoweekday() not in weekend and d not in holidays
start = parse(request.get("start"), "start")
if operation in ("count", "list"):
end = parse(request.get("end"), "end")
if end < start:
fail("end is before start")
days = [start + dt.timedelta(i) for i in range((end - start).days + 1)]
found = [d for d in days if working(d)]
result = {"operation": operation, "start": start.isoformat(), "end": end.isoformat(), "business_days": len(found),
"calendar_days": len(days)}
if operation == "list":
result["days"] = [d.isoformat() for d in found]
elif operation == "add":
days = request.get("days")
if not isinstance(days, int):
fail("days must be an integer")
step = 1 if days >= 0 else -1
current, left = start, abs(days)
while left:
current += dt.timedelta(step)
if working(current):
left -= 1
result = {"operation": "add", "start": start.isoformat(), "days": days, "result": current.isoformat(),
"weekday": current.strftime("%A")}
else:
fail("operation must be count, add or list")
print(json.dumps(result))
if __name__ == "__main__":
main()
Where it is served
The registry serves only AXLR clients, so a browser gets 403; fetch with a User-Agent that starts with axlr:
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/business_days/1.0.0/manifest.json
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/business_days/1.0.0/business_days.py