#!/usr/bin/apl -f
⍝ index of Bits and PiecesExplanations in English Explications en français.
⍝ Full text of the GPL License
∇ calfr∆license
'APL program to convert Gregorian dates'
'to French Revolutionary dates and the other way'
''
'Copyright (C) 2015, 2016 Jean Forget  (JFORGET at cpan dot org)'
''
'Build date: 2016-02-02'
''
'Portability: L1 (tested with GNU-APL and NARS2000)'
''
' This program is distributed under the GNU Public License version 1 or later'
''
' You can find the text of the license in the LICENSE file or at'
' http://www.gnu.org/licenses/gpl-1.0.html.'
''
' Here is the summary of GPL:'
''
' This program is free software; you can redistribute it and/or modify'
' it under the terms of the GNU General Public License as published by'
' the Free Software Foundation; either version 1, or (at your option)'
' any later version.'
''
' This program is distributed in the hope that it will be useful,'
' but WITHOUT ANY WARRANTY; without even the implied warranty of'
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the'
' GNU General Public License for more details.'
''
' You should have received a copy of the GNU General Public License'
' along with this program; if not, write to the Free Software Foundation,'
' Inc., <http://www.fsf.org/>.'
∇

⍝ Installing from GitHub (https://github.com/jforget/apl-calendar-french):
⍝     clone the Git repo and copy "workspaces/calfr.apl" to the "workspaces" sub-directory of your GNU-APL directory
⍝                         or copy "workspaces/calfrnars.atf" to some place where NARS2000 will find it
⍝ Installing from browsing https://www.gnu.org/software/apl/Bits_and_Pieces/
⍝     save the webpage as simple text into the "workspaces" sub-directory of your GNU-APL directory
∇ calfr∆usage
'Loading the module from the command line (for GNU-APL):'
'      apl -f workspaces/calfr.apl'
'Loading the module from inside GNU-APL:'
'      )LOAD calfr'
'Loading the module from inside NARS2000:'
'      )IN calfrnars'
'Dates are vectors of 3 numbers YYYY MM DD. e.g.'
'      DR ←    8 2 18 ⍝ for 18 Brumaire VIII'
'      DG ← 1794 7 27 ⍝ for 27th July 1794'
'Gregorian to French Revolutionary:'
'      DR ← calfr∆gr2fr DG'
'French Revolutionary to Gregorian:'
'      DG ← calfr∆fr2gr DR'
'Pretty-printing:'
'      STRING ← calfr∆prtfr DR'
'Pretty-printing today''s date:'
'      STRING ← calfr∆prtfr calfr∆gr2fr 3↑⎕TS'
∇

∇ R ← calfr∆year D
R ← D +.× 1 0 0
∇

∇ R ← calfr∆month D
R ← D +.× 0 1 0
∇

∇ R ← calfr∆day D
R ← D +.× 0 0 1
∇

∇ R ← calfr∆zerojanvnd Y
R ← Y ∘.× 1 0 0
R ← R + (⍴R) ⍴ 0 1 0
∇

∇ R ← calfr∆fr2rd DATE
R ← 654019 + (calfr∆frbis calfr∆year DATE) + DATE +.× 365 30 1
∇

∇ R ← calfr∆frbis YEAR
YEAR ← YEAR - 1
R ← - / ⌊ YEAR ∘.÷ 4 100 400 4000
R ← R + YEAR ∈ 3 7 11 15
∇

∇ R ← calfr∆gr2rd DATE; DIM
DIM ← ⍴ DATE
DATE←(DIM ⍴0 1 0) + DATE + (2≥calfr∆month DATE) ∘.× ¯1 12 0
R ← ¯428 + (-/ ⌊ (calfr∆year DATE) ∘.÷ 4 100 400) + ⌊DATE +.× 365 30.6 1
∇

∇ R ← calfr∆rd2fr N; ⎕IO; N1; YH; YL; YI; YR; DR; NR; CM; Y; M; D
⎕IO ← 1
N1 ← N - 654414
YH ← ⌈ N1 ÷ 365.24
YL ← ⌈ N1 ÷ 365.34
YI ← 0 , ⍳ ⌈/,YH-YL
YR ← YL ∘.+ YI
DR ← calfr∆zerojanvnd YR
NR ← calfr∆fr2rd DR
CM ← NR < N ∘.+ (⍴YI)⍴0
Y ← ⌈/YR×CM
D ← N - ⌈/NR×CM
M ← ⌈D÷30
D ← D - 30 × M - 1
R ← (Y ∘.× 1 0 0) + (M ∘.× 0 1 0) + D ∘.× 0 0 1
∇

∇ R ← calfr∆rd2gr N; ⎕IO; YH; YL; YI; YR; DR; NR; CM; Y; M; D; ML; MR
⎕IO ← 1
YH ← ⌈ N ÷ 365.24
YL ← ⌈ N ÷ 365.25
YI ← 0 , ⍳ ⌈/,YH-YL
YR ← YL ∘.+ YI
DR ← calfr∆zerojanvnd YR
NR ← calfr∆gr2rd DR
CM ← NR < N ∘.+ (⍴YI)⍴0
Y ← ⌈/YR×CM
D ← N - ⌈/NR×CM
ML ← ⌈D÷31
MR ← ML ∘.+ 0 1
DR ← (Y ∘.× 2 3 ⍴ 1 0 0) + (MR ∘.× 0 1 0)
NR ← calfr∆gr2rd DR
CM ← NR < N ∘.+ 0 0
M ← ⌈/MR×CM
D ← N - ⌈/NR×CM
R ← (Y ∘.× 1 0 0) + (M ∘.× 0 1 0) + D ∘.× 0 0 1
∇

∇ R ← calfr∆gr2fr D
R ← calfr∆rd2fr calfr∆gr2rd D
∇

∇ R ← calfr∆fr2gr D
R ← calfr∆rd2gr calfr∆fr2rd D
∇

∇ R ← calfr∆prtfr D; DAY; MONTH; CM; ⎕IO
⎕IO ← 1
DAY ← 10 8 ⍴ 'Décadi  Primidi Duodi   Tridi   QuartidiQuintidiSextidi Septidi Octidi  Nonidi  '
R ← DAY[1 + 10 | calfr∆day D;]
MONTH ← 13 11 ⍴ 'VendémiaireBrumaire   Frimaire   Nivôse     Pluviôse   Ventôse    Germinal   Floréal    Prairial   Messidor   Thermidor  Fructidor  jour compl.'
R ← R, ' ', (⍕ calfr∆day D), ' ', (MONTH[calfr∆month D;]), ' ', (calfr∆roman calfr∆year D), ', jour ', calfr∆feasts[¯30 + D +.× 0 30 1;]
CM ← ' ' ≠ R
R ← (CM ∨ 0,¯1↓CM) / R
R ← (⌽∨\⌽R≠' ')/R
∇

∇ R ← calfr∆roman N; NODES
→ ((N>0) ∧ N<4000)/CONV
R ← ⍕ N
→ 0
CONV:
NODES ← 40 4 ⍴ (calfr∆nodes 'IVX'), (calfr∆nodes 'XLC'), (calfr∆nodes 'CDM'), calfr∆nodes 'M??'
R ← ,NODES[⎕IO + 30 20 10 0 + 10 10 10 10 ⊤ N;]
R ← (R≠' ')/R
∇

∇ R ← calfr∆nodes CH
R ← (' ', CH) [ ' IVX' ⍳ '    I   II  III IV  V   VI  VII VIIIIX  ' ]
∇

∇ R ← calfr∆feasts; V
⍝ include here the contents of feasts
V ← ''
⍝ Vendémiaire
V ← V, 'du Raisin            '
V ← V, 'du Safran            '
V ← V, 'de la Châtaigne      '
V ← V, 'de la Colchique      '
V ← V, 'du Cheval            '
V ← V, 'de la Balsamine      '
V ← V, 'de la Carotte        '
V ← V, 'de l''Amarante        '
V ← V, 'du Panais            '
V ← V, 'de la Cuve           '
V ← V, 'de la Pomme de terre '
V ← V, 'de l''Immortelle      '
V ← V, 'du Potiron           '
V ← V, 'du Réséda            '
V ← V, 'de l''Âne             '
V ← V, 'de la Belle de nuit  '
V ← V, 'de la Citrouille     '
V ← V, 'du Sarrasin          '
V ← V, 'du Tournesol         '
V ← V, 'du Pressoir          '
V ← V, 'du Chanvre           '
V ← V, 'de la Pêche          '
V ← V, 'du Navet             '
V ← V, 'de l''Amaryllis       '
V ← V, 'du Bœuf              '
V ← V, 'de l''Aubergine       '
V ← V, 'du Piment            '
V ← V, 'de la Tomate         '
V ← V, 'de l''Orge            '
V ← V, 'du Tonneau           '
⍝ Brumaire
V ← V, 'de la Pomme          '
V ← V, 'du Céleri            '
V ← V, 'de la Poire          '
V ← V, 'de la Betterave      '
V ← V, 'de l''Oie             '
V ← V, 'de l''Héliotrope      '
V ← V, 'de la Figue          '
V ← V, 'de la Scorsonère     '
V ← V, 'de l''Alisier         '
V ← V, 'de la Charrue        '
V ← V, 'du Salsifis          '
V ← V, 'de la Macre          '
V ← V, 'du Topinambour       '
V ← V, 'de l''Endive          '
V ← V, 'du Dindon            '
V ← V, 'du Chervis           '
V ← V, 'du Cresson           '
V ← V, 'de la Dentelaire     '
V ← V, 'de la Grenade        '
V ← V, 'de la Herse          '
V ← V, 'de la Bacchante      '
V ← V, 'de l''Azerole         '
V ← V, 'de la Garance        '
V ← V, 'de l''Orange          '
V ← V, 'du Faisan            '
V ← V, 'de la Pistache       '
V ← V, 'du Macjon            '
V ← V, 'du Coing             '
V ← V, 'du Cormier           '
V ← V, 'du Rouleau           '
⍝ Frimaire
V ← V, 'de la Raiponce       '
V ← V, 'du Turneps           '
V ← V, 'de la Chicorée       '
V ← V, 'de la Nèfle          '
V ← V, 'du Cochon            '
V ← V, 'de la Mâche          '
V ← V, 'du Chou-fleur        '
V ← V, 'du Miel              '
V ← V, 'du Genièvre          '
V ← V, 'de la Pioche         '
V ← V, 'de la Cire           '
V ← V, 'du Raifort           '
V ← V, 'du Cèdre             '
V ← V, 'du Sapin             '
V ← V, 'du Chevreuil         '
V ← V, 'de l''Ajonc           '
V ← V, 'du Cyprès            '
V ← V, 'du Lierre            '
V ← V, 'de la Sabine         '
V ← V, 'du Hoyau             '
V ← V, 'de l''Érable-sucre    '
V ← V, 'de la Bruyère        '
V ← V, 'du Roseau            '
V ← V, 'de l''Oseille         '
V ← V, 'du Grillon           '
V ← V, 'du Pignon            '
V ← V, 'du Liège             '
V ← V, 'de la Truffe         '
V ← V, 'de l''Olive           '
V ← V, 'de la Pelle          '
⍝ Nivôse
V ← V, 'de la Tourbe         '
V ← V, 'de la Houille        '
V ← V, 'du Bitume            '
V ← V, 'du Soufre            '
V ← V, 'du Chien             '
V ← V, 'de la Lave           '
V ← V, 'de la Terre végétale '
V ← V, 'du Fumier            '
V ← V, 'du Salpêtre          '
V ← V, 'du Fléau             '
V ← V, 'du Granit            '
V ← V, 'de l''Argile          '
V ← V, 'de l''Ardoise         '
V ← V, 'du Grès              '
V ← V, 'du Lapin             '
V ← V, 'du Silex             '
V ← V, 'de la Marne          '
V ← V, 'de la Pierre à chaux '
V ← V, 'du Marbre            '
V ← V, 'du Van               '
V ← V, 'de la Pierre à plâtre'
V ← V, 'du Sel               '
V ← V, 'du Fer               '
V ← V, 'du Cuivre            '
V ← V, 'du Chat              '
V ← V, 'de l''Étain           '
V ← V, 'du Plomb             '
V ← V, 'du Zinc              '
V ← V, 'du Mercure           '
V ← V, 'du Crible            '
⍝ Pluviôse
V ← V, 'de la Lauréole       '
V ← V, 'de la Mousse         '
V ← V, 'du Fragon            '
V ← V, 'du Perce-neige       '
V ← V, 'du Taureau           '
V ← V, 'du Laurier-thym      '
V ← V, 'de l''Amadouvier      '
V ← V, 'du Mézéréon          '
V ← V, 'du Peuplier          '
V ← V, 'de la Cognée         '
V ← V, 'de l''Ellébore        '
V ← V, 'du Brocoli           '
V ← V, 'du Laurier           '
V ← V, 'de l''Avelinier       '
V ← V, 'de la Vache          '
V ← V, 'du Buis              '
V ← V, 'du Lichen            '
V ← V, 'de l''If              '
V ← V, 'de la Pulmonaire     '
V ← V, 'de la Serpette       '
V ← V, 'du Thlaspi           '
V ← V, 'du Thymelé           '
V ← V, 'du Chiendent         '
V ← V, 'de la Traînasse      '
V ← V, 'du Lièvre            '
V ← V, 'de la Guède          '
V ← V, 'du Noisetier         '
V ← V, 'du Cyclamen          '
V ← V, 'de la Chélidoine     '
V ← V, 'du Traîneau          '
⍝ Ventôse
V ← V, 'du Tussilage         '
V ← V, 'du Cornouiller       '
V ← V, 'du Violier           '
V ← V, 'du Troène            '
V ← V, 'du Bouc              '
V ← V, 'de l''Asaret          '
V ← V, 'de l''Alaterne        '
V ← V, 'de la Violette       '
V ← V, 'du Marsault          '
V ← V, 'de la Bêche          '
V ← V, 'du Narcisse          '
V ← V, 'de l''Orme            '
V ← V, 'de la Fumeterre      '
V ← V, 'du Vélar             '
V ← V, 'de la Chèvre         '
V ← V, 'de l''Épinard         '
V ← V, 'du Doronic           '
V ← V, 'du Mouron            '
V ← V, 'du Cerfeuil          '
V ← V, 'du Cordeau           '
V ← V, 'de la Mandragore     '
V ← V, 'du Persil            '
V ← V, 'du Cochléaria        '
V ← V, 'de la Pâquerette     '
V ← V, 'du Thon              '
V ← V, 'du Pissenlit         '
V ← V, 'de la Sylvie         '
V ← V, 'du Capillaire        '
V ← V, 'du Frêne             '
V ← V, 'du Plantoir          '
⍝ Germinal
V ← V, 'de la Primevère      '
V ← V, 'du Platane           '
V ← V, 'de l''Asperge         '
V ← V, 'de la Tulipe         '
V ← V, 'de la Poule          '
V ← V, 'de la Blette         '
V ← V, 'du Bouleau           '
V ← V, 'de la Jonquille      '
V ← V, 'de l''Aulne           '
V ← V, 'du Couvoir           '
V ← V, 'de la Pervenche      '
V ← V, 'du Charme            '
V ← V, 'de la Morille        '
V ← V, 'du Hêtre             '
V ← V, 'de l''Abeille         '
V ← V, 'de la Laitue         '
V ← V, 'du Mélèze            '
V ← V, 'de la Ciguë          '
V ← V, 'du Radis             '
V ← V, 'de la Ruche          '
V ← V, 'du Gainier           '
V ← V, 'de la Romaine        '
V ← V, 'du Marronnier        '
V ← V, 'de la Roquette       '
V ← V, 'du Pigeon            '
V ← V, 'du Lilas             '
V ← V, 'de l''Anémone         '
V ← V, 'de la Pensée         '
V ← V, 'de la Myrtille       '
V ← V, 'du Greffoir          '
⍝ Floréal
V ← V, 'de la Rose           '
V ← V, 'du Chêne             '
V ← V, 'de la Fougère        '
V ← V, 'de l''Aubépine        '
V ← V, 'du Rossignol         '
V ← V, 'de l''Ancolie         '
V ← V, 'du Muguet            '
V ← V, 'du Champignon        '
V ← V, 'de la Jacinthe       '
V ← V, 'du Rateau            '
V ← V, 'de la Rhubarbe       '
V ← V, 'du Sainfoin          '
V ← V, 'du Bâton-d''or        '
V ← V, 'du Chamérisier       '
V ← V, 'du Ver à soie        '
V ← V, 'de la Consoude       '
V ← V, 'de la Pimprenelle    '
V ← V, 'de la Corbeille-d''or '
V ← V, 'de l''Arroche         '
V ← V, 'du Sarcloir          '
V ← V, 'du Statice           '
V ← V, 'de la Fritillaire    '
V ← V, 'de la Bourrache      '
V ← V, 'de la Valériane      '
V ← V, 'de la Carpe          '
V ← V, 'du Fusain            '
V ← V, 'de la Civette        '
V ← V, 'de la Buglosse       '
V ← V, 'du Sénevé            '
V ← V, 'de la Houlette       '
⍝ Prairial
V ← V, 'de la Luzerne        '
V ← V, 'de l''Hémérocalle     '
V ← V, 'du Trèfle            '
V ← V, 'de l''Angélique       '
V ← V, 'du Canard            '
V ← V, 'de la Mélisse        '
V ← V, 'du Fromental         '
V ← V, 'du Martagon          '
V ← V, 'du Serpolet          '
V ← V, 'de la Faux           '
V ← V, 'de la Fraise         '
V ← V, 'de la Bétoine        '
V ← V, 'du Pois              '
V ← V, 'de l''Acacia          '
V ← V, 'de la Caille         '
V ← V, 'de l''Œillet          '
V ← V, 'du Sureau            '
V ← V, 'du Pavot             '
V ← V, 'du Tilleul           '
V ← V, 'de la Fourche        '
V ← V, 'du Barbeau           '
V ← V, 'de la Camomille      '
V ← V, 'du Chèvrefeuille     '
V ← V, 'du Caille-lait       '
V ← V, 'de la Tanche         '
V ← V, 'du Jasmin            '
V ← V, 'de la Verveine       '
V ← V, 'du Thym              '
V ← V, 'de la Pivoine        '
V ← V, 'du Chariot           '
⍝ Messidor
V ← V, 'du Seigle            '
V ← V, 'de l''Avoine          '
V ← V, 'de l''Oignon          '
V ← V, 'de la Véronique      '
V ← V, 'du Mulet             '
V ← V, 'du Romarin           '
V ← V, 'du Concombre         '
V ← V, 'de l''Échalotte       '
V ← V, 'de l''Absinthe        '
V ← V, 'de la Faucille       '
V ← V, 'de la Coriandre      '
V ← V, 'de l''Artichaut       '
V ← V, 'de la Giroflée       '
V ← V, 'de la Lavande        '
V ← V, 'du Chamois           '
V ← V, 'du Tabac             '
V ← V, 'de la Groseille      '
V ← V, 'de la Gesse          '
V ← V, 'de la Cerise         '
V ← V, 'du Parc              '
V ← V, 'de la Menthe         '
V ← V, 'du Cumin             '
V ← V, 'du Haricot           '
V ← V, 'de l''Orcanète        '
V ← V, 'de la Pintade        '
V ← V, 'de la Sauge          '
V ← V, 'de l''Ail             '
V ← V, 'de la Vesce          '
V ← V, 'du Blé               '
V ← V, 'de la Chalémie       '
⍝ Thermidor
V ← V, 'de l''Épautre         '
V ← V, 'du Bouillon-blanc    '
V ← V, 'du Melon             '
V ← V, 'de l''Ivraie          '
V ← V, 'du Bélier            '
V ← V, 'de la Prèle          '
V ← V, 'de l''Armoise         '
V ← V, 'du Carthame          '
V ← V, 'de la Mûre           '
V ← V, 'de l''Arrosoir        '
V ← V, 'du Panis             '
V ← V, 'du Salicor           '
V ← V, 'de l''Abricot         '
V ← V, 'du Basilic           '
V ← V, 'de la Brebis         '
V ← V, 'de la Guimauve       '
V ← V, 'du Lin               '
V ← V, 'de l''Amande          '
V ← V, 'de la Gentiane       '
V ← V, 'de l''Écluse          '
V ← V, 'de la Carline        '
V ← V, 'du Câprier           '
V ← V, 'de la Lentille       '
V ← V, 'de l''Aunée           '
V ← V, 'de la Loutre         '
V ← V, 'de la Myrte          '
V ← V, 'du Colza             '
V ← V, 'du Lupin             '
V ← V, 'du Coton             '
V ← V, 'du Moulin            '
⍝ Fructidor
V ← V, 'de la Prune          '
V ← V, 'du Millet            '
V ← V, 'du Lycoperdon        '
V ← V, 'de l''Escourgeon      '
V ← V, 'du Saumon            '
V ← V, 'de la Tubéreuse      '
V ← V, 'du Sucrion           '
V ← V, 'de l''Apocyn          '
V ← V, 'de la Réglisse       '
V ← V, 'de l''Échelle         '
V ← V, 'de la Pastèque       '
V ← V, 'du Fenouil           '
V ← V, 'de l''Épine-vinette   '
V ← V, 'de la Noix           '
V ← V, 'de la Truite         '
V ← V, 'du Citron            '
V ← V, 'de la Cardère        '
V ← V, 'du Nerprun           '
V ← V, 'du Tagette           '
V ← V, 'de la Hotte          '
V ← V, 'de l''Églantier       '
V ← V, 'de la Noisette       '
V ← V, 'du Houblon           '
V ← V, 'du Sorgho            '
V ← V, 'de l''Écrevisse       '
V ← V, 'de la Bagarade       '
V ← V, 'de la Verge-d''or     '
V ← V, 'du Maïs              '
V ← V, 'du Marron            '
V ← V, 'du Panier            '
⍝ jour complémentaire
V ← V, 'de la Vertu          '
V ← V, 'du Génie             '
V ← V, 'du Travail           '
V ← V, 'de l''Opinion         '
V ← V, 'des Récompenses      '
V ← V, 'de la Révolution     '
R ← 366 21 ⍴ V
∇

∇ R ← calfr∆testdata; V; L
⍝ include here the contents of testapl
V ← ⍳0
V ← V, 1792  9 22    1  1  1  654415
V ← V, 1793 10 23    2  2  2  654811
V ← V, 1794  7 27    2 11  9  655088
V ← V, 1794 11 23    3  3  3  655207
V ← V, 1795 10  5    4  1 13  655523
V ← V, 1795 12 25    4  4  4  655604
V ← V, 1797  1 24    5  5  5  656000
V ← V, 1798  2 24    6  6  6  656396
V ← V, 1799 11  9    8  2 18  657019
V ← V, 1801  3 29    9  7  8  657524
V ← V, 1804  4 30   12  8 10  658652
V ← V, 1807  6  1   15  9 12  659779
V ← V, 1810  7  3   18 10 14  660907
V ← V, 1813  8  4   21 11 16  662035
V ← V, 1816  9  4   24 12 18  663162
V ← V, 2000  1  1  208  4 12  730120
V ← V, 2001  5 11  209  8 22  730616
V ← V, 1792  9 22    1  1  1  654415
V ← V, 1793  9 21    1 13  5  654779
V ← V, 1793  9 22    2  1  1  654780
V ← V, 1794  9 21    2 13  5  655144
V ← V, 1794  9 22    3  1  1  655145
V ← V, 1795  9 22    3 13  6  655510
V ← V, 1795  9 23    4  1  1  655511
V ← V, 1796  9 21    4 13  5  655875
V ← V, 1796  9 22    5  1  1  655876
V ← V, 1797  9 21    5 13  5  656240
V ← V, 1797  9 22    6  1  1  656241
V ← V, 1799  9 22    7 13  6  656971
V ← V, 1799  9 23    8  1  1  656972
V ← V, 1800  9 22    8 13  5  657336
V ← V, 1800  9 23    9  1  1  657337
V ← V, 1801  9 22    9 13  5  657701
V ← V, 1801  9 23   10  1  1  657702
V ← V, 1823  9 22   31 13  5  665736
V ← V, 1823  9 23   32  1  1  665737
V ← V, 1824  9 22   32 13  6  666102
V ← V, 1824  9 23   33  1  1  666103
V ← V, 1825  9 22   33 13  5  666467
V ← V, 1825  9 23   34  1  1  666468
V ← V, 1892  9 21  100 13  5  690938
V ← V, 1892  9 22  101  1  1  690939
V ← V, 1900  9 22  108 13  6  693860
V ← V, 1900  9 23  109  1  1  693861
V ← V, 1992  9 21  200 13  5  727462
V ← V, 1992  9 22  201  1  1  727463
V ← V, 2000  9 21  208 13  6  730384
V ← V, 2000  9 22  209  1  1  730385
V ← V, 2092  9 20  300 13  5  763986
V ← V, 2092  9 21  301  1  1  763987
V ← V, 2100  9 21  308 13  6  766908
V ← V, 2100  9 22  309  1  1  766909
V ← V, 2192  9 21  400 13  6  800511
V ← V, 2192  9 22  401  1  1  800512
V ← V, 2193  9 21  401 13  5  800876
V ← V, 2199  9 22  408  1  1  803068
V ← V, 2200  9 22  408 13  6  803433
V ← V, 2791  9 23 1000  1  1 1019292
V ← V, 2792  9 22 1001  1  1 1019657
V ← V, 3000  1  1 1208  4 12 1095363
V ← V, 3001  1  1 1209  4 11 1095728
V ← V, 3791  9 22 2000  1  1 1384534
V ← V, 3792  9 22 2001  1  1 1384900
V ← V, 4000  1  1 2208  4 12 1460605
V ← V, 4001  1  1 2209  4 12 1460971
V ← V, 4320  9 10 2528 12 24 1577735
V ← V, 4320  9 11 2528 12 25 1577736
V ← V, 4791  9 23 3000  1  1 1749777
V ← V, 4792  9 22 3001  1  1 1750142
V ← V, 5000  1  1 3208  4 12 1825848
V ← V, 5001  1  1 3209  4 11 1826213
V ← V, 5791  9 22 4000  1  1 2115019
V ← V, 5792  9 21 4001  1  1 2115384
V ← V, 6000  1  1 4208  4 13 2191090
V ← V, 6001  1  1 4209  4 13 2191456
V ← V, 6791  9 22 5000  1  1 2480261
V ← V, 6792  9 21 5001  1  1 2480626
V ← V, 7791  9 21 6000  1  1 2845503
V ← V, 7792  9 21 6001  1  1 2845869
L ← (⍴ V) ÷ 7
R ← (L, 7) ⍴ V
∇

∇ R ← calfr∆teststring; V; L
V ← ''
⍝ include here the contents of testapl1
V ← V, 'Primidi 1 Vendémiaire I, jour du Raisin             '
V ← V, 'Duodi 2 Brumaire II, jour du Céleri                 '
V ← V, 'Nonidi 9 Thermidor II, jour de la Mûre              '
V ← V, 'Tridi 3 Frimaire III, jour de la Chicorée           '
V ← V, 'Tridi 13 Vendémiaire IV, jour du Potiron            '
V ← V, 'Quartidi 4 Nivôse IV, jour du Soufre                '
V ← V, 'Quintidi 5 Pluviôse V, jour du Taureau              '
V ← V, 'Sextidi 6 Ventôse VI, jour de l''Asaret              '
V ← V, 'Octidi 18 Brumaire VIII, jour de la Dentelaire      '
V ← V, 'Octidi 8 Germinal IX, jour de la Jonquille          '
V ← V, 'Décadi 10 Floréal XII, jour du Rateau               '
V ← V, 'Duodi 12 Prairial XV, jour de la Bétoine            '
V ← V, 'Quartidi 14 Messidor XVIII, jour de la Lavande      '
V ← V, 'Sextidi 16 Thermidor XXI, jour de la Guimauve       '
V ← V, 'Octidi 18 Fructidor XXIV, jour du Nerprun           '
V ← V, 'Duodi 12 Nivôse CCVIII, jour de l''Argile            '
V ← V, 'Duodi 22 Floréal CCIX, jour de la Fritillaire       '
V ← V, 'Primidi 1 Vendémiaire I, jour du Raisin             '
V ← V, 'Quintidi 5 jour compl. I, jour des Récompenses      '
V ← V, 'Primidi 1 Vendémiaire II, jour du Raisin            '
V ← V, 'Quintidi 5 jour compl. II, jour des Récompenses     '
V ← V, 'Primidi 1 Vendémiaire III, jour du Raisin           '
V ← V, 'Sextidi 6 jour compl. III, jour de la Révolution    '
V ← V, 'Primidi 1 Vendémiaire IV, jour du Raisin            '
V ← V, 'Quintidi 5 jour compl. IV, jour des Récompenses     '
V ← V, 'Primidi 1 Vendémiaire V, jour du Raisin             '
V ← V, 'Quintidi 5 jour compl. V, jour des Récompenses      '
V ← V, 'Primidi 1 Vendémiaire VI, jour du Raisin            '
V ← V, 'Sextidi 6 jour compl. VII, jour de la Révolution    '
V ← V, 'Primidi 1 Vendémiaire VIII, jour du Raisin          '
V ← V, 'Quintidi 5 jour compl. VIII, jour des Récompenses   '
V ← V, 'Primidi 1 Vendémiaire IX, jour du Raisin            '
V ← V, 'Quintidi 5 jour compl. IX, jour des Récompenses     '
V ← V, 'Primidi 1 Vendémiaire X, jour du Raisin             '
V ← V, 'Quintidi 5 jour compl. XXXI, jour des Récompenses   '
V ← V, 'Primidi 1 Vendémiaire XXXII, jour du Raisin         '
V ← V, 'Sextidi 6 jour compl. XXXII, jour de la Révolution  '
V ← V, 'Primidi 1 Vendémiaire XXXIII, jour du Raisin        '
V ← V, 'Quintidi 5 jour compl. XXXIII, jour des Récompenses '
V ← V, 'Primidi 1 Vendémiaire XXXIV, jour du Raisin         '
V ← V, 'Quintidi 5 jour compl. C, jour des Récompenses      '
V ← V, 'Primidi 1 Vendémiaire CI, jour du Raisin            '
V ← V, 'Sextidi 6 jour compl. CVIII, jour de la Révolution  '
V ← V, 'Primidi 1 Vendémiaire CIX, jour du Raisin           '
V ← V, 'Quintidi 5 jour compl. CC, jour des Récompenses     '
V ← V, 'Primidi 1 Vendémiaire CCI, jour du Raisin           '
V ← V, 'Sextidi 6 jour compl. CCVIII, jour de la Révolution '
V ← V, 'Primidi 1 Vendémiaire CCIX, jour du Raisin          '
V ← V, 'Quintidi 5 jour compl. CCC, jour des Récompenses    '
V ← V, 'Primidi 1 Vendémiaire CCCI, jour du Raisin          '
V ← V, 'Sextidi 6 jour compl. CCCVIII, jour de la Révolution'
V ← V, 'Primidi 1 Vendémiaire CCCIX, jour du Raisin         '
V ← V, 'Sextidi 6 jour compl. CD, jour de la Révolution     '
V ← V, 'Primidi 1 Vendémiaire CDI, jour du Raisin           '
V ← V, 'Quintidi 5 jour compl. CDI, jour des Récompenses    '
V ← V, 'Primidi 1 Vendémiaire CDVIII, jour du Raisin        '
V ← V, 'Sextidi 6 jour compl. CDVIII, jour de la Révolution '
V ← V, 'Primidi 1 Vendémiaire M, jour du Raisin             '
V ← V, 'Primidi 1 Vendémiaire MI, jour du Raisin            '
V ← V, 'Duodi 12 Nivôse MCCVIII, jour de l''Argile           '
V ← V, 'Primidi 11 Nivôse MCCIX, jour du Granit             '
V ← V, 'Primidi 1 Vendémiaire MM, jour du Raisin            '
V ← V, 'Primidi 1 Vendémiaire MMI, jour du Raisin           '
V ← V, 'Duodi 12 Nivôse MMCCVIII, jour de l''Argile          '
V ← V, 'Duodi 12 Nivôse MMCCIX, jour de l''Argile            '
V ← V, 'Quartidi 24 Fructidor MMDXXVIII, jour du Sorgho     '
V ← V, 'Quintidi 25 Fructidor MMDXXVIII, jour de l''Écrevisse'
V ← V, 'Primidi 1 Vendémiaire MMM, jour du Raisin           '
V ← V, 'Primidi 1 Vendémiaire MMMI, jour du Raisin          '
V ← V, 'Duodi 12 Nivôse MMMCCVIII, jour de l''Argile         '
V ← V, 'Primidi 11 Nivôse MMMCCIX, jour du Granit           '
V ← V, 'Primidi 1 Vendémiaire 4000, jour du Raisin          '
V ← V, 'Primidi 1 Vendémiaire 4001, jour du Raisin          '
V ← V, 'Tridi 13 Nivôse 4208, jour de l''Ardoise             '
V ← V, 'Tridi 13 Nivôse 4209, jour de l''Ardoise             '
V ← V, 'Primidi 1 Vendémiaire 5000, jour du Raisin          '
V ← V, 'Primidi 1 Vendémiaire 5001, jour du Raisin          '
V ← V, 'Primidi 1 Vendémiaire 6000, jour du Raisin          '
V ← V, 'Primidi 1 Vendémiaire 6001, jour du Raisin          '
R ← 79 52 ⍴ V
∇

∇ calfr∆alltests
calfr∆testfr2rd
calfr∆testgr2rd
calfr∆testrd2fr
calfr∆testrd2gr
calfr∆testgr2fr
calfr∆testfr2gr
calfr∆testprtfr
∇

∇ calfr∆testfr2rd; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;7]
PARAM ← calfr∆testdata[;4 5 6]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0fr2rd 1
OK ←OK ∧ calfr∆test0fr2rd 2
OK ←OK ∧ calfr∆test1fr2rd 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1fr2rd 5 5
OK ←OK ∧ calfr∆test1fr2rd 5 3 3
OK ←OK ∧ calfr∆test1fr2rd 5 2 2 2
OK ←OK ∧ calfr∆test1fr2rd 7 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆fr2rd : no errors'
∇

