Calendar,
een module voor kalenderberekeningen
over hoe Erik Groenhuis met datums stoeit

Voorgeschiedenis
Voor het nieuwe jaar 2002 wilde ik mijn moeder een kalender cadeau doen. Niet zomaar een willekeurige kalender, maar een die aan zeer specifieke eisen voldoet. Minstens A3 formaat, 1 pagina per maand, met een groot vak voor iedere dag. De dagen naast elkaar, zodat iedere week een strook vormt. Die stroken boven elkaar. Maandag als eerste dag van de week. Namen van de dagen langs de bovenkant, nummers van de weken langs de zijkant. Jaartal links en rechts aan de bovenkant, met de naam van de maand ertussen. Dag van de maand in ieder vak. Zie het voorbeeld voor oktober 2003.

Gedachten begonnen bij mij te borrelen. Zo iets zou je in Draw kunnen tekenen, maar dat moet je dan voor iedere maand overdoen. De vormgeving is eigenlijk heel regelmatig, en leent zich er heel goed voor om gegenereerd te worden door een programma. Een goed middel daarvoor is DrawScript.

Dat is een uitbreiding op BASIC met instructies om een Drawfile op te bouwen. Verder zijn alle gewone BASIC faciliteiten beschikbaar, zoals berekeningen met variabelen, goniometrische functies en dingen als FOR/NEXT loops en PROCs. Dit maakt DrawScript tot een krachtig gereedschap. De website geeft wat fraaie voorbeelden.

Na wat sleutelen lukte het al snel om kalenderpagina's te genereren die aan alle eisen voldoen. Een probleem dat al gauw de kop op stak was dat je voor iedere maand moest weten op welke dag van de week de eerste dag van de maand valt. Er moet ook rekening gehouden worden met het aantal dagen dat een maand lang is. Uiteindelijk kon ik deze vragen reduceren tot twee gegevens: de dag van de week waarop 1 januari valt, en het jaartal. Die moeten met de hand ingesteld worden, en het programma doet de rest.

Verder rekenen
Toch bleef de vraag knagen. Kalenderberekeningen zijn vaker nodig. Nu heeft RISC OS wel wat ingebouwd, maar dat heeft zijn beperkingen. Het kent bijvoorbeeld alleen de Gregoriaanse kalender. En de telling gaat niet verder terug dan 1 jan 1900 en loopt niet voorbij het jaar 2248 (RISC OS doet het wat dat betreft trouwens beter dan concurrerende systemen, die soms niet verder gaan dan 2038). Dit is natuurlijk ruim voldoende voor eigentijdse berekeningen, maar niet voor historisch werk.

Bijvoorbeeld in de Genealogie: bij dit stamboomonderzoek gaan niet alleen datums al snel terug tot vóór 1900, je krijgt ook te maken met vreemde kalenders, zoals die van de Franse Revolutie (in Nederland een paar jaar van kracht aan het begin van de 19e eeuw) of de Gregoriaanse.

Even zoeken
Daar was vast al iets voor bedacht. Wat zoeken op internet levert al snel een grote verzameling programmaatjes op van uiteenlopende kwaliteit. Voor mijn eigen toepassing zouden wat routines in C of geconverteerd naar BASIC al genoeg zijn. Maar hoe moet dat als iemand anders er gebruik van wil maken? Stel dat een programmeur, net als in mijn voorbeeld van het maken van een gedrukte kalender, alleen maar enkele simpele berekeningen nodig heeft. Dan zijn de rest van de routines alleen maar overtollige ballast. En hoe moet het als iemand het wil opnemen in Assembler code?

Voor dit probleem heeft RISC OS de ideale oplossing: het Module. De routines kunnen makkelijk vanuit iedere programmeertaal worden aangeroepen. Geen last van blokken overbodige sourcecode in je BASIC. Het module hoeft maar één keer geladen te worden en verschillende programma's kunnen er gebruik van maken.

Module
Doordat ik indertijd nog lid was van de RISC OS Foundation had ik de mogelijkheid om een officieel SWI blok nummer aan te vragen. Dit ging gemakkelijk en vlot, uiteraard per email. Het gaf mij als amateur-programmeur een grote voldoening om een werkstuk verheven te zien worden boven het gemiddelde Fröbel-werk dat ik meestal maak. Een extra reden om er een goed produkt van te maken.

Een aardige complicatie lag nog in de naam van het module. Het engelse woord voor kalender is "calendar". Let op de letter 'a' aan het eind. Het woord "calender" bestaat ook, maar dat is een pers om papier of stof een glanzend oppervlak te geven. In het nederlands is dat weer een kalandermachine. Als ik voor iedere keer dat ik me vergistte een kwartje kreeg, dan had ik nu een grote stapel lastig te wisselen munten.

Wat moet er in
Als je zo iets maakt moet het ook meteen maar goed. Ik wilde dus zoveel mogelijk verschillende kalenders ondersteunen (naast de bovengenoemde bijvoorbeeld ook de Joodse en de Islamitische). Per kalender moeten er voldoende faciliteiten zijn. En voor de gebruiker (d.w.z. de programmeur die de SWIs aanroept) is het handig als de manier van aanroepen van een SWI niet afhankelijk is van de kalender waar het over gaat.

Een belangrijke functie is ook het omrekenen van een datum in de ene kalender naar de overeenkomstige datum in de andere. De truc daarvoor is om de dagen in de geschiedenis opvolgend te nummeren. Zo krijgt iedere dag een uniek nummer dat onafhankelijk is van de kalenderdatum. Omrekenen tussen twee kalenders gaat nu zo: converteer eerst van de datum in de eerste kalender naar het unieke dagnummer, en vervolgens van het dagnummer naar de datum in de andere kalender. Dit systeem maakt het makkelijk om extra kalenders aan het repertoire toe te voegen. Ook het vinden van het aantal dagen verschil tussen twee data wordt simpel: je hoeft alleen de dagnummers van elkaar af te trekken.

Dan moet alleen nog maar een beslissing genomen worden over het beginpunt van de telling. Op zich maakt het niet zoveel uit. Het is handig om het begin flink ver in de geschiedenis te leggen, zodat je niet zo snel met negatieve dagnummers te maken krijgt. Wat ik wel besefte is dat als de keuze eenmaal gemaakt is je er niet meer aan moet sleutelen. Het is namelijk onvermijdelijk dat gebruikers de dagnummers intern gaan gebruiken (bijvoorbeeld om het datumveld in een spreadsheet te coderen). Als later het beginpunt van de dagnummers verandert dan kloppen plots alle datums die eerder opgeslagen zijn niet meer. De uiteindelijke keuze heb ik overgenomen van een verzameling routines die ik gevonden had: dag 1 valt op 1 januari in het jaar 1 in de Gregoriaanse kalender.

Vanwege de lengte van het artikel in de volgende Asterisk* het tweede deel.