Periods and calendar: deadline policy, add_calendar_period, add_business_days
In one sentence: these constructs answer the question “when a deadline falls”. Take them when a norm names a period (“within a month”, “ten business days”): the period policy says how to count, and the term counts; the calendar snapshot is attached only when the period runs in business days.
Deadlines are calendar steps and business-day counts under a named period policy: a business-day count always pins the official calendar snapshot, a calendar step never does.
1. When to take it and when not to
Section titled “1. When to take it and when not to”| Instead of | Selection rule |
|---|---|
add_calendar_period vs add_business_days | Period in calendar units (month, year, week, day-step) — add_calendar_period, no calendar snapshot is ever taken. Period in business days — add_business_days, the snapshot is always required, even with no_roll and even for calendar_day. The difference is in the term name; the calendar_day unit is allowed in both |
| Step vs measurement | “Deadline N ahead of a date” — a step (add_calendar_period / add_business_days). “How many days between dates” (penalty per day) — the days_between measurement: asks for no snapshot, counts no business days. Seven days as an interval is days_between, not a step (EU Regulation 261/2004, see corpus-forms.md) |
start_count same_day vs next_day | Text counts the trigger day (“from the day”) — same_day; text counts from the next day — next_day. Price of the mistake — exactly one day: 15.01 + 1 month gives 15.02 under same_day and 16.02 under next_day (checked against both policies in the second example package) |
month_end last_day_of_month vs absent | A month/year step across a nonexistent date (31.01 + 1 month) — the policy is mandatory: last_day_of_month gives 28.02, absence — MISSING_POLICY, not a silent choice. The field is read only by the month/year unit |
| Calendar snapshot vs case fact | The jurisdiction’s business calendar is pinned (a pinned dataset + law.lock line) — snapshot. Not pinned (an annual ministers’ decision that does not exist) — the business-day count comes as a case fact, and comparison against the limit is a literal in the rule (halal certification, see corpus-forms.md): honest “we do not count”, not an invented “weekends-only” calendar |
Grammar: the deadline window as an interval (EBNF verbatim from the grammar)
Section titled “Grammar: the deadline window as an interval (EBNF verbatim from the grammar)”Show syntax reference
A deadline window is an interval with value endpoints; a term boundary requires a named deadline policy, otherwise MISSING_POLICY. Goal-window syntax lives on the duty pages; only period computation lives here:
interval_expression = expression ;interval_literal = interval_left, interval_endpoint, ",", interval_endpoint, interval_right ;interval_left = "[" | "(" ;interval_right = "]" | ")" ;interval_endpoint = expression | "infinity" | "-infinity" ;2. Minimal example
Section titled “2. Minimal example”Package research.deadline.monthly: a report filed on day filed, deadline — one
month by calendar step, same_day policy without roll.
deadline policy CASE_POLICY { start_count same_day; include_end true; roll no_roll; month_end last_day_of_month;}
rule MonthlyDue(r: Report, filed: Date) strict { label ru-KZ official "Срок — месяц со дня подачи"; when filed_on(r, filed); then due_on(r, add_calendar_period(filed, 1 calendar_month));}Case 1: filed_on(r1, @2026-01-15), context with deadline_policy CASE_POLICY. Query: evaluate truth(due_on(r1, @2026-02-15)).
Case 2: filed_on(r2, @2026-01-31); query due_on(r2, @2026-02-28).
Observed engine answer (installed law, semantics law.core/0.2):
law test research.deadline.monthly: мир research.deadline.monthly ok [research.deadline.monthly#authored] tests/01-due.lawtest / urn:query:research-deadline-01 ok [research.deadline.monthly#authored] tests/02-month-end.lawtest / urn:query:research-deadline-02итого: 2 проверено, 2 прошли, 0 не прошли, 0 не исполнены; код 0law engine check — check OK, no warnings. The calendar step
takes no snapshot: the policy is read from the case environment (calendar
and policy come via the environment, not as an argument; the term arity is 2).
Sensitivity: removing filed_on gives NEITHER instead of TRUE_ONLY;
31 January gives 28 February only with month_end last_day_of_month —
without it the refusal is MISSING_POLICY (the two scenario tests).
Nearest wrong outcome (caught in trials): the same norm with
start_count next_day answers 16.02 instead of 15.02 — a one-day mistake with
no diagnostics, because both dates “look right”. The second
package is devoted to it.
Second package research.deadline.start — same rule, two policies:
| Policy | Filing | Question | Answer |
|---|---|---|---|
same_day | @2026-01-15 | due_on(@2026-02-15) | TRUE_ONLY |
next_day | @2026-01-15 | due_on(@2026-02-16) | TRUE_ONLY |
3. Example by domain
Section titled “3. Example by domain”- Law: French Civil Code (package
fr.code_civil, France) — five-year limitation by step (then echeance_prescription_le(c, add_calendar_period(depart, 5 calendar_year)), seecorpus-forms.md); the policy is a line in the case context (deadline_policy OECD_CODE_CIVIL_PRESCRIPTION). - Standard/protocol: EU Regulation 261/2004 (package
eu.transport.air_passenger_rights, European Union) — the seven-day period is computed withdays_between, not a step: no pinned source holds a calendar of non-business days for 27 member states, and opening an empty one would assert extras. - Religion/custom: Indonesian halal certification (package
id.halal, Indonesia) — business-day periods are NOT computed: the SKB calendar is not pinned, the count comes as a case fact. - Science/teaching case:
research.deadline.start— thestart_countprice of one day in miniature; the Turkish Constitution (packagetr.constitution, Türkiye) — a moratorium year by step, not recomputed in days: a year is not measured in days without a leap-year decision.
4. How the engine answers
Section titled “4. How the engine answers”Table — observed runs of this directory’s examples (installed
law, semantics law.core/0.2):
| Facts | Question | Answer | Why |
|---|---|---|---|
filing 15.01, same_day policy | due_on(15.02) | TRUE_ONLY | step to the corresponding date |
filing 31.01, month_end last_day | due_on(28.02) | TRUE_ONLY | nonexistent date — last day of the month |
filing 15.01, next_day policy | due_on(16.02) | TRUE_ONLY | count from the next day |
| no filing fact | due_on(…) | NEITHER | rule did not fire |
corpus term-window [appointed, add_calendar_period(appointed, 60 calendar_day)] | period by step | TRUE_ONLY with a policy, else MISSING_POLICY | a term boundary requires a named deadline policy (goal windows from the positions pages: 563 variable boundaries versus 3,929 literals) |
- The policy is a named package node (
deadline policy Name { … }), lowering — adeadline_policynode withcontentHash; the context carries only the reference; ambient policies and inline copies do not exist. - Without a policy (set by neither case nor profile) —
MISSING_POLICY: for a conclusion question this is a refusal status withmissing_inputsand the rule address, not just an issue. - The calendar step reads
start_countfrom the policy, and the month/year unit additionally readsmonth_end;rollmust beno_roll,include_end—true, otherwiseMISSING_POLICY, not interpretation. - “Period — a month, and a non-business day rolls to the next business one” is a
composition: the
add_calendar_periodresult feedsadd_business_dayswith zero business days; the calendar is then pinned explicitly. - Calendar questions do not read context axes: they take the date from the question itself.
- Month/year boundaries are the terms
month_start_of/month_end_of,year_start_of/year_end_of: neither calendar nor policy is read; weekday —weekday_of.
5. Common mistakes
Section titled “5. Common mistakes”- Wrong
start_count— a one-day mistake with no diagnostics (caught by trials;pitfalls.md, item 1). - No
month_endon a month step —MISSING_POLICY, not “last day” (pitfalls.md, item 2). business_dayonadd_calendar_period(and vice versa) —TYPE_ERROR: units do not mix (pitfalls.md, item 3).add_business_dayswithout a snapshot — refusal: the snapshot is always required, even withno_roll(pitfalls.md, item 4).- A “weekends-only” snapshot instead of a real calendar — an answer to the
dangerous side (earlier than real); honestly — a case fact (halal;
pitfalls.md, item 5). - Policy as a third term argument — no: arity 2, environment
(
LDC-E2116;pitfalls.md, item 6). cutoffoutside theHH:MM:SScanon —DEADLINE_POLICY_INVALIDwithout normalization (pitfalls.md, item 7).
6. References
Section titled “6. References”- The official calendar is external context: it is read as data and pinned by digest, never computed.
- Neighbouring pages: time (law date and
effective), duty (goal windows), lifecycle statuses (duty deadlines).
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.