∇ R  ← calfr∆test0fr2rd N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆fr2rd with scalar date ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆fr2rd PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDATA ⍝ no need to check dimension, it is the empty vector
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ R  ← calfr∆test1fr2rd DIM; LIB; EXP; GOT; NERR
LIB  ← 'Checking calfr∆fr2rd with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← DIM ⍴ EXPEC
⎕IO ← IO
GOT  ← calfr∆fr2rd (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ calfr∆testgr2rd; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;7]
PARAM ← calfr∆testdata[;1 2 3]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0gr2rd 1
OK ←OK ∧ calfr∆test0gr2rd 2
OK ←OK ∧ calfr∆test1gr2rd 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1gr2rd 5 5
OK ←OK ∧ calfr∆test1gr2rd 5 3 3
OK ←OK ∧ calfr∆test1gr2rd 5 2 2 2
OK ←OK ∧ calfr∆test1gr2rd 7 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆gr2rd : no errors'
∇

∇ R  ← calfr∆test0gr2rd N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆gr2rd with scalar date ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆gr2rd PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDATA ⍝ no need to check dimension, it is the empty vector
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ R  ← calfr∆test1gr2rd DIM; LIB; EXP; GOT; NERR
LIB  ← 'Checking calfr∆gr2rd with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← DIM ⍴ EXPEC
GOT ← calfr∆gr2rd (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ calfr∆testrd2fr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;4 5 6]
PARAM ← calfr∆testdata[;7]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0rd2fr 1
OK ←OK ∧ calfr∆test0rd2fr 2
OK ←OK ∧ calfr∆test1rd2fr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1rd2fr 3 3
OK ←OK ∧ calfr∆test1rd2fr 15 3
OK ←OK ∧ calfr∆test1rd2fr 2 3 3
OK ←OK ∧ calfr∆test1rd2fr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆rd2fr : no errors'
∇

