RFC-032: Datumonderdelen en afkapping

ProposedImplemented
Depends on

Context

De verrijker vroeg over vier ronden tien keer om een datumbewerking. Zes verschillende namen: YEAR, MONTH, DAY_OF_MONTH, START_OF_MONTH, een periodetype, en een “kalendermaand volgende op”. Ze komen neer op twee functies.

Die achterstand was af te lezen uit het corpus zelf. Een markering met resolution: operation is de bewering dat een bewerking ontbreekt, en sinds RFC-031 noemt resolved_by verplicht wat die bewering zou oplossen. Groepeer de markeringen op dat veld en de gevraagde bewerkingen staan op een rij, met hun frequentie erbij. Zonder dat veld waren dit tien losse aantekeningen in tien bestanden gebleven.

De vraag eronder gaat over tellen. Vier gevraagde namen zijn twee functies op drie kalendereenheden, en de taal heeft al een plek waar een kalendereenheid staat: het veld in van DATE_DIFF uit RFC-021.

Wat er al was

De motor kende vijf datumbewerkingen en ze zijn alle vijf anders geparameteriseerd. DATE bouwt een datum uit year, month en day, waar de eenheid de veldnaam is. DATE_ADD telt op met dezelfde vorm. DATE_DIFF neemt de eenheid als waarde in in. AGE en DAY_OF_WEEK dragen hun eenheid in hun naam.

Die spreiding is een spoor. AGE is DATE_DIFF ... in: years met een eigen naam, geschreven voordat DATE_DIFF bestond; RFC-021 zegt dat zelf. DAY_OF_WEEK is het spoor van één wet die ooit een weekdag nodig had. Dat DAY_OF_WEEK bestaat en DAY_OF_MONTH niet, is dus geen gat in een ontwerp. Vier namen erbij verlengen dat spoor met vier.

Decision

Twee bewerkingen erbij, en niet vier.

operation: DATE_PART # datum -> geheel getal date: $een_datum in: year | month | day operation: START_OF # datum -> datum date: $een_datum in: year | month

YEAR, MONTH, DAY_OF_MONTH en START_OF_MONTH komen er niet als aparte namen. DAY_OF_WEEK blijft staan zoals hij is.

De regel eronder: een parameter is toegestaan wanneer hij loopt over een gesloten dimensie die al elders in de taal staat. Een vrij mode-veld is dat niet, en daarom viel het in RFC-024 af. Een kalendereenheid is het wel.

Voor DATE_PART is die dimensie exact het argumentenlijstje van DATE. DATE_PART is de inverse van DATE en leest terug wat DATE opbouwt. Daarmee is beantwoord waarom in: month bestaat terwijl geen enkele wettekst erom vraagt: een nieuwe bewerking heeft een wettekst nodig, een nieuwe eenheidswaarde binnen een dimensie die de taal al volledig kent niet. De inverse van een bestaande bewerking is compleet of hij is willekeurig.

Diezelfde regel houdt de weekdag erbuiten. Een weekdag hoort niet bij de jaar-maand-dagontleding, valt dus buiten de dimensie, en zou een grondtekst nodig hebben. Vraagt een volgende wettekst opnieuw om een weekdag, dan is de zet weekday aan DATE_PART toevoegen en DAY_OF_WEEK degraderen tot compat-alias via het mechanisme dat de motor daarvoor al heeft. Geen enkele wet in het corpus gebruikt DAY_OF_WEEK, alleen een conformance-fixture, dus die degradatie kost later niets.

Voor START_OF is de dimensie krapper. day is de identiteit, en week vraagt een conventie (ISO-maandag) die geen wettekst oplegt. END_OF is de spiegel van START_OF en komt er niet in, omdat alle gevonden wetteksten naar beneden afkappen en geen enkele naar boven.

Vaste schrijfwijze

Vier van de vijf wetteksten die om deze bewerkingen vroegen, lopen uit op dezelfde twee regels: de eerste dag van de kalendermaand volgende op de maand waarin iets gebeurt. Die figuur krijgt één schrijfwijze en die staat vast.

operation: DATE_ADD date: operation: START_OF date: $peildatum in: month months: 1

Afkappen gaat vóór optellen. In die volgorde is de uitkomst exact voor elke invoerdatum, inclusief de eerste van de maand en inclusief 31 januari, want de afgekapte datum is dag 1 en die bestaat in elke maand. De dagklem van DATE_ADD (31 januari plus een maand is 28 februari) komt er dus nooit aan te pas. De omgekeerde volgorde geeft toevallig hetzelfde antwoord, maar redeneert via die klem, en dat is een controle die een reviewer elke keer opnieuw moet doen.

Waar de wet vraagt of iets ná de eerste dag van de maand gebeurde, staat er een dagnummer: GREATER_THAN(DATE_PART($d, in: day), 1). Een wijziging óp de eerste telt in die maand zelf mee, en dat is de enige casus die deze vraag hardop stelt. De schrijfwijze EQUALS($d, START_OF($d, in: month)) levert hetzelfde op en leest als een puzzel.

Why

Benefits

  • Eén bewerking heeft één schema-ingang, één beschrijving en één plek in de editor. De agent die zoekt hoe hij een datumdeel leest, vindt één antwoord in plaats van drie gescheiden antwoorden waarvan hij er twee nooit ziet.
  • Het berekeningsjaar uit Awir art. 2 is vandaag een aangeleverd gegeven, omdat de motor het niet uit de peildatum kon halen. Met DATE_PART vervalt die invoer, en daarmee een hele klasse inconsistente invoer: een berekeningsjaar dat niet bij de peildatum hoort.
  • De figuur “de eerste dag van de kalendermaand volgende op” komt overal in het socialezekerheidsrecht terug. Eén vaste schrijfwijze van twee regels maakt hem in alle drie de gevallen herkenbaar.

