deadline_calc
Compute a legal or administrative time limit from a counting rule stated in the input: working or calendar days, weeks, months or years; counting from the next day or the same day; non-working weekdays, holidays and periods such as August; how months end; and moving a last day that falls on a non-working day. Never assumes a jurisdiction's rule.
Install
/tools install deadline_calc /tools install deadline_calc@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 · deadline_calc 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 deadline_calc; each run is approved as a local_exec.
Arguments
One JSON object on stdin, validated against the schema before the program starts.
| name | type | required | default | meaning |
|---|---|---|---|---|
start | string | yes | Day of the notification or publication. pattern ^\d{4}-\d{2}-\d{2}$ | |
amount | integer | yes | ≥ 0 | |
unit | string | yes | one of "working_days", "calendar_days", "weeks", "months", "years" | |
rule | object | yes |
Input schema (JSON)
{
"type": "object",
"required": [
"start",
"amount",
"unit",
"rule"
],
"additionalProperties": false,
"properties": {
"start": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Day of the notification or publication"
},
"amount": {
"type": "integer",
"minimum": 0
},
"unit": {
"type": "string",
"enum": [
"working_days",
"calendar_days",
"weeks",
"months",
"years"
]
},
"rule": {
"type": "object",
"required": [
"count_from",
"if_last_day_non_working"
],
"additionalProperties": false,
"properties": {
"count_from": {
"type": "string",
"enum": [
"next_day",
"same_day"
]
},
"non_working_weekdays": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1,
"maximum": 7
},
"description": "ISO weekdays; [6, 7] is Saturday and Sunday"
},
"holidays": {
"type": "array",
"items": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
}
},
"non_working_periods": {
"type": "array",
"items": {
"type": "object",
"required": [
"from",
"to"
],
"additionalProperties": false,
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
}
}
},
"description": "MM-DD every year, or YYYY-MM-DD"
},
"month_end": {
"type": "string",
"enum": [
"same_day",
"day_before"
],
"default": "same_day"
},
"if_last_day_non_working": {
"type": "string",
"enum": [
"next_working_day",
"keep"
]
}
}
}
}
}
Example
Input
{
"start": "2026-10-16",
"amount": 10,
"unit": "working_days",
"rule": {
"count_from": "next_day",
"non_working_weekdays": [
6,
7
],
"holidays": [],
"if_last_day_non_working": "next_working_day"
}
}
Output (stdout)
{
"end": "2026-10-30",
"weekday": "Friday"
}
Files
The command the install card registers is python3 deadline_calc.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.
deadline_calc.py · sha256 3ada1ec43dd9398de386566b3f0aa3833eebc179a98d6b100d58da94af5578fa
#!/usr/bin/env python3
"""Legal and administrative time limits from a rule the caller states: working or calendar
days, weeks, months or years; the day the count starts; which weekdays, dates and periods are
non-working; how a month ends; and what happens when the last day is non-working. The rule is
always given in the input; nothing is assumed about a jurisdiction.
Input (JSON on stdin):
start "YYYY-MM-DD" the day of the triggering act (notification, publication)
amount integer
unit "working_days" | "calendar_days" | "weeks" | "months" | "years"
rule {
"count_from": "next_day" | "same_day",
"non_working_weekdays": [6, 7], ISO weekdays; required for working_days
"holidays": ["YYYY-MM-DD", ...],
"non_working_periods": [{"from": "08-01", "to": "08-31"}], MM-DD (every year) or full dates
"month_end": "same_day" | "day_before", months/years: the same date number, or the day before it;
missing dates fall to the month's last day
"if_last_day_non_working": "next_working_day" | "keep"
}
"""
import calendar
import datetime as dt
import json
import sys
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
def fail(message):
print(json.dumps({"error": message}))
sys.exit(2)
def date(value, field):
try:
return dt.date.fromisoformat(value)
except (TypeError, ValueError):
fail(f"{field} must be a date YYYY-MM-DD")
def period_matcher(periods):
checks = []
for i, p in enumerate(periods):
if not isinstance(p, dict) or not isinstance(p.get("from"), str) or not isinstance(p.get("to"), str):
fail(f"rule.non_working_periods[{i}] needs from and to")
a, b = p["from"], p["to"]
if len(a) == 5 and len(b) == 5:
try:
ka, kb = (int(a[:2]), int(a[3:])), (int(b[:2]), int(b[3:]))
except ValueError:
fail(f"rule.non_working_periods[{i}] must be MM-DD or YYYY-MM-DD")
checks.append(lambda d, ka=ka, kb=kb: (ka <= (d.month, d.day) <= kb) if ka <= kb else ((d.month, d.day) >= ka or (d.month, d.day) <= kb))
else:
da, db = date(a, f"rule.non_working_periods[{i}].from"), date(b, f"rule.non_working_periods[{i}].to")
checks.append(lambda d, da=da, db=db: da <= d <= db)
return lambda d: any(c(d) for c in checks)
def add_months(start, months, month_end):
month_index = start.month - 1 + months
year, month = start.year + month_index // 12, month_index % 12 + 1
last = calendar.monthrange(year, month)[1]
target = dt.date(year, month, min(start.day, last))
if month_end == "day_before" and start.day <= last:
target -= dt.timedelta(1)
return target
def main():
try:
request = json.load(sys.stdin)
except ValueError as error:
fail(f"stdin is not JSON: {error}")
if not isinstance(request, dict):
fail("input must be a JSON object")
start = date(request.get("start"), "start")
amount, unit, rule = request.get("amount"), request.get("unit"), request.get("rule")
if not isinstance(amount, int) or isinstance(amount, bool) or amount < 0 or amount > 100000:
fail("amount must be a non-negative integer")
if unit not in ("working_days", "calendar_days", "weeks", "months", "years"):
fail("unit must be working_days, calendar_days, weeks, months or years")
if not isinstance(rule, dict):
fail("rule is required: the jurisdiction's counting rule is never assumed")
count_from = rule.get("count_from")
if count_from not in ("next_day", "same_day"):
fail("rule.count_from must be next_day or same_day")
weekdays = rule.get("non_working_weekdays")
if weekdays is None and unit == "working_days":
fail("rule.non_working_weekdays is required for working_days (e.g. [6, 7] for Saturday and Sunday)")
weekdays = set(weekdays or [])
if not all(isinstance(w, int) and 1 <= w <= 7 for w in weekdays):
fail("rule.non_working_weekdays must be ISO weekdays 1-7")
holidays = {date(h, "rule.holidays[]") for h in rule.get("holidays", [])}
in_period = period_matcher(rule.get("non_working_periods", []))
month_end = rule.get("month_end", "same_day")
if month_end not in ("same_day", "day_before"):
fail("rule.month_end must be same_day or day_before")
shift = rule.get("if_last_day_non_working")
if shift not in ("next_working_day", "keep"):
fail("rule.if_last_day_non_working must be next_working_day or keep")
def why_not_working(d):
if d.isoweekday() in weekdays:
return "weekday"
if d in holidays:
return "holiday"
if in_period(d):
return "period"
return None
skipped = {"weekday": 0, "holiday": 0, "period": 0}
first = start + dt.timedelta(1) if count_from == "next_day" else start
if unit == "working_days":
current, counted = first - dt.timedelta(1), 0
while counted < amount:
current += dt.timedelta(1)
reason = why_not_working(current)
if reason:
skipped[reason] += 1
else:
counted += 1
end = current if amount else start
elif unit in ("calendar_days", "weeks"):
days = amount * (7 if unit == "weeks" else 1)
end = first + dt.timedelta(days - 1) if days else start
else:
months = amount * (12 if unit == "years" else 1)
end = add_months(start, months, month_end)
nominal, shifted_by = end, 0
if shift == "next_working_day":
while why_not_working(end):
skipped[why_not_working(end)] += 1
end += dt.timedelta(1)
shifted_by += 1
if shifted_by > 3660:
fail("no working day within ten years after the nominal end")
result = {
"start": start.isoformat(), "amount": amount, "unit": unit, "first_day_counted": first.isoformat(),
"nominal_end": nominal.isoformat(), "end": end.isoformat(), "weekday": WEEKDAYS[end.weekday()],
"calendar_days_elapsed": (end - start).days, "shifted_days": shifted_by,
"skipped": {"non_working_weekdays": skipped["weekday"], "holidays": skipped["holiday"], "non_working_periods": skipped["period"]},
"disclaimer": "Computed from the rule given in the input, not legal advice: check the rule against the applicable law and the official calendar of non-working days.",
}
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/deadline_calc/1.0.0/manifest.json
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/deadline_calc/1.0.0/deadline_calc.py