∇ R  ← calfr∆test0rd2fr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N]
LIB  ← 'Checking calfr∆rd2fr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆rd2fr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ R ← calfr∆test1rd2fr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆rd2fr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆rd2fr DIM ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ calfr∆testrd2gr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;1 2 3]
PARAM ← calfr∆testdata[;7]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0rd2gr 1
OK ←OK ∧ calfr∆test0rd2gr 2
OK ←OK ∧ calfr∆test1rd2gr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1rd2gr 3 3
OK ←OK ∧ calfr∆test1rd2gr 15 3
OK ←OK ∧ calfr∆test1rd2gr 2 3 3
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆rd2gr : no errors'
∇

∇ R  ← calfr∆test0rd2gr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N]
LIB  ← 'Checking calfr∆rd2gr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆rd2gr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ R ← calfr∆test1rd2gr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆rd2gr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆rd2gr DIM ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ calfr∆testgr2fr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;4 5 6]
PARAM ← calfr∆testdata[;1 2 3]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0gr2fr 1
OK ←OK ∧ calfr∆test0gr2fr 2
OK ←OK ∧ calfr∆test1gr2fr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1gr2fr 3 3
OK ←OK ∧ calfr∆test1gr2fr 15 3
OK ←OK ∧ calfr∆test1gr2fr 2 3 3
OK ←OK ∧ calfr∆test1gr2fr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆gr2fr : no errors'
∇

∇ R  ← calfr∆test0gr2fr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆gr2fr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆gr2fr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ R ← calfr∆test1gr2fr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆gr2fr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆gr2fr (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ calfr∆testfr2gr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;1 2 3]
PARAM ← calfr∆testdata[;4 5 6]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0fr2gr 1
OK ←OK ∧ calfr∆test0fr2gr 2
OK ←OK ∧ calfr∆test1fr2gr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1fr2gr 3 3
OK ←OK ∧ calfr∆test1fr2gr 15 3
OK ←OK ∧ calfr∆test1fr2gr 2 3 3
OK ←OK ∧ calfr∆test1fr2gr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆fr2gr : no errors'
∇

∇ R  ← calfr∆test0fr2gr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆fr2gr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆fr2gr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ R ← calfr∆test1fr2gr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking fr2gr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆fr2gr (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

∇ calfr∆testprtfr; TD; TS; I; IMAX; PAR; EXP; GOT; N; IO; ⎕IO
IO ← 2
LOOPIO:
IO ← IO - 1
'Checking calfr∆prtfr with the full vector (a bit slow) and ⎕IO ', ⍕IO
⎕IO ← 1
TD ← calfr∆testdata[; 4 5 6 ]
TS ← calfr∆teststring
I ← 0
N ← 0
IMAX ← (⍴ TS)[1]
LOOP:
⎕IO ← 1
I ← I + 1
→ (I > IMAX)/END
PAR ← TD[I;]
EXP ← TS[I;]
EXP ← (⌽∨\⌽EXP≠' ')/EXP
⎕IO ← IO
GOT ← calfr∆prtfr PAR
→ ((⍴ EXP)  = ⍴ GOT)/NEXT
'Different length: ', (⍕⍴ EXP), ' ', ⍕⍴GOT
'Expected: ', EXP
'Got     : ', GOT
N ← N + 1
→ LOOP
NEXT:
→ (∧/EXP=GOT)/LOOP
'Different content'
'Expected: ', EXP
'Got     : ', GOT
N ← N + 1
→ LOOP
END:
'Data errors with ⎕IO = ', (⍕IO), ' : ', ⍕N
→ IO / LOOPIO 
∇

]NEXTFILE

English

French Revolutionary Calendar
calfr∆license
Conversion between calendars
Instructions for Use
Installation
Loading
Basic use
calfr∆usage
Higher Orders
Enclosed Values
Implementation
Date Elements
calfr∆year
calfr∆month
calfr∆day
calfr∆zerojanvnd - Concept of "zeroth day"
calfr∆fr2rd - Converting from the French Revolutionary calendar to Rata Die
calfr∆frbis - Number of leap years before year N (French Revolutionary calendar)
calfr∆gr2rd - Converting from the Gregorian calendar to Rata Die
calfr∆rd2fr - Converting from Rata Die to the French Revolutionary calendar
calfr∆rd2gr - Converting from Rata Die to the Gregorian calendar
The Conversion Functions
calfr∆gr2fr - Conversion from the Gregorian calendar to the French Revolutionary calendar
calfr∆fr2gr - Conversion from the French Revolutionary calendar to the Gregorian calendar
Displaying the Dates
calfr∆prtfr - Displaying a French Revolutionary calendar date
calfr∆roman - Converting a number into Roman numerals
calfr∆nodes - Building the nodes of Roman numerals
calfr∆feasts - Feasts of the French Revolutionary calendar
Built-in Tests
calfr∆testdata - Test Data
calfr∆teststring - Test alphanumeric data
calfr∆alltests - Run all the tests
calfr∆testfr2rd
calfr∆test0fr2rd
calfr∆test1fr2rd
calfr∆testgr2rd
calfr∆test0gr2rd
calfr∆test1gr2rd
calfr∆testrd2fr
calfr∆test0rd2fr
calfr∆test1rd2fr
calfr∆testrd2gr
calfr∆test0rd2gr
calfr∆test1rd2gr
calfr∆testgr2fr
calfr∆test0gr2fr
calfr∆test1gr2fr
calfr∆testfr2gr
calfr∆test0fr2gr
calfr∆test1fr2gr
calfr∆testprtfr
See Also
Software
Internet
Books

French Revolutionary Calendar

calfr∆license

The text part of this repository is licensed under the terms of Creative Commons, with attribution and share-alike (CC-BY-SA). The code part of this repository is licensed with the GPL version 1.0 or later.

As required by the GPL, each file with code must start with a one-line description of the program and the summary of the GPL. Here it is.

∇ calfr∆license
'APL program to convert Gregorian dates'
'to French Revolutionary dates and the other way'
''
'Copyright (C) 2015, 2016 Jean Forget  (JFORGET at cpan dot org)'
''
'Build date:'
''
'Portability: L1 (tested with GNU-APL and NARS2000)'
''
' This program is distributed under the GNU Public License version 1 or later'
''
' You can find the text of the license in the LICENSE file or at'
' http://www.gnu.org/licenses/gpl-1.0.html.'
''
' Here is the summary of GPL:'
''
' This program is free software; you can redistribute it and/or modify'
' it under the terms of the GNU General Public License as published by'
' the Free Software Foundation; either version 1, or (at your option)'
' any later version.'
''
' This program is distributed in the hope that it will be useful,'
' but WITHOUT ANY WARRANTY; without even the implied warranty of'
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the'
' GNU General Public License for more details.'
''
' You should have received a copy of the GNU General Public License'
' along with this program; if not, write to the Free Software Foundation,'
' Inc., <http://www.fsf.org/>.'
∇

As you can see, this summary is executable code included in the software. So, after you have logged into APL and initialised your workspace with my script, you can display this summary at any moment.

Portability: by converting the script file to ATF, the programs can be loaded into NARS2000. The only glitch is that any vowel with diacritic or any "c" with cedilla triggers a syntax error. So the build tool convert these characters to plain vowels and plain "c". After that, the ATF file gives programs that function correctly under NARS2000.

Conversion between calendars

This group of functions applies to three different calendars.

The French Revolutionary calendar
This calendar was in effect only in France and only from 1792 to 1805, but the programs allow you to use dates past this limit, until today and beyond. The dates are entered as vectors of 3 integer numbers YYYY MM DD. This calendar contains 12 months of 30 days each, plus 5 or 6 additional days. For convenience, these additional days are grouped together in what is considered as an additional month, numbered 13.
The proleptic Gregorian calendar
This is the Gregorian calendar, but extending in the past farther than 1582. Not only the leap year rule is the same as the Gregorian rule, but also the beginning of the year is always on January 1st, although it was not always the case during the Middle Ages with the actual calendar. The Gregorian dates are implemented as vectors of 3 numbers, in YYYY MM DD order.
The Rata Die calendar
This calendar ignores completely the notion of year. It counts only the days since January 1st, year 1, in the proleptic Gregorian calendar. Very convenient for computers, much less for humans. Actually, the script just uses the Rata Die calendar as a step between the two other calendars. You can still find some use with it, for example to compute how many days lie between two given dates.

Instructions for Use

Installation

The calfr.apl file must be stored in the workspaces sub-directory of the current directory, so you can use the )LOAD APL command. You may store it in a wslibn sub-directory (n from 1 to 9) and in this case, you will use the )LOAD n calfr.apl variant. Likewise, when the present text shows the command )LOAD, you can use the )XLOAD or )COPY variants instead.

Installation for GNU-APL from GitHub: you may clone the repository and copy workspaces/calfr.apl to the workspaces subdirectory of your APL directory. Or else, display https://github.com/.../calfr.apl click on the "raw" button and save the file as "text only" into the workspaces of your APL directory.

Installation for GNU-APL from GNU-APL Bits and Pieces: save the file as "text only" into the workspace subdirectory. Do not bother about all this English and French text, il will be hidden while loading the program into APL.

Installation for NARS2000 from GitHub: Clone the repository and copy workspaces/calfrnars.atf to the c:\user\myself\Application Data\NARS2000\workspaces directory or, if you use GNU/Linux and Wine: /home/myself/.wine/drive_c/users/myself/Application Data/NARS2000/workspaces.

Loading

Loading into GNU-APL: you initialise an pristine workspace with the conversion functions with:

apl -f workspaces/calfr.apl

or with:

apl
    )LOAD calfr

Loading into NARS2000: you initialise an pristine workspace with the conversion functions with:

)IN calfrnars

Basic use

You are now in the REPL loop of APL. You can for example ask to convert 13 Vendémiaire IV in this way:

     calfr∆fr2gr 4 1 13
1795 10 5

which means that 13 Vendémiaire IV is equivalent to 5th October 1795. In the other direction, converting 27th July 1794 is:

     calfr∆gr2fr 1794 7 27
2 11 9

which means 9 Thermidor II. To display the date in a more convenient way, use calfr∆prtfr in addition:

     calfr∆prtfr 2 11 9
Nonidi 9 Thermidor II, jour de la Mûre
     calfr∆prtfr calfr∆gr2fr 1794 7 27
Nonidi 9 Thermidor II, jour de la Mûre

And to get the current day

     calfr∆prtfr calfr∆gr2fr 3↑⎕TS

If you want to compute the number of days between two dates, use the Rata Die calendar. For example, if you want to know how many days elapsed from the uncle's coup (18 Brumaire VIII) and the nephew's coup (2nd December 1851), just type:

     (calfr∆gr2rd 1851 12 2) - calfr∆fr2rd 8 2 18
19015

calfr∆usage

And here is the embedded help, callable from APL.