Tradeoffs

  • Enkelvoud tegenover meervoud. DATE_DIFF telt en schrijft days|months|years; DATE_PART en START_OF selecteren en schrijven year|month|day. Dit is de zwakste plek van het voorstel: een agent die kopieert schrijft ooit in: months bij START_OF. Het schema vangt dat met een enum, en de motor noemt de fout bij naam in plaats van hem stil te accepteren. Eén meervoudsvorm voor beide betekenissen was het alternatief, en in: years voor “het jaartal” leest verkeerd genoeg om die prijs niet te betalen.
  • Een maandnummer is alleen betekenisvol binnen één jaar. Twee maandnummers vergelijken over een jaargrens heen is vrijwel altijd fout, en in: month maakt die fout schrijfbaar. De schemabeschrijving waarschuwt ervoor; geen van de tien verzoeken had hem nodig.
  • AGE blijft ongemoeid. Hij is DATE_DIFF ... in: years met een eigen naam en een eigen grondslag (BW art. 1:2), en hem nu opruimen raakt wetten in het corpus.

Alternatives Considered

Alternatief 1: vier losse namen (YEAR, MONTH, DAY_OF_MONTH, START_OF_MONTH)

Afgewezen. Het vermoeden achter deze route is dat weinig geparameteriseerde bewerkingen moeilijker fout te gebruiken zijn. Voor extractie klopt dat niet. De fout die een agent hier maakt is semantisch: maanden van verschillende jaren vergelijken, rekenen op een dagnummer. MONTH als eigen naam maakt die fout even waarschijnlijk als in: month. De parameterisatie wint op vindbaarheid.

Alternatief 2: alleen DATE_PART, en START_OF weglaten

START_OF is volledig afleidbaar uit DATE_PART en het bestaande DATE: lees het jaar, lees de maand, zet day: 1. Onder een zuinige lezing is één nieuwe bewerking dus genoeg. RFC-024 sluit die lezing af: dit is een rekenketen die een afkapping benadert, negen regels lang, met de afkapping verstopt in de literal day: 1, en RFC-024 verwierp zulke ketens uitdrukkelijk. Een afkapping krijgt een naam. De negenregelige schrijfwijze hoort in de reverse-validatie als aan te merken omweg, naast de afrondingsketens die RFC-012 al noemt.

Het tweede gevolg van RFC-024 zit in wat START_OF niet mag worden. Geen eigenschap van een datumtype, geen stille normalisatie op de grens van een bewerking, geen impliciet gedrag van type: date. De afkapping staat in de wet-YAML op de plek waar de wet hem voorschrijft, en nergens anders. Dat is dezelfde reden waarom RFC-024 het afronden weghaalde bij de eenheidscoercie van RFC-015.

Alternatief 3: een periodetype

De verrijker vroeg om een type voor “de periode dat het bevel ten uitvoer wordt gelegd”, omdat dat in de wettekst één zelfstandig naamwoord is. Afgewezen. Een periode is een paar <naam>_begindatum en <naam>_einddatum, met een lege einddatum voor een lopende periode. Een echt periodetype zou vergelijking, bevatting, rekenkunde, schema, type_spec en taint-propagatie nodig hebben voor een begrip dat met twee datums al volledig werkt. De “voor zover”-clausule maakt dat zichtbaar: het eerste wat zo’n type zou moeten kunnen is zijn begin verschuiven, en dat is DATE_ADD op de begindatum.

Alternatief 4: in mag een variabele zijn

DATE_DIFF staat een $variabele toe als eenheid en het schema noemt die mogelijkheid naast de enum. DATE_PART en START_OF doen dat niet. Geen wettekst laat het te lezen datumdeel zelf uit een berekening volgen, en een harde enum is de plek waar de fout van een agent goedkoop wordt gevangen.

Implementation Notes

  • DATE_PART levert een Int, START_OF een string in canonieke YYYY-MM-DD vorm, zodat de uitkomst zowel onder EQUALS als onder de ordening klopt (RFC-021).
  • Beide geven een untranslatable invoer door als waarde in plaats van erop te falen, zoals RFC-012 voorschrijft, en beide eisen de canonieke datumvorm van RFC-021.
  • Beide moeten in een value genest staan en zijn niet geldig op actieniveau, net als de andere datumbewerkingen.
  • Het model leest in als een gewone string. Het schema is daarin strenger dan het model, en de motor noemt een onbekende waarde bij naam (START_OF 'in' must be one of 'year', 'month', got 'months'). De conformance-suite meet dat verschil als bekende afwijking in plaats van het te verzwijgen.
  • bdd/grammar.yaml heeft geen nieuwe stappen nodig. De grammatica gaat over invoer, uitvoering en assertie, en niet over bewerkingen: output "x" equals {n} dekt DATE_PART en output "x" equals "2026-03-01" dekt START_OF.

RegelRecht

An exploration by Bureau Architectuur of the Dutch Ministry of Economic Affairs and Climate Policy into the possibilities of transparent, executable legislation.

Links

GitHub repository
How it works
Stay informed
Roadmap (Dutch)
Documentation
Research

Contact

regelrecht@minbzk.nl

Part of

Bureau Architectuur
Ministry of Economic Affairs and Climate Policy