Skip to content
docs
Arxo ↗

Terms: calendar, policy, and the business-day step

For LLMs7 sections

“Within ten business days” is neither a date nor a number. To turn these words into a day, three things must be known: which days are working, from which day to count, and what to do if the tenth day falls on a day off. The core guesses none of the three answers: the calendar is hashed data, the counting rule a declared policy, and the step a term that asks for both. Measured across the corpus: 52 packages count business days, 48 calendar families are pinned by locks.

The Archive of Veliky Ustin answers requests within ten business days, extends issue by a month, and requires visit notice three business days ahead. The archive takes working days from Kazakhstan’s official 2026 calendar.

Arxo Law
language "law.core" version "0.2";
package tutorial.archive version "0.6.0";
namespace "urn:law:tutorial:archive";
entity Person;
source KZ_OFFICIAL_CALENDAR_2026 {
kind law;
jurisdiction KZ;
number "calendar-2026";
}
calendar archive_calendar_2026 {
timezone "Asia/Almaty";
timezone_db "iana-tzdb@2026a";
period [@2026-01-01, @2027-01-01);
source KZ_OFFICIAL_CALENDAR_2026;
resource "artifact:corpus/clir/kz-official-2026.calendar.json";
format "law.calendar/0.1";
content_hash "sha256:a17b851ac08e622c05c116637ecf8d818bc7b480568f5560407d69461c4b8c21";
}
deadline policy ARCHIVE_BUSINESS_DAYS {
start_count next_day;
include_end true;
roll next_working_day;
}
deadline policy ARCHIVE_CALENDAR_MONTHS {
start_count same_day;
include_end true;
roll no_roll;
month_end last_day_of_month;
}
relation request_received(p: Person, on: Date) kind empirical { key(p); }
relation reply_due(p: Person, due: Date) kind institutional;
relation extension_requested(p: Person, on: Date) kind empirical { key(p); }
relation extended_until(p: Person, until: Date) kind institutional;
relation notice_sent(p: Person, on: Date) kind empirical { key(p); }
relation visit_scheduled(p: Person, on: Date) kind empirical { key(p); }
relation notice_timely(p: Person) kind institutional;
relation digitization_requested(p: Person, on: Date) kind empirical { key(p); }
relation digitization_due(p: Person, due: Date) kind institutional;
relation large_order(p: Person) kind empirical;

Three things without which there is no term

Section titled “Three things without which there is no term”

Calendar is a snapshot: zone, zone-database version, coverage period, source, and a dataset with the hash of its bytes. Holidays and shifts are snapshot data, not machine settings. The runner finds the dataset bytes via the package’s law.lock, or, for a bare program like here, via the artifact: address upward from the test file; in both cases it hashes the found bytes and accepts them only on a match with content_hash. A package holds one snapshot, chosen by itself; with two or more, the case must name it via the calendar axis, else AMBIGUOUS_CALENDAR before any derivation.

Policy is how to count. Three fields are mandatory: start_count — count from the event day or the next; include_end — whether the last day counts; roll — where to move a term landing on a non-working day. cutoff and month_end are optional. A policy is not a norm’s property but a case input: the package declares it, and the case selects it via the deadline_policy axis. Without it no term is computed: MISSING_POLICY, and the norm stays silent.

Step is a term, and there are two. add_business_days takes business_day and calendar_day and always asks the calendar, even at no_roll. add_calendar_period takes calendar_day, calendar_week, calendar_month, and calendar_year and never asks the calendar: a month is a step to the same date, not a count of days. The split is not stylistic: while the step was one, the snapshot requirement depended on the policy, and one implementation asked the calendar where another stayed silent, with green statics.

Arxo Law
rule ReplyDeadline strict {
for p: Person;
for on: Date;
when request_received(p, on);
then reply_due(p, add_business_days(on, 10 business_day));
}

The request arrived on Friday 6 March 2026. The next_day policy: count from the 7th. Saturday and Sunday do not count; 8 March is a holiday shifted to Monday the 9th. The first four business days are 10–13 March. Then 15 March, Constitution Day, a Sunday shifted to the 16th; 17–20 March make eight. Nauryz takes 21–25 March with shifts, and the ninth and tenth days fall on 26 and 27 March.

Date askedPolicyreply_due
27 Marchbusiness daysTRUE_ONLY
26 Marchbusiness daysNEITHER
27 Marchnot setNEITHER, MISSING_POLICY

The first row is the test from this page, byte for byte. Note the deadline_policy axis in the context and the calendar axis: the second is not mandatory here — the snapshot is one — but named for clarity.

Arxo Law
test "запрос получен в пятницу 6 марта — ответ до 27 марта" {
given {
context {
deadline_policy ARCHIVE_BUSINESS_DAYS;
decision_time @2026-04-01T09:00:00+05:00;
knowledge_time @2026-04-01T09:00:00+05:00;
legal_time @2026-03-06;
timezone "Asia/Almaty";
calendar archive_calendar_2026;
}
assert request_received(entity_ref("urn:tutorial:ivanova"), @2026-03-06) {
id "assert-request";
origin case_input;
}
}
evaluate truth(reply_due(entity_ref("urn:tutorial:ivanova"), @2026-03-27));
expect truth_status == TRUE_ONLY;
expect evaluation_status == COMPUTED;
}