⍝ Installing from GitHub (https://github.com/jforget/apl-calendar-french):
⍝     clone the Git repo and copy "workspaces/calfr.apl" to the "workspaces" sub-directory of your GNU-APL directory
⍝                         or copy "workspaces/calfrnars.atf" to some place where NARS2000 will find it
⍝ Installing from browsing https://www.gnu.org/software/apl/Bits_and_Pieces/
⍝     save the webpage as simple text into the "workspaces" sub-directory of your GNU-APL directory
∇ calfr∆usage
'Loading the module from the command line (for GNU-APL):'
'      apl -f workspaces/calfr.apl'
'Loading the module from inside GNU-APL:'
'      )LOAD calfr'
'Loading the module from inside NARS2000:'
'      )IN calfrnars'
'Dates are vectors of 3 numbers YYYY MM DD. e.g.'
'      DR ←    8 2 18 ⍝ for 18 Brumaire VIII'
'      DG ← 1794 7 27 ⍝ for 27th July 1794'
'Gregorian to French Revolutionary:'
'      DR ← calfr∆gr2fr DG'
'French Revolutionary to Gregorian:'
'      DG ← calfr∆fr2gr DR'
'Pretty-printing:'
'      STRING ← calfr∆prtfr DR'
'Pretty-printing today''s date:'
'      STRING ← calfr∆prtfr calfr∆gr2fr 3↑⎕TS'
∇

Higher Orders

A date is implemented as a vector of 3 numbers. Several dates can be gathered into a date-vector, which will actually be an array of numbers. Thus, a 5-date vector will be an array with a 5 3 dimension.

For example, the date vector:

21-JAN-17945-OCT-17959-NOV-17992-DEC-185131-MAR-2015

will be implemented by:

     DATEVEC 
1794  1 21
1795 10  5
1799 11  9
1851 12  2
2015  3 31
     ⍴ DATEVEC 
5 3

Likewise, a 4 5 array of dates will be entered as a 4 5 3 block of numbers, and a 2 2 6 block of dates will be entered as a 2 2 6 3 block of numbers and so on. The calfr∆gr2fr and calfr∆fr2gr functions can deal with these structures, except when using a rank nearly equal to the maximum allowed value. For example, in this implementation APL is limited to rank 8, so the calfr∆gr2fr and calfr∆fr2gr functions are limited to rank 6.

Continuing with the example above, converting the date vector is done with:

     calfr∆gr2fr DATEVEC 
  2  5  2
  4  1 13
  8  2 18
 60  3 11
223  7 11

On the other hand, the calfr∆prtfr function is not compatible with date vectors or date arrays. It works only with scalar dates.

Enclosed Values

I know that APL allow to embed objects into others, using the and operators. But when I learnt APL more than thirty years ago, these operators did not exist. So, I have written the present script as a first version which does not use these operations. I had the intention of writing a second version which would rely on et .

But I tried some experiments with the script as is. By using the ¨ (each) operator, the functions become compatible with enclosed objects. For example:

     V ← 2 2 ⍴ (1792 9 22) (1795 10 5) (1799 11 9), ⊂ 3↑⎕TS
     V
1792 9 22  1795 10 5  
1799 11 9  2015 12 24 
     ⍴V
2 2
     V1 ← calfr∆prtfr ¨ calfr∆gr2fr ¨ V
     V1
Primidi 1 Vendémiaire I, jour du Raisin        Tridi 13 Vendémiaire IV, jour du Potiron 
Octidi 18 Brumaire VIII, jour de la Dentelaire Quartidi 4 Nivôse CCXXIV, jour du Soufre 
     ⍴V1
2 2

Implementation

Date Elements

To extract the year from a date DATE, you juste have to write DATE[1]. To extract the day, just write DATE[3]. But this does not work with date vectors, for which you need to write DATEVEC[;1] and DATEVEC[;3], which in turn does not work with date arrays, for which you write DATEARRAY[;;1] and DATEARRAY[;;3]. There are several ways to use the same functions for all. The simplest way is as follows:

calfr∆year

∇ R ← calfr∆year D
R ← D +.× 1 0 0
∇

calfr∆month

∇ R ← calfr∆month D
R ← D +.× 0 1 0
∇

calfr∆day

∇ R ← calfr∆day D
R ← D +.× 0 0 1
∇

calfr∆zerojanvnd - Concept of "zeroth day"

To help the description of the conversion algorithms, it is convenient to use the "zeroth day" of a given month. The zeroth day is a virtual day, before the first day of the month and after the last day of the previous month. For example, 0th March 2015 occurs before 1st March 2015 and after 28th February 2015. The fact that 0th March 2015 and 28th February 2015 share the same Rata Die is irrelevant, 0th March is after 28th February.

Here is a function which builds a 0th January or a 0th Vendémiaire for a given year:

∇ R ← calfr∆zerojanvnd Y
R ← Y ∘.× 1 0 0
R ← R + (⍴R) ⍴ 0 1 0
∇

calfr∆fr2rd - Converting from the French Revolutionary calendar to Rata Die

Let us start by computing the Rata Die for a given date in the French Revolutionary calendar. This number is the result of the addition of:

Actually, we reorder the terms to have a single additive constant, which gives the following terms:

∇ R ← calfr∆fr2rd DATE
R ← 654019 + (calfr∆frbis calfr∆year DATE) + DATE +.× 365 30 1
∇

calfr∆frbis - Number of leap years before year N (French Revolutionary calendar)

This subroutine gives the number of leap years preceding the current year. It allows refining the date computation, initialised with a computation using only 365-day years.

Until year XX, the leap year rule is an astronomical rule, stating that the 1st Vendémiaire should happen on the autumn equinox. Then from year XX on, there would have been an arithmetical rule, similar to the Gregorian Calendar rule, introduced by Gilbert Romme's reform.

The Romme rule states that a year is a leap year if it is a multiple of 4, except if it is a multiple of 100, but is nevertheless leap if it is a multiple of 400, except if it is a multiple of 4000.

See below the comparison between both rules for the beginning of the Republican Era. The two "Arithmetic" columns simulate the hypothesis of applying the arithmetic rule from the beginning of the era. The "Astronomical" columns show the actual case, with the astronomical rule at first and the arithmetic rule after. The columns "Leap?" indicates leap years with an "X". The columns "frbis" show the number of leap years before the current year, which should be the result of the calfr∆frbis function.

Arithmetic Astronomical
Year Leap?frbis Leap?frbis
1   0   0
2   0   0
3   0 x 0
4 x 0   1
5   1   1
6   1   1
7   1 x 1
8 x 1   2
9   2   2
10   2   2
11   2 x 2
12 x 2   3
13   3   3
14   3   3
15   3 x 3
16 x 3   4
17   4   4
18   4   4
19   4   4
20 x 4 x 4
21   5   5
22   5   5
23   5   5
24 x 5 x 5
25   6   6

Actually, the two rules differ only for years IV, VIII, XII and XVI. So the subroutine applies the arithmetical rule starting from year I and then checks the year, tweaking the result is the year is one of the four exceptions. Remember that the subroutine begins with subtracting 1 from the year, therefore the values to check are 3, 7, 11 and 15.

∇ R ← calfr∆frbis YEAR
YEAR ← YEAR - 1
R ← - / ⌊ YEAR ∘.÷ 4 100 400 4000
R ← R + YEAR ∈ 3 7 11 15
∇

calfr∆gr2rd - Converting from the Gregorian calendar to Rata Die

Although the leap year criteria for the French Revolutionary calendar are a bit complicated, converting from this calendar to Rata Die is simple when compared with the Gregorian Calendar. The filler period (additional days) is at the end of the year, so its shorter and variable duration has no impact on the computation. On the other hand, in the Gregorian calendar, the filler period is February, near the beginning of the year, so its influence spans over the main part of the year. In addition, the duration of the other months is variable: 30 or 31 days.

This is why we use yet another calendar, the "shifted Gregorian calendar". In this calendar, the January and February months are assigned to the previous year so the year will begin on the First of March. In addition, for reasons shown below, the months are numbered from 4 (March) to 15 (February).

So, 28th February 2015 becomes 2014 15 28 and 1st March 2015 becomes 2015 4 1.

In the table below, the left side show how to count the number of 31-day months before the current month and since the beginning of the shifted year. And the right side shows how to compute the number of days from 0th March to the zeroth day of the current month.

Mnb31 M0.6 × M¯2 + ⌊ 0.6 × MM ln nb30.6 × M¯122 + ⌊ 30.6 × M
M 0 4 2.4 0 M 31 0 122.4 0
A 1 5 3.0 1 A 30 31 153.0 31
M 1 6 3.6 1 M 31 61 183.6 61
J 2 7 4.2 2 J 30 92 214.2 92
J 2 8 4.8 2 J 31 122 244.8 122
A 3 9 5.4 3 A 31 153 275.4 153
S 4 10 6.0 4 S 30 184 306.0 184
O 4 11 6.6 4 O 31 214 336.6 214
N 5 12 7.2 5 N 30 245 367.2 245
D 5 13 7.8 5 D 31 275 397.8 275
J 6 14 8.4 6 J 31 306 428.4 306
F 7 15 9.0 7 F28? 29?337 459.0 337

So here is the function:

∇ R ← calfr∆gr2rd DATE; DIM
DIM ← ⍴ DATE
DATE←(DIM ⍴0 1 0) + DATE + (2≥calfr∆month DATE) ∘.× ¯1 12 0
R ← ¯428 + (-/ ⌊ (calfr∆year DATE) ∘.÷ 4 100 400) + ⌊DATE +.× 365 30.6 1
∇

calfr∆rd2fr - Converting from Rata Die to the French Revolutionary calendar

Here is how it is done. Starting from the Rata Die value, the function computes a YL-to-YH interval of candidate years. Suppose we start with Rata Die value 672800 and that the candidate years are 50 to 53 inclusive. The program computes the full list of candidate years YR, that is, in this example, the vector 50 51 52 53. Then the program turns this year vector into a date vector DR, consisting of the zeroth Vendémiaire for each year.

    DR
50 1 0
51 1 0
52 1 0
53 1 0  

The calfr∆fr2rd function computes the Rata Die of these dates

    NR
672311 672676 673041 673407

and compares with the initial value 672800, which gives:

    CM
1 1 0 0

The proper year is the highest year for which the zeroth Vendémiaire has a Rata Die value less than the value given as a parameter. In the example above, this would give year 51. By subtracting the Rata Die of the zeroth Vendémiaire from the parameter, we get 124, which can easily yield "4 Pluviôse" (5 4) with simple divisions by 30.

Remark: in the example above, the interval of candidate years has 4 values. Actually, in most cases there will be only one candidate year when the date is far from the year's beginning and end, and there will be two around the years' ends. Using 4 values instead of just 1 or 2 has a pedagogical purpose. Actually, the first date with a 3-value range occurs in year 3744 and the first 4-value range appears in year 7480.

Other remark: when the input parameter is not a scalar, but a vector, array or higher ranked structure, the various variables also have a higher rank. The only question is what happens if a first Rata Die value yields a 1-value range for the candidate years and another one yields a 2-value range. In this case, the shorter range is extended to 2 values, so the YR variable will be properly rectangular.

∇ R ← calfr∆rd2fr N; ⎕IO; N1; YH; YL; YI; YR; DR; NR; CM; Y; M; D
⎕IO ← 1
N1 ← N - 654414
YH ← ⌈ N1 ÷ 365.24
YL ← ⌈ N1 ÷ 365.34
YI ← 0 , ⍳ ⌈/,YH-YL
YR ← YL ∘.+ YI
DR ← calfr∆zerojanvnd YR
NR ← calfr∆fr2rd DR
CM ← NR < N ∘.+ (⍴YI)⍴0
Y ← ⌈/YR×CM
D ← N - ⌈/NR×CM
M ← ⌈D÷30
D ← D - 30 × M - 1
R ← (Y ∘.× 1 0 0) + (M ∘.× 0 1 0) + D ∘.× 0 0 1
∇

Summary of the local variables:

YHyear (high)
YLyear (low)
YIyear increments, to build the year range
YRyear range
DRdate range (0 Vendémiaire)
NRnumber range (Rata Die)
CMcomparison
Y year final value
D day final value
M month final value

Here is an example using a vector, not a scalar, as the input parameter. Note that for the first value, the year range should include one year only, but because of the fourth value, this range was extended to three years.

      V←800505 800511 800879 2115019
      calfr∆rd2fr V
 400 12 30
 400 13  6
 402  1  3
4000  1  1
      YH
400 401 402 4000
      YL
400 400 401 3998
      YI
0 1 2
      YR
 400  401  402
 400  401  402
 401  402  403
3998 3999 4000
      DR
 400 1 0
 401 1 0
 402 1 0

400 1 0 401 1 0 402 1 0

401 1 0 402 1 0 403 1 0

3998 1 0 3999 1 0 4000 1 0 NR 800145 800511 800876 800145 800511 800876 800511 800876 801241 2114288 2114653 2115018 CM 1 0 0 1 0 0 1 1 0 1 1 1 Y 400 400 402 4000 M 12 13 1 1 D 30 6 3 1

calfr∆rd2gr - Converting from Rata Die to the Gregorian calendar

For this function, there are two options. The first consists in converting Rata Die to shifted Gregorian and then converting from shifted Gregorian to standard Gregorian. The second possibility consists in using twice the "possible value range", the first time to compute the year (as in calfr∆rd2fr) and the second time to compute the month. I use this second way.

Computing the upper and lower limits of the candidate month range involves a division by 29.5 and a division by 31. So, the 3-value range will arrive sooner than with the candidate years. It will arrive with a day number 620. Even if the guesstimate of the month is coarser than the estimated value of the year, we know that the computation will never involve more than 366 days or 12 months, so the range will have at most two values. Therefore, we do not bother refining the estimation and we always use a two-value range for the month, even if the guesstimate would give a single-value range.

∇ R ← calfr∆rd2gr N; ⎕IO; YH; YL; YI; YR; DR; NR; CM; Y; M; D; ML; MR
⎕IO ← 1
YH ← ⌈ N ÷ 365.24
YL ← ⌈ N ÷ 365.25
YI ← 0 , ⍳ ⌈/,YH-YL
YR ← YL ∘.+ YI
DR ← calfr∆zerojanvnd YR
NR ← calfr∆gr2rd DR
CM ← NR < N ∘.+ (⍴YI)⍴0
Y ← ⌈/YR×CM
D ← N - ⌈/NR×CM
ML ← ⌈D÷31
MR ← ML ∘.+ 0 1
DR ← (Y ∘.× 2 3 ⍴ 1 0 0) + (MR ∘.× 0 1 0)
NR ← calfr∆gr2rd DR
CM ← NR < N ∘.+ 0 0
M ← ⌈/MR×CM
D ← N - ⌈/NR×CM
R ← (Y ∘.× 1 0 0) + (M ∘.× 0 1 0) + D ∘.× 0 0 1
∇

The Conversion Functions

calfr∆gr2fr - Conversion from the Gregorian calendar to the French Revolutionary calendar

After all theses complicated computations, the final conversion function is a bit of an anti-climax.

∇ R ← calfr∆gr2fr D
R ← calfr∆rd2fr calfr∆gr2rd D
∇

calfr∆fr2gr - Conversion from the French Revolutionary calendar to the Gregorian calendar

Same thing for the other conversion function.

∇ R ← calfr∆fr2gr D
R ← calfr∆rd2gr calfr∆fr2rd D
∇

Displaying the Dates

calfr∆prtfr - Displaying a French Revolutionary calendar date

Gathering the various date elements: day name, day number, month name, etc, is simple enough. What is more difficult is that this gives a result with extraneous spaces and that we have to get rid of these spaces, while keeping the required spaces.

∇ R ← calfr∆prtfr D; DAY; MONTH; CM; ⎕IO
⎕IO ← 1
DAY ← 10 8 ⍴ 'Décadi  Primidi Duodi   Tridi   QuartidiQuintidiSextidi Septidi Octidi  Nonidi  '
R ← DAY[1 + 10 | calfr∆day D;]
MONTH ← 13 11 ⍴ 'VendémiaireBrumaire   Frimaire   Nivôse     Pluviôse   Ventôse    Germinal   Floréal    Prairial   Messidor   Thermidor  Fructidor  jour compl.'
R ← R, ' ', (⍕ calfr∆day D), ' ', (MONTH[calfr∆month D;]), ' ', (calfr∆roman calfr∆year D), ', jour ', calfr∆feasts[¯30 + D +.× 0 30 1;]
CM ← ' ' ≠ R
R ← (CM ∨ 0,¯1↓CM) / R
R ← (⌽∨\⌽R≠' ')/R
∇

calfr∆roman - Converting a number into Roman numerals

If the number is higher than 3999, it is displayed with indo-arab numerals. Same thing if zero or negative.

∇ R ← calfr∆roman N; NODES
→ ((N>0) ∧ N<4000)/CONV
R ← ⍕ N
→ 0
CONV:
NODES ← 40 4 ⍴ (calfr∆nodes 'IVX'), (calfr∆nodes 'XLC'), (calfr∆nodes 'CDM'), calfr∆nodes 'M??'
R ← ,NODES[⎕IO + 30 20 10 0 + 10 10 10 10 ⊤ N;]
R ← (R≠' ')/R
∇

calfr∆nodes - Building the nodes of Roman numerals

The word "nodes" comes from the French-written book Histoire comparée des numérations écrites (which would translate to "Compared History of Written Number Systems"), by Geneviève Guitel. The original french word is "nœuds". When dealing with radix b, a node is a number k × bn, with k between 0 and b - 1. This function provides a step to build a string which contains 10 substrings, each one being a radix-10 node, written with Roman numerals. Each node is filled with spaces to reach a common length of 4 chars.

∇ R ← calfr∆nodes CH
R ← (' ', CH) [ ' IVX' ⍳ '    I   II  III IV  V   VI  VII VIIIIX  ' ]
∇

calfr∆feasts - Feasts of the French Revolutionary calendar

This function lists all 366 feasts for all days in a French Revolutionary calendar year.

∇ R ← calfr∆feasts; V
⍝ include here the contents of feasts
∇

It returns an array of 366 lines of 21 chars, one line per day.

      4 21↑calfr∆feasts
du Raisin            
du Safran            
de la Châtaigne      
de la Colchique      

Built-in Tests

calfr∆testdata - Test Data

This function gives an array of n lines and 7 columns, holding the data to test the conversion functions. Each line contains the Gregorian date in YYYY MM DD format, then the French Revolutionary date in YYYY MM DD format and lastly the Rata Die value. For example

      2 7↑calfr∆testdata
1792  9 22 1  1  1 654415
1793 10 23 2  2  2 654811

The first lines of the test data show that the 22nd September 1792 converts to 1st Vendémiaire I, with a Rata Die value of 654415. Likewise, the 23rd October 1793 converts to 2 Brumaire II, that is, Rata Die 654811.

The values used in these built-in tests come from the Perl module DateTime::Calendar::FrenchRevolutionary. The program which turns these dates into an APL code chunk is available on GitHub. And a utility script (in Perl) in this project inserts the contents of testapl in the final script.

∇ R ← calfr∆testdata; V; L
⍝ include here the contents of testapl
L ← (⍴ V) ÷ 7
R ← (L, 7) ⍴ V
∇

calfr∆teststring - Test alphanumeric data

This function gives the expected calfr∆prtfr result for each corresponding line ot calfr∆testdata. For example

      2 52↑calfr∆teststring
Primidi 1 Vendémiaire I, jour du Raisin             
Duodi 2 Brumaire II, jour du Céleri                 

The strings are produced and inserted into the final code in the same fashion as calfr∆testdata.

∇ R ← calfr∆teststring; V; L
V ← ''
⍝ include here the contents of testapl1
∇

calfr∆alltests - Run all the tests

∇ calfr∆alltests
calfr∆testfr2rd
calfr∆testgr2rd
calfr∆testrd2fr
calfr∆testrd2gr
calfr∆testgr2fr
calfr∆testfr2gr
calfr∆testprtfr

calfr∆testfr2rd

This function checks calfr∆fr2rd with a wide set of parameters: two scalar dates, a date vector, a date array, and so on until a rank-7 structure of dates (that is, a rank-8 block of integers). Tests are run a first time with ⎕IO set to 1, a second time with ⎕IO set to 0.

Each failed subtest prints an error message. If everything succeeds, the main test function prints a single message.

∇ calfr∆testfr2rd; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;7]
PARAM ← calfr∆testdata[;4 5 6]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0fr2rd 1
OK ←OK ∧ calfr∆test0fr2rd 2
OK ←OK ∧ calfr∆test1fr2rd 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1fr2rd 5 5
OK ←OK ∧ calfr∆test1fr2rd 5 3 3
OK ←OK ∧ calfr∆test1fr2rd 5 2 2 2
OK ←OK ∧ calfr∆test1fr2rd 7 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆fr2rd : no errors'
∇

calfr∆test0fr2rd

This function checks calfr∆fr2rd with a scalar date. ⎕IO is initialised with the global variableIO, actually a variable local to the upper function calfr∆testfr2rd and transmitted globally. The same transmission mechanism applies to PARAM (parameters for the checked function) and EXPEC (expected results).

∇ R  ← calfr∆test0fr2rd N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆fr2rd with scalar date ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆fr2rd PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDATA ⍝ no need to check dimension, it is the empty vector
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1fr2rd

This function checks calfr∆fr2rd with a date vector or a higher rank structure. ⎕IO is initialised with the global variableIO, actually a variable local to the upper function calfr∆testfr2rd and transmitted globally.

∇ R  ← calfr∆test1fr2rd DIM; LIB; EXP; GOT; NERR
LIB  ← 'Checking calfr∆fr2rd with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← DIM ⍴ EXPEC
⎕IO ← IO
GOT  ← calfr∆fr2rd (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testgr2rd

∇ calfr∆testgr2rd; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;7]
PARAM ← calfr∆testdata[;1 2 3]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0gr2rd 1
OK ←OK ∧ calfr∆test0gr2rd 2
OK ←OK ∧ calfr∆test1gr2rd 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1gr2rd 5 5
OK ←OK ∧ calfr∆test1gr2rd 5 3 3
OK ←OK ∧ calfr∆test1gr2rd 5 2 2 2
OK ←OK ∧ calfr∆test1gr2rd 7 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆gr2rd : no errors'
∇

calfr∆test0gr2rd

∇ R  ← calfr∆test0gr2rd N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆gr2rd with scalar date ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆gr2rd PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDATA ⍝ no need to check dimension, it is the empty vector
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1gr2rd

∇ R  ← calfr∆test1gr2rd DIM; LIB; EXP; GOT; NERR
LIB  ← 'Checking calfr∆gr2rd with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← DIM ⍴ EXPEC
GOT ← calfr∆gr2rd (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testrd2fr

∇ calfr∆testrd2fr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;4 5 6]
PARAM ← calfr∆testdata[;7]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0rd2fr 1
OK ←OK ∧ calfr∆test0rd2fr 2
OK ←OK ∧ calfr∆test1rd2fr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1rd2fr 3 3
OK ←OK ∧ calfr∆test1rd2fr 15 3
OK ←OK ∧ calfr∆test1rd2fr 2 3 3
OK ←OK ∧ calfr∆test1rd2fr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆rd2fr : no errors'
∇

calfr∆test0rd2fr

∇ R  ← calfr∆test0rd2fr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N]
LIB  ← 'Checking calfr∆rd2fr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆rd2fr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1rd2fr

∇ R ← calfr∆test1rd2fr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆rd2fr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆rd2fr DIM ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testrd2gr

∇ calfr∆testrd2gr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;1 2 3]
PARAM ← calfr∆testdata[;7]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0rd2gr 1
OK ←OK ∧ calfr∆test0rd2gr 2
OK ←OK ∧ calfr∆test1rd2gr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1rd2gr 3 3
OK ←OK ∧ calfr∆test1rd2gr 15 3
OK ←OK ∧ calfr∆test1rd2gr 2 3 3
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆rd2gr : no errors'
∇

calfr∆test0rd2gr

∇ R  ← calfr∆test0rd2gr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N]
LIB  ← 'Checking calfr∆rd2gr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆rd2gr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1rd2gr

∇ R ← calfr∆test1rd2gr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆rd2gr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆rd2gr DIM ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testgr2fr

∇ calfr∆testgr2fr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;4 5 6]
PARAM ← calfr∆testdata[;1 2 3]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0gr2fr 1
OK ←OK ∧ calfr∆test0gr2fr 2
OK ←OK ∧ calfr∆test1gr2fr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1gr2fr 3 3
OK ←OK ∧ calfr∆test1gr2fr 15 3
OK ←OK ∧ calfr∆test1gr2fr 2 3 3
OK ←OK ∧ calfr∆test1gr2fr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆gr2fr : no errors'
∇

calfr∆test0gr2fr

∇ R  ← calfr∆test0gr2fr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆gr2fr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆gr2fr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1gr2fr

∇ R ← calfr∆test1gr2fr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆gr2fr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆gr2fr (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testfr2gr

∇ calfr∆testfr2gr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;1 2 3]
PARAM ← calfr∆testdata[;4 5 6]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0fr2gr 1
OK ←OK ∧ calfr∆test0fr2gr 2
OK ←OK ∧ calfr∆test1fr2gr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1fr2gr 3 3
OK ←OK ∧ calfr∆test1fr2gr 15 3
OK ←OK ∧ calfr∆test1fr2gr 2 3 3
OK ←OK ∧ calfr∆test1fr2gr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆fr2gr : no errors'
∇

calfr∆test0fr2gr

∇ R  ← calfr∆test0fr2gr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆fr2gr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆fr2gr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1fr2gr

∇ R ← calfr∆test1fr2gr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking fr2gr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆fr2gr (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testprtfr

∇ calfr∆testprtfr; TD; TS; I; IMAX; PAR; EXP; GOT; N; IO; ⎕IO
IO ← 2
LOOPIO:
IO ← IO - 1
'Checking calfr∆prtfr with the full vector (a bit slow) and ⎕IO ', ⍕IO
⎕IO ← 1
TD ← calfr∆testdata[; 4 5 6 ]
TS ← calfr∆teststring
I ← 0
N ← 0
IMAX ← (⍴ TS)[1]
LOOP:
⎕IO ← 1
I ← I + 1
→ (I > IMAX)/END
PAR ← TD[I;]
EXP ← TS[I;]
EXP ← (⌽∨\⌽EXP≠' ')/EXP
⎕IO ← IO
GOT ← calfr∆prtfr PAR
→ ((⍴ EXP)  = ⍴ GOT)/NEXT
'Different length: ', (⍕⍴ EXP), ' ', ⍕⍴GOT
'Expected: ', EXP
'Got     : ', GOT
N ← N + 1
→ LOOP
NEXT:
→ (∧/EXP=GOT)/LOOP
'Different content'
'Expected: ', EXP
'Got     : ', GOT
N ← N + 1
→ LOOP
END:
'Data errors with ⎕IO = ', (⍕IO), ' : ', ⍕N
→ IO / LOOPIO 
∇

See Also

Software

In Perl: DateTime::Calendar::FrenchRevolutionary also available on GitHub.

Also in Perl: Date::Convert::French_Rev, available on GitHub.

Exists also in Ruby (not from me).

Or else with Emacs using e-LISP (I did the superfluous, others did the backbone).

Exists on Android (not from me either).

Exists also on HP-48 and HP-50 (GitHub repository).

Lastly, exists on HP-41.

Internet

Of course, the Wikipedia entry.

Claus Tondering's FAQ.

The entry on the H2G2 website.

A website in French.

Books

Quid 2001, M and D Frémy, publ. Robert Laffont, in French

Agenda Républicain 197 (1988/89), publ. Syros Alternatives, in French, ISBN 2 86738 262-9

Any French schoolbook about the French Revolution

The French Revolution, Thomas Carlyle, Oxford University Press, ISBN 0-19-281843-0, in English.

Introduction à APL, S. Pommier, éditions Dunod, 1978, ISBN 2-04-010402-X, in French.

Apprendre et appliquer le langage APL, B. Legrand, éditions Masson, 1981, ISBN 2-225-69421-4, in French.

]NEXTFILE

Français

Calendrier Républicain
calfr∆license
Conversion entre calendriers
Mode d'emploi
Installation
Chargement
Utilisation
calfr∆usage
Ordres supérieurs
Utilisation de "enclose"
Implémentation
Éléments de date
calfr∆year
calfr∆month
calfr∆day
calfr∆zerojanvnd - Notion de « jour zéro »
calfr∆fr2rd - Conversion du calendrier républicain vers Rata Die
calfr∆frbis - Nombre d'années bissextiles avant l'année N (calendrier républicain)
calfr∆gr2rd - Conversion du calendrier grégorien vers Rata Die
calfr∆rd2fr - Conversion du Rata Die vers le calendrier républicain
calfr∆rd2gr - Conversion du Rata Die vers le calendrier grégorien
Les fonctions de conversion
calfr∆gr2fr - Conversion du calendrier grégorien vers le calendrier républicain
calfr∆fr2gr - Conversion du calendrier républicain vers le calendrier grégorien
Affichage des dates
calfr∆prtfr - Affichage d'une date du calendrier républicain
calfr∆roman - Conversion d'un nombre en chiffres romains
calfr∆nodes - Construction des nœuds de la numération romaine
calfr∆feasts - Fêtes du calendrier républicain
Tests internes
calfr∆testdata - Données pour les tests
calfr∆teststring - Données alphanumériques de tests
calfr∆alltests - Lancement de tous les tests
calfr∆testfr2rd
calfr∆test0fr2rd
calfr∆test1fr2rd
calfr∆testgr2rd
calfr∆test0gr2rd
calfr∆test1gr2rd
calfr∆testrd2fr
calfr∆test0rd2fr
calfr∆test1rd2fr
calfr∆testrd2gr
calfr∆test0rd2gr
calfr∆test1rd2gr
calfr∆testgr2fr
calfr∆test0gr2fr
calfr∆test1gr2fr
calfr∆testfr2gr
calfr∆test0fr2gr
calfr∆test1fr2gr
calfr∆testprtfr
Voir également
Logiciels
Internet
Livres

Calendrier Républicain

calfr∆license

La partie texte de ce dépôt git est distribuée sous la licence Creative Commons avec attribution et partage dans les mêmes conditions (CC-BY-SA). La partie code de ce dépôt est distribuée sous la licence GPL version 1.0 ou ultérieure.

Ainsi que le requiert la licence GPL, tout fichier de code doit commencer par un commentaire décrivant de façon sommaire le logiciel et résumant la GPL. La description sommaire en français :

« Les fonctions de ce script permettent de faire des conversions du calendrier grégorien vers le calendrier républicain et vice-versa. »

Quant au résumé de la GPL, le voici, en anglais (je ne suis pas assez calé pour traduire en français un texte de teneur juridique).

∇ calfr∆license
'APL program to convert Gregorian dates'
'to French Revolutionary dates and the other way'
''
'Copyright (C) 2015, 2016 Jean Forget  (JFORGET at cpan dot org)'
''
'Build date:'
''
'Portability: L1 (tested with GNU-APL and NARS2000)'
''
' This program is distributed under the GNU Public License version 1 or later'
''
' You can find the text of the license in the LICENSE file or at'
' http://www.gnu.org/licenses/gpl-1.0.html.'
''
' Here is the summary of GPL:'
''
' This program is free software; you can redistribute it and/or modify'
' it under the terms of the GNU General Public License as published by'
' the Free Software Foundation; either version 1, or (at your option)'
' any later version.'
''
' This program is distributed in the hope that it will be useful,'
' but WITHOUT ANY WARRANTY; without even the implied warranty of'
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the'
' GNU General Public License for more details.'
''
' You should have received a copy of the GNU General Public License'
' along with this program; if not, write to the Free Software Foundation,'
' Inc., <http://www.fsf.org/>.'
∇