The test name reads: “Request received Friday 6 March — reply due by 27 March.”

The policy the test must declare itself with the same deadline policy block — otherwise the reference will not resolve even at lowering (LDC-E1332). And runtime resolves it against the program’s CLIR, so the name in the test and the name in the package must match. The ready-made test file for this page starts with the same two policies.

Extending issue is by one month from the application date.

Arxo Law
rule ExtensionTerm strict {
for p: Person;
for on: Date;
when extension_requested(p, on);
then extended_until(p, add_calendar_period(on, 1 calendar_month));
}
ApplicationPolicyextended_until
31 Januarycalendar months28 February, TRUE_ONLY
31 Januarybusiness daysNEITHER, MISSING_POLICY

The first row is about month_end. February has no 31st, and the core does not choose for the act what to do about it: the policy must say last_day_of_month or reject_nonexistent, and with month and year units the field is mandatory. start_count decides here too: at same_day a month from 31 January ends 28 February, at next_day — 1 March. Both classical readings are expressed by one field.

The second row is the most important. A business-day policy with roll next_working_day is rejected for a calendar step, not interpreted: shifting to a working day is a working-calendar notion, and admitting it here would bring the calendar back through the back door. The consequence for authors: a case has one policy, and a case where a monthly term and a business-day term meet in one question will not execute. Such questions are asked separately — a norm needing both has composition: the add_calendar_period result feeds add_business_days with zero business days.

A backward count is a condition, not a date

Section titled “A backward count is a condition, not a date”

Visit notice is due no later than three business days ahead. The temptation to write add_business_days(visit, -3 business_day) is great, and check will let it through. At runtime such a step answers DEADLINE_POLICY_INVALID, and the refusal is not local: it rises to the whole document, and neighbouring terms of the same case become NEITHER without explanation. A norm about “no less than” judges an already performed action, and is written as a comparison:

Arxo Law
rule NoticeTimely strict {
for p: Person;
for sent, visit: Date;
when notice_sent(p, sent) and visit_scheduled(p, visit)
and days_between(add_business_days(sent, 3 business_day), visit) >= 0;
then notice_timely(p);
}
NoticeVisitnotice_timely
18 March27 MarchTRUE_ONLY
18 March20 MarchNEITHER

days_between yields a whole number of calendar days and does not read the calendar; it is fed the already business-day-computed boundary. Duration in the core is only measured: the Duration and CalendarPeriod types have no constructor, and 30 days is a magnitude incomparable with a whole number.

A digitization order is filled within ten business days, a large one within twenty. A general term with named exceptions is the term construction:

Arxo Law
term DigitizationTerm {
for p: Person; for on: Date;
from digitization_requested(p, on);
due digitization_due(p);
default 10 business_day
label ru official "общий срок исполнения заказа";
case Large when large_order(p): 20 business_day
label ru official "крупный заказ";
}

The labels read: “general order-fulfilment term” and “large order”.

term has no node of its own: the compiler unfolds it into a defeasible general-term rule, a special-case rule, an exception rule, and a priority of the case over the default. That is why the layer view of this page answers L1 — “a term with a defeasible default and special cases”. The step unit is read at runtime: business_day follows the working calendar, calendar_month a calendar step.

Order of 6 MarchDate askeddigitization_due
ordinary27 MarchTRUE_ONLY
large10 AprilTRUE_ONLY
large27 MarchFALSE_ONLY

The third row shows the special case does not merely win: the exception rule gives the general term a negative support, and the question “is the deadline 27 March?” for a large order is refuted, not left unanswered.

A date does not add with a magnitude; a term is built by a step, not by arithmetic:

Output
error LDC-E2108: «+»: несовместимые виды Date и Quantity — §58 требует
совпадения видов для сложения и вычитания

The diagnostic reads: “‘+’: incompatible kinds Date and Quantity — kinds must match for addition and subtraction”.

A policy without a mandatory field is a refusal at check, not MISSING_POLICY at runtime; a repeated field too:

Output
error LDC-E1307: политика §86 неполна: не заданы roll; на исполнении это
читалось бы как MISSING_POLICY («политики нет»)
error LDC-E1207: поле политики "roll" задано дважды (§86)

The diagnostics read: “policy is incomplete: roll unset; at runtime this would read as MISSING_POLICY (‘no policy’)” and “policy field ‘roll’ is set twice”.

A day count is whole, and it cannot be compared with a magnitude:

Output
error LDC-E2108: «>=»: несравнимые виды Integer и Quantity — §57/§48
требуют совпадения видов, неявных конверсий нет

The diagnostic reads: “‘>=’: incomparable kinds Integer and Quantity — kinds must match, no implicit conversions”.

A term holds exactly one general term:

Output
error LDC-E0201: term содержит ровно одну клаузу `default` (DECISION-0101)

The diagnostic reads: “‘term’ holds exactly one default clause”.

All five refusals belong to the language. The negative step cannot be pinned the same way: check lets it through, and the lesson stays the text above.

A term computed here is a date in a fact. In the duty tutorial such a date became the aim window’s boundary; now it need not be fed in the case but derived by a rule from the event date. The norm’s own window — effective — stayed in the time tutorial, and that is still another axis.

The exercise for this page is /tutorials/exercise-deadlines/.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.