Comme vous pouvez le deviner, ce résumé fait partie intégrante du logiciel. Donc, une fois que vous êtes entrés sous APL et que vous avez initialisé votre espace de travail avec mon script, vous pouvez à tout moment afficher ce résumé.

Portabilité : en convertissant le fichier script en fichier ATF, on peut charger les fonctions sous NARS2000. Juste un hic, les lettres accentuées et c-cédille déclenchent une erreur de syntaxe. La chaîne de compilation remplace donc ces lettres par des lettres inoffensives, voyelles sans accent et « c » sans cédille. Avec cette précaution, les programmes fonctionnent parfaitement sous NARS2000.

Conversion entre calendriers

Cette série de fonctions s'applique à trois calendriers différents.

Le calendrier républicain
Le calendrier républicain a été en vigueur en France de 1792 à 1805, mais les fonctions de ce script permettent d'utiliser des dates jusqu'à aujourd'hui et au-delà. Les dates sont représentées par un vecteur de trois nombres au format AAAA MM JJ. Il y a douze mois de 30 jours chacun, plus 5 ou 6 jours complémentaires à la fin de l'année. Pour des raisons de simplicité, ces jours complémentaires constituent un mois supplémentaire numéroté 13.
Le calendrier grégorien proleptique
Il s'agit du calendrier grégorien, extrapolé dans le passé au-delà de l'année 1582. Non seulement la règle des années bissextiles est celle du calendrier grégorien, mais en plus on considère que l'année commence toujours le premier janvier, ce qui n'était pas toujours le cas avec les calendriers réellement en usage au Moyen-Âge. Les dates grégoriennes sont exprimées sous la forme d'un vecteur de trois nombres, au format AAAA MM JJ.
Le Rata Die (RD)
C'est un calendrier qui ignore totalement les années. Il ne compte que les jours, à partir du 1er janvier de l'an 1 du calendrier grégorien proleptique. Très pratique pour les programmes, beaucoup moins pour les humains. En fait, le calendrier Rata Die ne vous servira pas en tant que tel, c'est simplement un intermédiaire entre les deux autres. Vous pouvez en avoir besoin quand même par exemple pour calculer le nombre de jours entre deux dates.

Mode d'emploi

Installation

Le fichier calfr.apl doit se trouver dans le sous-répertoire workspaces, pour compatibilité avec la commande )LOAD. Vous pouvez stocker ce fichier dans un sous-répertoire wslibn (n valant de 1 à 9), auquel cas vous devrez utiliser la variante )LOAD n calfr.apl. De même, là où le mode d'emploi indique la commande )LOAD, vous pouvez utiliser les variantes )XLOAD ou )COPY.

Installation pour GNU-APL à partir de GitHub : vous pouvez cloner le dépôt et copier workspaces/calfr.apl vers le sous-répertoire workspaces de votre répertoire APL. Ou alors, affichez https://github.com/.../calfr.apl en mode raw et sauvegardez-le en mode « texte seul » dans le sous-répertoire workspaces de votre répertoire APL.

Installation pour GNU-APL à partir de GNU-APL Bits and Pieces : sauvegardez le fichier affiché en mode « texte seul » dans le sous-répertoire workspace. Ne vous préoccupez pas de tout le texte en anglais et en français, il sera masqué lors du chargement sous APL.

Installation pour NARS2000 à partir de GitHub : clonez le dépôt et copier workspaces/calfrnars.atf vers le répertoire c:\user\moi-meme\Application Data\NARS2000\workspaces ou, si vous utilisez GNU/Linux et Wine : /home/moi-meme/.wine/drive_c/users/moi-meme/Application Data/NARS2000/workspaces.

Chargement

Chargement dans GNU-APL : vous chargez un espace de travail initialisé avec les fonctions de conversion par :

apl -f workspaces/calfr.apl

ou bien avec :

apl
    )LOAD calfr

Chargement dans NARS2000 : vous chargez un espace de travail initialisé avec les fonctions de conversion par :

)IN calfrnars

Utilisation

Vous vous retrouvez alors dans la boucle REPL (lecture, évaluation, impression et boucle) d'APL. Vous pouvez alors demander la conversion du 13 Vendémiaire IV ainsi :

     calfr∆fr2gr 4 1 13
1795 10 5

ce qui veut dire que le 13 Vendémiaire IV correspond au 5 octobre 1795. Dans l'autre sens, la conversion du 27 juillet 1794 se fait ainsi :

     calfr∆gr2fr 1794 7 27
2 11 9

ce qui signifie 9 Thermidor II. Pour avoir la date sous forme plus agréable, utilisez calfr∆prtfr :

     calfr∆prtfr 2 11 9
Nonidi 9 Thermidor II, jour de la Mûre
     calfr∆prtfr calfr∆gr2fr 1794 7 27
Nonidi 9 Thermidor II, jour de la Mûre

Et pour avoir la date du jour

     calfr∆prtfr calfr∆gr2fr 3↑⎕TS

Si vous voulez calculer le nombre de jours entre deux dates, utilisez le Rata Die. Par exemple, vous voulez savoir combien de jours séparent le coup d'état du neveu (2 décembre 1851) du coup d'état de l'oncle (18 Brumaire VIII). Il suffit de taper :

     (calfr∆gr2rd 1851 12 2) - calfr∆fr2rd 8 2 18
19015

calfr∆usage

Voici l'aide en ligne en anglais.

⍝ Installing from GitHub (https://github.com/jforget/apl-calendar-french):
⍝     clone the Git repo and copy "workspaces/calfr.apl" to the "workspaces" sub-directory of your GNU-APL directory
⍝                         or copy "workspaces/calfrnars.atf" to some place where NARS2000 will find it
⍝ Installing from browsing https://www.gnu.org/software/apl/Bits_and_Pieces/
⍝     save the webpage as simple text into the "workspaces" sub-directory of your GNU-APL directory
∇ calfr∆usage
'Loading the module from the command line (for GNU-APL):'
'      apl -f workspaces/calfr.apl'
'Loading the module from inside GNU-APL:'
'      )LOAD calfr'
'Loading the module from inside NARS2000:'
'      )IN calfrnars'
'Dates are vectors of 3 numbers YYYY MM DD. e.g.'
'      DR ←    8 2 18 ⍝ for 18 Brumaire VIII'
'      DG ← 1794 7 27 ⍝ for 27th July 1794'
'Gregorian to French Revolutionary:'
'      DR ← calfr∆gr2fr DG'
'French Revolutionary to Gregorian:'
'      DG ← calfr∆fr2gr DR'
'Pretty-printing:'
'      STRING ← calfr∆prtfr DR'
'Pretty-printing today''s date:'
'      STRING ← calfr∆prtfr calfr∆gr2fr 3↑⎕TS'
∇

Ordres supérieurs

Une date est représentée par un vecteur de 3 nombres. On peut organiser les dates en un vecteur de dates, qui sera en réalité un tableau de nombres. Ainsi, un vecteur de 5 dates sera un tableau de dimension 5 3.

Par exemple, le vecteur de dates :

21-JAN-17945-OCT-17959-NOV-17992-DEC-185131-MAR-2015

sera représenté par :

     DATEVEC 
1794  1 21
1795 10  5
1799 11  9
1851 12  2
2015  3 31
     ⍴ DATEVEC 
5 3

De la même manière, un tableau 4 5 de dates sera représenté par un bloc 4 5 3 de nombres, un bloc 2 2 6 de dates sera représenté par un bloc 2 2 6 3 de nombres, et ainsi de suite. Les fonctions calfr∆gr2fr et calfr∆fr2gr sont compatibles avec toutes ces structures, sauf lorsque l'on arrive au voisinage du rang maximal. Par exemple, sur cette implémentation d'APL le rang maximal est 8, donc les fonctions commencent à provoquer des erreurs avec une structure de rang 7.

Ainsi, la conversion du vecteur ci-dessus se fait par :

     calfr∆gr2fr DATEVEC 
  2  5  2
  4  1 13
  8  2 18
 60  3 11
223  7 11

Par contre, la fonction calfr∆prtfr n'est pas compatible avec les vecteurs de dates ou les tableaux de dates. Elle accepte seulement les dates scalaires.

Utilisation de "enclose"

Je sais que APL permet d'inclure des objets dans d'autres, avec les opérateurs et . Mais cela n'existait pas lorsque j'ai appris APL il y a plus de trente ans. J'ai donc écrit le présent script sans chercher à utiliser ces opérateurs. J'avais l'intention d'écrire dans un deuxième temps un nouveau script qui reposerait sur et .

Mais j'ai fait quelques expériences avec le script dans l'état actuel. En utilisant la fonction ¨ (each), les fonctions deviennent compatibles avec les objets « enclosed ». Exemple :

     V ← 2 2 ⍴ (1792 9 22) (1795 10 5) (1799 11 9), ⊂ 3↑⎕TS
     V
1792 9 22  1795 10 5  
1799 11 9  2015 12 24 
     ⍴V
2 2
     V1 ← calfr∆prtfr ¨ calfr∆gr2fr ¨ V
     V1
Primidi 1 Vendémiaire I, jour du Raisin        Tridi 13 Vendémiaire IV, jour du Potiron 
Octidi 18 Brumaire VIII, jour de la Dentelaire Quartidi 4 Nivôse CCXXIV, jour du Soufre 
     ⍴V1
2 2

Implémentation

Éléments de date

Pour extraire l'année d'une date DATE, il suffit d'écrire DATE[1]. Pour en extraire le jour, il suffit d'écrire DATE[3]. Mais cela ne fonctionne pas avec les vecteurs de dates pour lesquels il faut écrire DATEVEC[;1] et DATEVEC[;3], ni avec les tableaux de dates pour lesquel il faut écrire DATEARRAY[;;1] et DATEARRAY[;;3]. Il y a plusieurs façons de s'en tirer et d'avoir une fonction compatible avec tous les rangs et toutes les dimensions. La plus simple consiste à écrire :

calfr∆year

∇ R ← calfr∆year D
R ← D +.× 1 0 0
∇

calfr∆month

∇ R ← calfr∆month D
R ← D +.× 0 1 0
∇

calfr∆day

∇ R ← calfr∆day D
R ← D +.× 0 0 1
∇

calfr∆zerojanvnd - Notion de « jour zéro »

Pour comprendre comment les algorithmes de conversion fonctionnent, il est utile d'utiliser la notion de « jour zéro » pour un mois donné. Le jour zéro est un jour virtuel situé avant le premier jour du mois et après le dernier jour du mois précédent. Ainsi, le 0 mars 2015 se situe après le 28 février 2015 et avant le 1er mars 2015. Le fait que son Rata Die soit identique à celui du 28 février ne change rien au fait qu'il est censé se situer après le 28 février.

Voici une fonction qui permet de construire le 0 janvier d'une année ou le 0 Vendémiaire d'une année :

∇ R ← calfr∆zerojanvnd Y
R ← Y ∘.× 1 0 0
R ← R + (⍴R) ⍴ 0 1 0
∇

calfr∆fr2rd - Conversion du calendrier républicain vers Rata Die

Commençons par calculer le Rata Die d'une date du calendrier républicain. Il faut ajouter les termes suivants :

En fait, on rassemble toutes les constantes additives en une seule, ce qui donne le calcul suivant :

∇ R ← calfr∆fr2rd DATE
R ← 654019 + (calfr∆frbis calfr∆year DATE) + DATE +.× 365 30 1
∇

calfr∆frbis - Nombre d'années bissextiles avant l'année N (calendrier républicain)

Cette fonction donne le nombre d'années bissextiles qui ont eu lieu avant l'année en paramètre. Elle permet d'affiner le calcul de la date, par rapport à l'estimation obtenue en prenant des années à 365 jours.

La règle pour les années bissextiles était une règle astronomique jusqu'en l'an XX, le premier Vendémiaire devant coïncider avec l'équinoxe d'automne. Ensuite, la réforme de Gilbert Romme aurait fait appliquer une règle arithmétique analogue à celle du calendrier grégorien.

La règle de Romme indique qu'une année est bissextile si elle est divisible par 4, sauf si elle est divisible par 100, mais elle l'est quand même si elle est divisible par 400, sauf si elle est divisible par 4000.

Voici la comparaison des deux règles pour le début de l'ère républicaine. Les deux colonnes « arithmétique » simulent l'hypothèse où la règle arithmétique aurait été en vigueur dès le début. Les deux colonnnes « astronomique » présentent le cas réel : règle astronomique au début et règle arithmétique ensuite. Les colonnes « Bis ? » indiquent par un « X » si l'année est bissextile en fonction de la règle utilisée. Les colonnes « frbis » indiquent le nombre d'années bissextiles avant l'année considérée, ce qui correspond au résultat de la fonction calfr∆frbis.

ArithmétiqueAstronomique
AnnéeBis ?frbis Bis ?frbis
1   0   0
2   0   0
3   0 x 0
4 x 0   1
5   1   1
6   1   1
7   1 x 1
8 x 1   2
9   2   2
10   2   2
11   2 x 2
12 x 2   3
13   3   3
14   3   3
15   3 x 3
16 x 3   4
17   4   4
18   4   4
19   4   4
20 x 4 x 4
21   5   5
22   5   5
23   5   5
24 x 5 x 5
25   6   6

En fait, les deux règles n'engendrent une différence que pour les années IV, VIII, XII et XVI. Donc le sous-programme effectue le calcul avec la règle arithmétique dès l'an I, puis rectifie le résultat si l'on a l'une de ces quatre années. N'oubliez pas que le sous-programme commence par soustraire 1 à l'année, c'est pour cela qu'il teste les valeurs 3, 7, 11 et 15.

∇ R ← calfr∆frbis YEAR
YEAR ← YEAR - 1
R ← - / ⌊ YEAR ∘.÷ 4 100 400 4000
R ← R + YEAR ∈ 3 7 11 15
∇

calfr∆gr2rd - Conversion du calendrier grégorien vers Rata Die

Malgré les critères pour les années bissextiles, la conversion du calendrier républicain vers le Rata Die est simple, comparée avec la fonction correspondante du calendrier grégorien. En effet, la période bouche-trou (jours complémentaires) est à la fin de l'année donc sa longueur variable et plus faible que les mois normaux n'a pas d'influence sur les calculs. En revanche, dans le calendrier grégorien, le mois bouche-trou, février, apparaît au début de l'année et complique le calcul pour les mois suivants. De plus, la longueur des autres mois est variable : 30 ou 31 jours.

C'est pourquoi nous introduisons un nouveau calendrier, le « calendrier grégorien décalé ». Dans ce calendrier, les mois de janvier et février sont transférés à la fin de l'année précédente et l'année commence au 1er mars. De plus, pour les raisons exposées ci-dessous, les mois sont numérotés de 4 (mars) à 15 (février), pour la raison exposée ci-dessous.

Ainsi le 28 février 2015 devient 2014 15 28 et le premier mars 2015 devient 2015 4 1.

Dans le tableau ci-dessous, la partie gauche s'intéresse au nombre de mois à 31 jours qui précèdent le mois courant dans l'année décalée. La partie droite explique le calcul du nombre de jours depuis le zéro mars jusqu'au jour zéro du mois courant.

Mnb31 M0.6 × M¯2 + ⌊ 0.6 × MM ln nb30.6 × M¯122 + ⌊ 30.6 × M
M 0 4 2.4 0 M 31 0 122.4 0
A 1 5 3.0 1 A 30 31 153.0 31
M 1 6 3.6 1 M 31 61 183.6 61
J 2 7 4.2 2 J 30 92 214.2 92
J 2 8 4.8 2 J 31 122 244.8 122
A 3 9 5.4 3 A 31 153 275.4 153
S 4 10 6.0 4 S 30 184 306.0 184
O 4 11 6.6 4 O 31 214 336.6 214
N 5 12 7.2 5 N 30 245 367.2 245
D 5 13 7.8 5 D 31 275 397.8 275
J 6 14 8.4 6 J 31 306 428.4 306
F 7 15 9.0 7 F28? 29?337 459.0 337

Voici donc la fonction :

∇ R ← calfr∆gr2rd DATE; DIM
DIM ← ⍴ DATE
DATE←(DIM ⍴0 1 0) + DATE + (2≥calfr∆month DATE) ∘.× ¯1 12 0
R ← ¯428 + (-/ ⌊ (calfr∆year DATE) ∘.÷ 4 100 400) + ⌊DATE +.× 365 30.6 1
∇

calfr∆rd2fr - Conversion du Rata Die vers le calendrier républicain

Principe : en partant de la valeur Rata Die, la fonction évalue une fourchette pour l'année, qui peut donc s'étaler de YL à YH. Supposons que l'on parte de la valeur Rata Die 672800, avec une fourchette de 50 à 53. Le sous-programme construit ensuite la liste YR des années de la fourchette, soit dans l'exemple un vecteur YR valant 50 51 52 53. Puis le sous-programme transforme ce vecteur d'années en vecteurs de dates DR, correspondant au zéro Vendémiaire de chaque année, ce qui donnerait :

    DR
50 1 0
51 1 0
52 1 0
53 1 0  

La fonction calfr∆fr2rd convertit cela en Rata Die

    NR
672311 672676 673041 673407

et compare avec la valeur initiale 672800, ce qui donne :

    CM
1 1 0 0

Cela permet d'avoir l'année de la date en prenant l'année la plus élevée pour laquelle la valeur dans NR est inférieure à la valeur du paramètre d'entrée N. Dans l'exemple ci-dessus, on en déduit que l'année est 51. En faisant la différence des deux valeurs Rata Die, on obtient 124, ce qui est facilement converti en 4 Pluviôse (5 4) avec de simples divisions par 30.

Remarque : dans l'exemple ci-dessus, la plage d'années avait 4 valeurs. Dans la réalité, la plage n'aura qu'une seule valeur, parfois deux au voisinage des changements d'année. Le choix de 4 valeurs avait un rôle pédagogique. En fait, la première date pour laquelle la plage comporte trois valeurs est l'année 3744 et il faut attendre 7480 pour qu'elle compte quatre valeurs.

Autre remarque : lorsque le paramètre en entrée n'est pas un scalaire, mais un vecteur, un tableau ou une structure d'ordre plus élevé, les variables intermédiaires ont également un ordre plus élevé. Le seul point délicat est de savoir ce qui se passe lorsque l'une des valeurs Rata Die donne une plage de 1 valeur et l'autre une plage de 2 valeurs (par exemple). Dans ce cas, la plage de valeurs du premier Rata Die est allongée à 2 valeurs pour conserver la rectangularité de la variable YR.

∇ R ← calfr∆rd2fr N; ⎕IO; N1; YH; YL; YI; YR; DR; NR; CM; Y; M; D
⎕IO ← 1
N1 ← N - 654414
YH ← ⌈ N1 ÷ 365.24
YL ← ⌈ N1 ÷ 365.34
YI ← 0 , ⍳ ⌈/,YH-YL
YR ← YL ∘.+ YI
DR ← calfr∆zerojanvnd YR
NR ← calfr∆fr2rd DR
CM ← NR < N ∘.+ (⍴YI)⍴0
Y ← ⌈/YR×CM
D ← N - ⌈/NR×CM
M ← ⌈D÷30
D ← D - 30 × M - 1
R ← (Y ∘.× 1 0 0) + (M ∘.× 0 1 0) + D ∘.× 0 0 1
∇

Résumé des variables locales :

YHyear (high) borne supérieure de la plage d'années
YLyear (low) borne inférieure de la plage d'années
YIyear increments vecteur d'incréments pour construire la plage d'années
YRyear range plage d'années
DRdate range plage de dates (0 Vendémiaire)
NRnumber range plage de nombres (Rata Die)
CMcomparison résultat de la comparaison
Y year final value valeur finale de l'année
D day final value valeur finale du jour
M month final valuevaleur finale du mois

Voici un exemple d'exécution avec un vecteur en entrée. Notons que pour le premier nombre, la plage de valeurs de la date comportait une seule année, l'année 400. Mais par « contamination » de la dernière valeur, cette plage a été étendue à trois années.

      V←800505 800511 800879 2115019
      calfr∆rd2fr V
 400 12 30
 400 13  6
 402  1  3
4000  1  1
      YH
400 401 402 4000
      YL
400 400 401 3998
      YI
0 1 2
      YR
 400  401  402
 400  401  402
 401  402  403
3998 3999 4000
      DR
 400 1 0
 401 1 0
 402 1 0

400 1 0 401 1 0 402 1 0

401 1 0 402 1 0 403 1 0

3998 1 0 3999 1 0 4000 1 0 NR 800145 800511 800876 800145 800511 800876 800511 800876 801241 2114288 2114653 2115018 CM 1 0 0 1 0 0 1 1 0 1 1 1 Y 400 400 402 4000 M 12 13 1 1 D 30 6 3 1

calfr∆rd2gr - Conversion du Rata Die vers le calendrier grégorien

Pour cette fonction, il y a deux possibilités. La première consiste à convertir le Rata Die en grégorien décalé, puis le grégorien décalé en grégorien. Ou alors, convertir directement en grégorien en utilisant deux fois le mécanisme de plage de valeurs possibles utilisé dans calfr∆rd2fr, une première fois pour calculer l'année, la seconde fois pour calculer le mois. C'est cette deuxième possibilité que j'ai choisie.

Le calcul de la borne supérieure et de la borne inférieure de la plage de mois nécessite une division par 29,5 et une division par 31. Du coup, l'estimation du mois est beaucoup moins précise que l'estimation de l'année. La plage aura donc trois valeurs dès que le nombre de jours vaut 620. Or, on sait que le nombre de jours ne dépassera pas 366 et que le numéro du mois ne dépassera pas 12. Dans ces conditions, la plage aura au maximum deux valeurs. Donc on ne s'embête pas, on prend toujours une plage à deux valeurs pour calculer le mois, même si l'estimation avait donné une plage à une seule valeur.

∇ R ← calfr∆rd2gr N; ⎕IO; YH; YL; YI; YR; DR; NR; CM; Y; M; D; ML; MR
⎕IO ← 1
YH ← ⌈ N ÷ 365.24
YL ← ⌈ N ÷ 365.25
YI ← 0 , ⍳ ⌈/,YH-YL
YR ← YL ∘.+ YI
DR ← calfr∆zerojanvnd YR
NR ← calfr∆gr2rd DR
CM ← NR < N ∘.+ (⍴YI)⍴0
Y ← ⌈/YR×CM
D ← N - ⌈/NR×CM
ML ← ⌈D÷31
MR ← ML ∘.+ 0 1
DR ← (Y ∘.× 2 3 ⍴ 1 0 0) + (MR ∘.× 0 1 0)
NR ← calfr∆gr2rd DR
CM ← NR < N ∘.+ 0 0
M ← ⌈/MR×CM
D ← N - ⌈/NR×CM
R ← (Y ∘.× 1 0 0) + (M ∘.× 0 1 0) + D ∘.× 0 0 1
∇

Les fonctions de conversion

calfr∆gr2fr - Conversion du calendrier grégorien vers le calendrier républicain

Après tous ces calculs compliqués, la fonction de conversion est simplissime, un peu décevante.

∇ R ← calfr∆gr2fr D
R ← calfr∆rd2fr calfr∆gr2rd D
∇

calfr∆fr2gr - Conversion du calendrier républicain vers le calendrier grégorien

Idem pour l'autre fonction de conversion.

∇ R ← calfr∆fr2gr D
R ← calfr∆rd2gr calfr∆fr2rd D
∇

Affichage des dates

calfr∆prtfr - Affichage d'une date du calendrier républicain

Rassembler les éléments de la date : nom du jour, numéro, nom du mois, etc, ne pose pas de problème. Ce qui en pose, c'est le fait que le résultat comporte des espaces superflus et qu'il faut les éliminer tout en conservant les espaces nécessaires.

∇ R ← calfr∆prtfr D; DAY; MONTH; CM; ⎕IO
⎕IO ← 1
DAY ← 10 8 ⍴ 'Décadi  Primidi Duodi   Tridi   QuartidiQuintidiSextidi Septidi Octidi  Nonidi  '
R ← DAY[1 + 10 | calfr∆day D;]
MONTH ← 13 11 ⍴ 'VendémiaireBrumaire   Frimaire   Nivôse     Pluviôse   Ventôse    Germinal   Floréal    Prairial   Messidor   Thermidor  Fructidor  jour compl.'
R ← R, ' ', (⍕ calfr∆day D), ' ', (MONTH[calfr∆month D;]), ' ', (calfr∆roman calfr∆year D), ', jour ', calfr∆feasts[¯30 + D +.× 0 30 1;]
CM ← ' ' ≠ R
R ← (CM ∨ 0,¯1↓CM) / R
R ← (⌽∨\⌽R≠' ')/R
∇

calfr∆roman - Conversion d'un nombre en chiffres romains

Si le nombre dépasse 3999, il conserve son expression en chiffres indo-arabes. Idem s'il est négatif ou nul.

∇ R ← calfr∆roman N; NODES
→ ((N>0) ∧ N<4000)/CONV
R ← ⍕ N
→ 0
CONV:
NODES ← 40 4 ⍴ (calfr∆nodes 'IVX'), (calfr∆nodes 'XLC'), (calfr∆nodes 'CDM'), calfr∆nodes 'M??'
R ← ,NODES[⎕IO + 30 20 10 0 + 10 10 10 10 ⊤ N;]
R ← (R≠' ')/R
∇

calfr∆nodes - Construction des nœuds de la numération romaine

Le terme « nœuds » provient de Histoire comparée des numérations écrites par Geneviève Guitel et je l'ai traduit en « nodes ». Dans une numération de base b, un nœud est un nombre de la forme k × bn, avec k entre 0 et b - 1. Cette fonction participe à la construction d'une chaîne de caractères comportant 10 nœuds de la base 10 écrit en chiffres romains. Chaque nœud est complété par des espaces pour avoir une longueur de 4.

∇ R ← calfr∆nodes CH
R ← (' ', CH) [ ' IVX' ⍳ '    I   II  III IV  V   VI  VII VIIIIX  ' ]
∇

calfr∆feasts - Fêtes du calendrier républicain

Cette fonction énumère les fêtes des 366 jours du calendier républicain.

∇ R ← calfr∆feasts; V
⍝ include here the contents of feasts
∇

Elle renvoie un tableau de 366 lignes de 21 caractères, une ligne par jour.

      4 21↑calfr∆feasts
du Raisin            
du Safran            
de la Châtaigne      
de la Colchique      

Tests internes

calfr∆testdata - Données pour les tests

Cette fonction renvoie un tableau de n lignes et 7 colonnes, contenant les données pour tester les fonctions de conversion. Chaque ligne contient la date grégorienne, au format AAAA MM JJ, la date républicaine au format AAAA MM JJ et la valeur Rata Die. Exemple :

      2 7↑calfr∆testdata
1792  9 22 1  1  1 654415
1793 10 23 2  2  2 654811

Les premières lignes montrent que le 22 septembre 1792 correspond au 1er Vendémiaire I avec une valeur Rata Die 654415. De même, le 23 octobre 1973 correspond au 2 Brumaire II, Rata Die 654811.

Les valeurs adoptées sont les valeurs utilisées dans les tests de DateTime::Calendar::FrenchRevolutionary. Le programme qui les formatte avec la syntaxe APL est disponible sur GitHub. Et un utilitaire (en Perl) est fourni dans le présent projet pour insérer les valeurs dans le script final.

∇ R ← calfr∆testdata; V; L
⍝ include here the contents of testapl
L ← (⍴ V) ÷ 7
R ← (L, 7) ⍴ V
∇

calfr∆teststring - Données alphanumériques de tests

Cette fonction donne pour chaque ligne de calfr∆testdata le résultat attendu de la fonction calfr∆prtfr. Par exemple :

      2 52↑calfr∆teststring
Primidi 1 Vendémiaire I, jour du Raisin             
Duodi 2 Brumaire II, jour du Céleri                 

L'origine des données et l'utilitaire pour les insérer dans le script sont les mêmes que pour calfr∆testdata.

∇ R ← calfr∆teststring; V; L
V ← ''
⍝ include here the contents of testapl1
∇

calfr∆alltests - Lancement de tous les tests

∇ calfr∆alltests
calfr∆testfr2rd
calfr∆testgr2rd
calfr∆testrd2fr
calfr∆testrd2gr
calfr∆testgr2fr
calfr∆testfr2gr
calfr∆testprtfr

calfr∆testfr2rd

Cette fonction teste calfr∆fr2rd avec une variété de paramètres en entrée : deux dates scalaires, un vecteur de dates, un tableau de dates, jusqu'à une structure de dates de rang 7 (donc une structure d'entiers de rang 8). Les tests sont effectués une première fois avec ⎕IO à 1, une seconde fois avec ⎕IO à 0.

Chaque test élémentaire tombant en erreur affiche un message. Si tous les tests élémentaires réussissent, la fonction principale de test affiche un seul message.

∇ calfr∆testfr2rd; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;7]
PARAM ← calfr∆testdata[;4 5 6]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0fr2rd 1
OK ←OK ∧ calfr∆test0fr2rd 2
OK ←OK ∧ calfr∆test1fr2rd 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1fr2rd 5 5
OK ←OK ∧ calfr∆test1fr2rd 5 3 3
OK ←OK ∧ calfr∆test1fr2rd 5 2 2 2
OK ←OK ∧ calfr∆test1fr2rd 7 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆fr2rd : no errors'
∇

calfr∆test0fr2rd

Cette fonction teste calfr∆fr2rd pour une date scalaire. ⎕IO est initialisé avec la variable globale IO, en fait une variable locale de la fonction appelante calfr∆testfr2rd transmise globalement, comme c'est le cas pour PARAM (paramètres à utiliser pour les fonctions) et EXPEC (résultat attendu ou expected result).

∇ R  ← calfr∆test0fr2rd N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆fr2rd with scalar date ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆fr2rd PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDATA ⍝ no need to check dimension, it is the empty vector
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1fr2rd

Cette fonction teste calfr∆fr2rd pour un vecteur de dates ou une structure de rang supérieur. ⎕IO est initialisé avec la variable globale IO, en fait une variable locale de la fonction appelante calfr∆testfr2rd transmise globalement.

∇ R  ← calfr∆test1fr2rd DIM; LIB; EXP; GOT; NERR
LIB  ← 'Checking calfr∆fr2rd with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← DIM ⍴ EXPEC
⎕IO ← IO
GOT  ← calfr∆fr2rd (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testgr2rd

∇ calfr∆testgr2rd; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;7]
PARAM ← calfr∆testdata[;1 2 3]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0gr2rd 1
OK ←OK ∧ calfr∆test0gr2rd 2
OK ←OK ∧ calfr∆test1gr2rd 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1gr2rd 5 5
OK ←OK ∧ calfr∆test1gr2rd 5 3 3
OK ←OK ∧ calfr∆test1gr2rd 5 2 2 2
OK ←OK ∧ calfr∆test1gr2rd 7 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆gr2rd : no errors'
∇

calfr∆test0gr2rd

∇ R  ← calfr∆test0gr2rd N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆gr2rd with scalar date ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆gr2rd PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDATA ⍝ no need to check dimension, it is the empty vector
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1gr2rd

∇ R  ← calfr∆test1gr2rd DIM; LIB; EXP; GOT; NERR
LIB  ← 'Checking calfr∆gr2rd with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← DIM ⍴ EXPEC
GOT ← calfr∆gr2rd (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testrd2fr

∇ calfr∆testrd2fr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;4 5 6]
PARAM ← calfr∆testdata[;7]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0rd2fr 1
OK ←OK ∧ calfr∆test0rd2fr 2
OK ←OK ∧ calfr∆test1rd2fr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1rd2fr 3 3
OK ←OK ∧ calfr∆test1rd2fr 15 3
OK ←OK ∧ calfr∆test1rd2fr 2 3 3
OK ←OK ∧ calfr∆test1rd2fr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆rd2fr : no errors'
∇

calfr∆test0rd2fr

∇ R  ← calfr∆test0rd2fr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N]
LIB  ← 'Checking calfr∆rd2fr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆rd2fr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1rd2fr

∇ R ← calfr∆test1rd2fr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆rd2fr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆rd2fr DIM ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testrd2gr

∇ calfr∆testrd2gr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;1 2 3]
PARAM ← calfr∆testdata[;7]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0rd2gr 1
OK ←OK ∧ calfr∆test0rd2gr 2
OK ←OK ∧ calfr∆test1rd2gr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1rd2gr 3 3
OK ←OK ∧ calfr∆test1rd2gr 15 3
OK ←OK ∧ calfr∆test1rd2gr 2 3 3
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆rd2gr : no errors'
∇

calfr∆test0rd2gr

∇ R  ← calfr∆test0rd2gr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N]
LIB  ← 'Checking calfr∆rd2gr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆rd2gr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1rd2gr

∇ R ← calfr∆test1rd2gr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆rd2gr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆rd2gr DIM ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testgr2fr

∇ calfr∆testgr2fr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;4 5 6]
PARAM ← calfr∆testdata[;1 2 3]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0gr2fr 1
OK ←OK ∧ calfr∆test0gr2fr 2
OK ←OK ∧ calfr∆test1gr2fr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1gr2fr 3 3
OK ←OK ∧ calfr∆test1gr2fr 15 3
OK ←OK ∧ calfr∆test1gr2fr 2 3 3
OK ←OK ∧ calfr∆test1gr2fr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆gr2fr : no errors'
∇

calfr∆test0gr2fr

∇ R  ← calfr∆test0gr2fr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆gr2fr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆gr2fr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1gr2fr

∇ R ← calfr∆test1gr2fr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking calfr∆gr2fr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆gr2fr (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testfr2gr

∇ calfr∆testfr2gr; IO; ⎕IO; OK; PARAM; EXPEC; RESUL
⎕IO ← 1
EXPEC ← calfr∆testdata[;1 2 3]
PARAM ← calfr∆testdata[;4 5 6]
OK ← 1
IO ← 2
LOOP:
IO ← IO - 1
⎕IO ← IO
OK ←OK ∧ calfr∆test0fr2gr 1
OK ←OK ∧ calfr∆test0fr2gr 2
OK ←OK ∧ calfr∆test1fr2gr 1 ↑ ⍴PARAM ⍝ checking with the full vector
OK ←OK ∧ calfr∆test1fr2gr 3 3
OK ←OK ∧ calfr∆test1fr2gr 15 3
OK ←OK ∧ calfr∆test1fr2gr 2 3 3
OK ←OK ∧ calfr∆test1fr2gr 6 ⍴ 2 ⍝ checking max allowed rank
→ IO / LOOP
→ (OK=0) / 0 ⍝ exit if the errors are already reported
'Checking calfr∆fr2gr : no errors'
∇

calfr∆test0fr2gr

∇ R  ← calfr∆test0fr2gr N; LIB; PAR; EXP; GOT; NERR
⎕IO ← 1
EXP ← EXPEC[N;]
PAR ← PARAM[N;]
LIB  ← 'Checking calfr∆fr2gr with scalar RD value ', (⍕PAR), ' and ⎕IO ', ⍕IO
⎕IO ← IO
GOT  ← calfr∆fr2gr PAR
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank: expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimensions: expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆test1fr2gr

∇ R ← calfr∆test1fr2gr DIM; EXP; GOT; LIB; NERR
LIB  ← 'Checking fr2gr with dimension ', (⍕DIM), ' and ⎕IO ', ⍕IO
EXP ← (DIM, 3) ⍴ EXPEC
GOT ← calfr∆fr2gr (DIM, 3) ⍴ PARAM
→ ((⍴⍴EXP)=⍴⍴GOT)/CHKDIM
LIB, ' Wrong rank, expected ', (⍕⍴⍴EXP), ', got ', ⍕⍴⍴GOT
R ← 0
→ 0
CHKDIM:
→ (∧/(⍴EXP)=⍴GOT)/CHKDATA
LIB, ' Wrong dimension, expected ', (⍕⍴EXP), ', got ', ⍕⍴GOT
R ← 0
→ 0
CHKDATA:
NERR ← +/,∨/EXP≠GOT
→ (NERR=0) / CHKIO
LIB, ' Data errors: ', ⍕ NERR
R ← 0
→ 0
CHKIO:
→ (IO=⎕IO) / SUCCESS
LIB, ' ⎕IO clobbered: was ', (⍕IO), ' became ', ⍕⎕IO
R ← 0
→ 0
SUCCESS:
R ← 1
→ 0
∇

calfr∆testprtfr

∇ calfr∆testprtfr; TD; TS; I; IMAX; PAR; EXP; GOT; N; IO; ⎕IO
IO ← 2
LOOPIO:
IO ← IO - 1
'Checking calfr∆prtfr with the full vector (a bit slow) and ⎕IO ', ⍕IO
⎕IO ← 1
TD ← calfr∆testdata[; 4 5 6 ]
TS ← calfr∆teststring
I ← 0
N ← 0
IMAX ← (⍴ TS)[1]
LOOP:
⎕IO ← 1
I ← I + 1
→ (I > IMAX)/END
PAR ← TD[I;]
EXP ← TS[I;]
EXP ← (⌽∨\⌽EXP≠' ')/EXP
⎕IO ← IO
GOT ← calfr∆prtfr PAR
→ ((⍴ EXP)  = ⍴ GOT)/NEXT
'Different length: ', (⍕⍴ EXP), ' ', ⍕⍴GOT
'Expected: ', EXP
'Got     : ', GOT
N ← N + 1
→ LOOP
NEXT:
→ (∧/EXP=GOT)/LOOP
'Different content'
'Expected: ', EXP
'Got     : ', GOT
N ← N + 1
→ LOOP
END:
'Data errors with ⎕IO = ', (⍕IO), ' : ', ⍕N
→ IO / LOOPIO 
∇

Voir également

Logiciels

En Perl : DateTime::Calendar::FrenchRevolutionary également disponible sur GitHub.

En Perl également : Date::Convert::French_Rev également disponible sur GitHub.

Existe aussi en Ruby (mais ce n'est pas moi).

Ou alors sous Emacs en e-LISP (les fioritures sont de moi, pas la substantifique moëlle).

Existe sur Android (là encore, ce n'est pas moi).

Existe aussi sur HP-48 et HP-50 (dépôt GitHub).

Existe enfin sur HP-41.

Internet

Forcément, l'entrée de Wikipedia.

La FAQ de Claus Tondering (en anglais).

L'entrée dans le site H2G2 (en anglais).

Un site en français.

Livres

Quid 2001, M and D Frémy, éditions Robert Laffont.

Agenda Républicain 197 (1988/89), éditions Syros Alternatives, ISBN 2 86738 262-9.

N'importe quel livre scolaire consacré à l'Histoire de la Révolution française.

The French Revolution, Thomas Carlyle, Oxford University Press, ISBN 0-19-281843-0, en anglais.

Introduction à APL, S. Pommier, éditions Dunod, 1978, ISBN 2-04-010402-X.

Apprendre et appliquer le langage APL, B. Legrand, éditions Masson, 1981, ISBN 2-225-69421-4.

]NEXTFILE

GPL License



                    GNU GENERAL PUBLIC LICENSE
                     Version 1, February 1989
 
  Copyright (C) 1989 Free Software Foundation, Inc.
                 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
                            Preamble
 
   The license agreements of most software companies try to keep users
 at the mercy of those companies.  By contrast, our General Public
 License is intended to guarantee your freedom to share and change free
 software--to make sure the software is free for all its users.  The
 General Public License applies to the Free Software Foundation's
 software and to any other program whose authors commit to using it.
 You can use it for your programs, too.
 
   When we speak of free software, we are referring to freedom, not
 price.  Specifically, the General Public License is designed to make
 sure that you have the freedom to give away or sell copies of free
 software, that you receive source code or can get it if you want it,
 that you can change the software or use pieces of it in new free
 programs; and that you know you can do these things.
 
   To protect your rights, we need to make restrictions that forbid
 anyone to deny you these rights or to ask you to surrender the rights.
 These restrictions translate to certain responsibilities for you if you
 distribute copies of the software, or if you modify it.
 
   For example, if you distribute copies of a such a program, whether
 gratis or for a fee, you must give the recipients all the rights that
 you have.  You must make sure that they, too, receive or can get the
 source code.  And you must tell them their rights.
 
   We protect your rights with two steps: (1) copyright the software, and
 (2) offer you this license which gives you legal permission to copy,
 distribute and/or modify the software.
 
   Also, for each author's protection and ours, we want to make certain
 that everyone understands that there is no warranty for this free
 software.  If the software is modified by someone else and passed on, we
 want its recipients to know that what they have is not the original, so
 that any problems introduced by others will not reflect on the original
 authors' reputations.
 
   The precise terms and conditions for copying, distribution and
 modification follow.
  
                    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
   0. This License Agreement applies to any program or other work which
 contains a notice placed by the copyright holder saying it may be
 distributed under the terms of this General Public License.  The
 "Program", below, refers to any such program or work, and a "work based
 on the Program" means either the Program or any work containing the
 Program or a portion of it, either verbatim or with modifications.  Each
 licensee is addressed as "you".
 
   1. You may copy and distribute verbatim copies of the Program's source
 code as you receive it, in any medium, provided that you conspicuously and
 appropriately publish on each copy an appropriate copyright notice and
 disclaimer of warranty; keep intact all the notices that refer to this
 General Public License and to the absence of any warranty; and give any
 other recipients of the Program a copy of this General Public License
 along with the Program.  You may charge a fee for the physical act of
 transferring a copy.
 
   2. You may modify your copy or copies of the Program or any portion of
 it, and copy and distribute such modifications under the terms of Paragraph
 1 above, provided that you also do the following:
 
     a) cause the modified files to carry prominent notices stating that
     you changed the files and the date of any change; and
 
     b) cause the whole of any work that you distribute or publish, that
     in whole or in part contains the Program or any part thereof, either
     with or without modifications, to be licensed at no charge to all
     third parties under the terms of this General Public License (except
     that you may choose to grant warranty protection to some or all
     third parties, at your option).
 
     c) If the modified program normally reads commands interactively when
     run, you must cause it, when started running for such interactive use
     in the simplest and most usual way, to print or display an
     announcement including an appropriate copyright notice and a notice
     that there is no warranty (or else, saying that you provide a
     warranty) and that users may redistribute the program under these
     conditions, and telling the user how to view a copy of this General
     Public License.
 
     d) You may charge a fee for the physical act of transferring a
     copy, and you may at your option offer warranty protection in
     exchange for a fee.
 
 Mere aggregation of another independent work with the Program (or its
 derivative) on a volume of a storage or distribution medium does not bring
 the other work under the scope of these terms.
  
   3. You may copy and distribute the Program (or a portion or derivative of
 it, under Paragraph 2) in object code or executable form under the terms of
 Paragraphs 1 and 2 above provided that you also do one of the following:
 
     a) accompany it with the complete corresponding machine-readable
     source code, which must be distributed under the terms of
     Paragraphs 1 and 2 above; or,
 
     b) accompany it with a written offer, valid for at least three
     years, to give any third party free (except for a nominal charge
     for the cost of distribution) a complete machine-readable copy of the
     corresponding source code, to be distributed under the terms of
     Paragraphs 1 and 2 above; or,
 
     c) accompany it with the information you received as to where the
     corresponding source code may be obtained.  (This alternative is
     allowed only for noncommercial distribution and only if you
     received the program in object code or executable form alone.)
 
 Source code for a work means the preferred form of the work for making
 modifications to it.  For an executable file, complete source code means
 all the source code for all modules it contains; but, as a special
 exception, it need not include source code for modules which are standard
 libraries that accompany the operating system on which the executable
 file runs, or for standard header files or definitions files that
 accompany that operating system.
 
   4. You may not copy, modify, sublicense, distribute or transfer the
 Program except as expressly provided under this General Public License.
 Any attempt otherwise to copy, modify, sublicense, distribute or transfer
 the Program is void, and will automatically terminate your rights to use
 the Program under this License.  However, parties who have received
 copies, or rights to use copies, from you under this General Public
 License will not have their licenses terminated so long as such parties
 remain in full compliance.
 
   5. By copying, distributing or modifying the Program (or any work based
 on the Program) you indicate your acceptance of this license to do so,
 and all its terms and conditions.
 
   6. Each time you redistribute the Program (or any work based on the
 Program), the recipient automatically receives a license from the original
 licensor to copy, distribute or modify the Program subject to these
 terms and conditions.  You may not impose any further restrictions on the
 recipients' exercise of the rights granted herein.
  
   7. The Free Software Foundation may publish revised and/or new versions
 of the General Public License from time to time.  Such new versions will
 be similar in spirit to the present version, but may differ in detail to
 address new problems or concerns.
 
 Each version is given a distinguishing version number.  If the Program
 specifies a version number of the license which applies to it and "any
 later version", you have the option of following the terms and conditions
 either of that version or of any later version published by the Free
 Software Foundation.  If the Program does not specify a version number of
 the license, you may choose any version ever published by the Free Software
 Foundation.
 
   8. If you wish to incorporate parts of the Program into other free
 programs whose distribution conditions are different, write to the author
 to ask for permission.  For software which is copyrighted by the Free
 Software Foundation, write to the Free Software Foundation; we sometimes
 make exceptions for this.  Our decision will be guided by the two goals
 of preserving the free status of all derivatives of our free software and
 of promoting the sharing and reuse of software generally.
 
                            NO WARRANTY
 
   9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 REPAIR OR CORRECTION.
 
   10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGES.
 
                     END OF TERMS AND CONDITIONS
 
        Appendix: How to Apply These Terms to Your New Programs
 
   If you develop a new program, and you want it to be of the greatest
 possible use to humanity, the best way to achieve this is to make it
 free software which everyone can redistribute and change under these
 terms.
 
   To do so, attach the following notices to the program.  It is safest to
 attach them to the start of each source file to most effectively convey
 the exclusion of warranty; and each file should have at least the
 "copyright" line and a pointer to where the full notice is found.
 
     
     Copyright (C) 19yy  
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 1, or (at your option)
     any later version.
 
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software Foundation,
     Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 
 Also add information on how to contact you by electronic and paper mail.
 
 If the program is interactive, make it output a short notice like this
 when it starts in an interactive mode:
 
     Gnomovision version 69, Copyright (C) 19xx name of author
     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
     This is free software, and you are welcome to redistribute it
     under certain conditions; type `show c' for details.
 
 The hypothetical commands `show w' and `show c' should show the
 appropriate parts of the General Public License.  Of course, the
 commands you use may be called something other than `show w' and `show
 c'; they could even be mouse-clicks or menu items--whatever suits your
 program.
 
 You should also get your employer (if you work as a programmer) or your
 school, if any, to sign a "copyright disclaimer" for the program, if
 necessary.  Here a sample; alter the names:
 
   Yoyodyne, Inc., hereby disclaims all copyright interest in the
   program `Gnomovision' (a program to direct compilers to make passes
   at assemblers) written by James Hacker.
 
   , 1 April 1989
   Ty Coon, President of Vice
 
 That's all there is to it!