Ved udviklingen af kalenderprogrammet benytter jeg derfor samme grundlæggende strategi som de to herrer, nemlig at definere en generel, overordnet kalender, som jeg kalder den absolutte kalender, og som alle de specifikke kalendere kan benytte sig af. Forskellen er, at Lisp er et funktionalsprog (alle beregninger er funktioner, der opererer på lister), mens Java er et objektorinteret sprog. Dette har store konsekvenser for strukturen af programmerne, mens algoritmerne i princippet kan være ens. Jeg har dog ikke kunnet nære mig for at lave lidt om i Dershowitz's og Reingolds algoritmer, men mere herom senere.
Overordnet set består kalenderproblemet i at holde rede på tidens gang. I de kalendere, jeg foreløbig kender til, løses denne opgave ved at associere tidens gang med bevægelsen af himmellegemerne. Den grundlæggende tidsenhed, dagen eller døgnet, refererer til jordens rotation om sin egen akse. Dagene grupperes i måneder, hvis længde fastlægges i relation til månens rotation om jorden. Endelig fastlægges året ud fra årstidernes skiften, dvs. i relation til jordens rotation om solen. Problemet består nu i, at der ikke er noget simpelt forhold mellem disse astronomiske fænomener. Hvis man insisterer på at regne med hele tal, kan man ikke konstruere en kalender, hvor alle måneder er lige lange og samtidig alle år er lige lange; i hvert fald ikke uden at man kommer ud af trit med himmellegemerne. De forskellige kalendere løser dette skisma på forskellige måder, bl.a. ved indførelse af skudår. En interessant kalender er ugekalenderen, i programmet kaldet ISO kalenderen, fordi den er defineret af den internationale standardiseringsorganisation ISO. I denne kalender har man forsøgt at erstatte måneden med ugen, som består af et helt antal dage, nemlig 7. Alle uger har altså samme længde i denne kalender, og alle år består af 52 uger. Et år har således 364 dage, hvilket skaber nogle problemer når ISO kalenderen skal sameksistere med den sædvanlige (gregorianske) kalender.
Jeg har ikke programmeret specifikke input/output procedurer, men tænker mig, at input/output kan bygges ovenpå mit program. Derfor forestiller jeg mig, at hver kalender skal indeholde metoder til omregning mellem datoer og typen String i Java. Dette betyder, at det jeg ikke vil lave er en færdig applikation, men derimod et standardprogram, der kan indgå i et programbibliotek.
Hvad angår beregninger på datoer er der mange muligheder,
men da mit program skal være et standardprogram, vil jeg kun tage
de helt oplagte beregninger med. Det ville fx være praktisk at kunne
addere en dato og et helt tal, hvorved man får en ny dato; eller
at kunne finde differensen mellem to datoer, som giver et helt tal (nemlig
antal dage). Det er ikke meningen, at man skal kunne operere på det
absolutte dagnummer; dette dagnummer skal være en velbevaret hemmelighed,
der ikke vedkommer brugeren af mit standardprogram. Det kan kun ændres
via metoderne i de konkrete kalendere.
Der er mange muligheder for design af strukturen af et datoobjekt.
Man kunne fx lave en hel flad struktur, hvor år, måned og dag
var simple hele tal, som vist til venstre i hosstående figur. Det
ville indebære, at alle metoder, også dem, der kun vedrører
et år eller en måned, skulle anbringes i samme klasse. En oplagt
årmetode er check på, om et år er skudår. Det forekommer
lidt mærkeligt, at denne metode er placeret i en datoklasse, der
jo i princippet kan have et objekt for hver dag i året. Man vinder
i overskuelighed ved at anbringe skudårsmetoden i en særlig
år-klasse og fx en metode til beregning af en måneds længde
i en særlig måned-klasse (men bemærk, at månedernes
længder kan afhænge af, om det er skudår). Dette bringer
os til det næste design på figuren, som er et rent arvehierarki.
En tommelfingerregel i objektorienteret design siger, at nedarvning bruges
ved specialisering, altså når arvingen er en speciel udgave
af overklassen. Det er jo ikke tilfældet her: en måned er ikke
et specialiseret år, og en dato er ikke en specialiseret måned.
Derimod kan man sige, at en dato har såvel et år som
en måned, hvilket bringer os til figurens tredje design. Der er dog
også problemer med dette design, fx at en måneds længde
afhænger af året, hvilket betyder, at en måned må
kende sit år. Man kunne måske forestille sig et design med
multipel nedarvning, hvor en dato arver fra både år
og måned. Dette er imidlertid ikke muligt i Java, så det design,
jeg lander på, ser lidt anderledes ud. Hvorom alting er, så
ser det ud til at være en god ide at udvikle en særlig klasse
for året, så lad os se lidt nærmere på sådan
en.
Denne
figur viser det klassehierarki, jeg er kommet frem til for året.
Øverst ligger en klasse, Year, der skal indeholde de attributter
og metoder, der er fælles for alle år. Klassen er abstrakt
(symboliseret ved den dobbelte indramning), hvilket indebærer, at
man ikke kan generere objekter af klassen vha. new. Man kunne spørge:
"Hvorfor skal man så have den?". Svaret er, at de fælles attributter
og metoder jo så kun behøver at være repræsenteret
ét sted, nemlig her. Det sparer plads, men måske mere væsentligt:
man skal kun rette ét sted, hvis de skal ændres.
Arvingen til Year er en ny abstrakt klasse, som jeg har kaldt LeapyYear. Den indeholder alt, hvad der har med skudår at gøre. Hvorfor nu denne opdeling? Jo, såfremt der findes kalendere uden skudår, så kan de arve direkte fra Year, og behøver altså ikke at slæbe rundt på skudårsdata og -algoritmer. ISO kalenderen har ganske vist lige lange år, altså i princippet ikke skudår, men definitionen af kalenderen opererer alligevel med det sædvanlige gregorianske år, så LeapyYear skal også bruges her. Jeg kender ikke andre kalendere uden skudår, så dette er altså et forsøg på at generalisere programmet ud over de øjeblikkelige behov.
Nederst i hierarkiet finder vi så de konkrete kalenderes år. Jeg har foreløbig implementeret den gregorianske kalender, den julianske kalender og den islamiske kalender (samt ugekalenderen, men den bruger som sagt det gregorianske år).
Klassen Year har to konstruktører, hvilket måske er lidt overraskende, da Year jo er abstrakt. Formålet er, at arvingerne kan bruge disse konstruktører i deres konstruktører. Den første konstruktør er konstruktøren uden parametre, den såkaldte erstatningskonstruktør (eller default constructor på nydansk). Jeg har valgt at lade denne konstruktør skabe et objekt, der repræsenterer det aktuelle år i den gregorianske kalender. Den gregorianske kalender har altså en særstilling i programmet, hvilket også afspejler sig i den absolutte kalender, hvor nulpunktet for dagnummeret er valgt i relation til denne kalender. Det spiller ikke den store rolle i praksis, brugeren mærker det ikke, men det betyder i det konkrete tilfælde, at jeg kan gøre brug af det datoprogram der findes i java.util, når dags dato skal bestemmes.
Den anden konstruktør har en parameter af typen int og genererer et objekt med det år, der er parameter.
Der er to metoder i klassen Year; de returnerer begge værdien af den skjulte attribut, der repræsenterer årstallet for this. Accesmetoden year() returnerer årstallet som et heltal, mens metoden toString() returnerer årstallet som en String.
public class OutOfRangeException extends ArithmeticException { public OutOfRangeException(String s) { super(s); } }Nedenfor er vist den metode i Year, der udfører kontrollen. Som man ser skal årstallet ligge mellem 1 og 100000. De 100000 er valgt så stor, at der ikke er stor risiko for at en bruger løber tør for årstal, men samtidig så lille, at dagnummeret i den absolutte kalender kan repræsenteres vha. et heltal (den 31. december 100000 har dag nr. 36524249 og typen int i Java går til ca. 2 milliarder). Det fremgår også, at årstallet returneres af YearCheck selv om det er forkert. Denne virkning følges generelt i hele programmet - alle kontroller kaster kun en exception uden at gøre forsøg på at rette fejlen. Det er så op til brugerprogrammet at fange fejlen ved hjælp af passende try-konstruktioner, og det er helt op til brugeren, hvad der skal gøres hvis der er fejl.
//Metode til kontrol af Årstallet. protected int YearCheck(int x) { if (x < 1 || x > 100000) throw new OutOfRangeException("årstallet"); return x; }
En af vanskelighederne i nogle kalendere er eksistensen af såkaldte bevægelige helligdage. Det er helligdage, der ikke indtræffer på samme dato hvert år, men flytter sig fra år til år, ofte efter indviklede regler. Den mest interessante bevægelige helligdag i den gregorianske kalender er nok påsken. Derfor har jeg implementeret en metode ved navn easter() i GregorianYear. Den beregner datoen for påskedag i this. Denne dato har stor betydning, da mange helligdage knytter sig til den. Fx ligger pinse præcis 49 dage efter påske, mens fastelavn ligger 49 dage før.
Beregning af datoen for påskedag i den Gregorianske kalender findes fx i: T. H. O'Beirne: Puzzles and Paradoxes, Oxford University Press, London 1965. Reprinted by Dover Publications, New York 1984 side 168 - 184, hvorfra algoritmen i programmet er taget. Algoritmen giver kun det rette svar for årstal efter 1582, da den katolske kirke gik fra den Julianske kalender til den Gregorianske. For at lette jævnførelse med kilden har jeg bibeholdt de variabelnavne og den typografiske opstilling, der er anvendt i denne. Hvis du er interesseret i algoritmiske detaljer, kan du se programteksten ved at klikke på referencen til Year m.v. til slut i dette notat.
Skudårsberegningen i den gregorianske kalender en indviklet affære, men programmet kan gøres relativt kort, se nedenfor. Bemærk den logiske tildelingssætning; den bruges sjældent, jeg synes for sjældent i forhold til dens anvendelsesmuligheder. Måske er det fordi moderne programmører kun tænker i betingelser og ikke i logik.
//Et år y er skudår hvis 400 går op, eller hvis 4 går op //og 100 ikke går op. protected boolean leapYear() { if (y%400 == 0) return true; if (y%4 == 0) return y%100 != 0; return false; }
Det islamiske år adskiller sig fra de to forrige på flere punkter. For det første begynder den islamiske tidsregning først i året 622, fordi Mohammeds flugt til Medina danner starten på den islamiske kalender. Dennne rejse fandt sted fredag den 16. juli 622 i den Julianske kalender. Erstatningskonstruktøren beregner dags dato i den islamiske kalender under hensyn hertil.
Den islamiske kalender er en ren månekalender, hvilket vil sige,
at det islamiske nytår flytter sig i forhold til årstiderne.
Året er på 354 dage, skudår har 355. De 12 måneder
har skiftevis 30 og 29 dage; de ulige måneder har 30 dage mens de
lige har 29. Dog har den tolvte måned 30 dage i skudår, hvilket
indtræffer i år nr. 2, 5, 7, 10, 13, 16, 18, 21, 24, 26 og
29 i hver 30-års periode. Det er vanskeligt umiddelbart at komme
på en matematisk formel, der beregner om et år er skudår,
men det viser sig at de kan findes ved at beregne et udtryk af formen (a*y+b)
mod 30 < c, hvor y er året i en 30 års periode, og a, b
og c er konstanter. Hvis dette udtryk er true, er det skudår (bemærk
at udtrykket må være periodisk med perioden 30). Jeg lavede
et Prologprogram til at finde egnede værdier af a, b og c. Programmet
benytter den velkendte "generate and test" strategi fra kunstig intelligens.
Det viser sig, at (a,b,c) = (11,14,11) virker, men også (a,b,c)
= (19,26,11) er en mulighed i intervallet 0 <= a,b,c < 30.
Her er Prologprogrammet (jeg bringer det ukommenteret, som en lille gåde
til interesserede):
run :- generate(A,B,C),test(A,B,C,1),print(A,B,C),fail. run. generate(A,B,C) :- period(L), member(B,L),member(A,L),member(C,L). period([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, 16,17,18,19,20,21,22,23,24,25,26,27,28,29]). test(_,_,_,30). test(A,B,C,N) :- leapYears(L),member(N,L),X is (N*A+B) mod 30,!, X < C,N1 is N+1,test(A,B,C,N1). test(A,B,C,N) :- X is (N*A+B) mod 30, X >= C,N1 is N+1,test(A,B,C,N1). print(A,B,C) :- write('a = '),write(A),write(', b = '),write(B), write(', c = '),write(C),nl. leapYears([2,5,7,10,13,16,18,21,24,26,29]). member(X,[X|_]). member(X,[_|U]) :- member(X,U).
Skudårsberegningen i den islamiske kalender kan derfor programmeres således:
protected boolean leapYear() { return (11*year() + 14)%30 < 11; }
Erstatningskonstruktøren beregner dags dato ved hjælp af standardklassen Date i java.util. En anden konstruktør har et heltal som parameter og finder dag, måned og år ud fra dette heltal, der forudsættes at være et absolut dagnummer. Denne konstruktør er hjertet i hele systemet. Ved hjælp af den er det let at konvertere en dato i én kalender til en dato i en anden, fx:
Islam = new IslamicDate(Grego.dayno());hvor Islam og Grego er hhv. en islamisk og en gregoriansk dato og dayno() er en metode, der returnerer det skjulte dagnummer.
I Date findes der nogle abstrakte metoder; det er de metoder, der afhænger
af den konkrete kalender, men som klassen Date alligevel refererer til,
eller som skal defineres i alle arvinger. Der er følgende
abstrakte metoder:
void CalendarDate(int n) |
beregner år, måned og dag i this ud fra dagnummeret n |
int daynumber() |
returnerer dagnummeret for this ud fra år måned og dag |
String toString() |
returnerer this som en String |
Date plus(int n) |
returnerer den dato, der ligger n dage efter this |
Date minus(int n) |
returnerer den dato, der ligger n dage før this |
CalendarDate og daynumber er faktisk hjertet i hele systemet; det er i disse metoder de enkelte kalenderes særlige kendetegn kommer til udtryk. Jeg har lavet programmet sådan at disse metoder kun kaldes i konstruktørerne og ikke i de øvrige metoder. Så skal disse forholdsvis tidkrævende processer kun udføres én gang for hver ny dato.
Der findes også nogle konkrete metoder i Date:
int year() |
returnerer årstallet for this |
int month() |
returnerer måneden for this |
int day() |
returnerer dagen for this |
int dayno() |
returnerer dagnummeret for this |
int minus(Date d) |
returnerer antal dage fra this til d incl. |
boolean equals(Date d) |
returnerer true hvis this og d er ens |
boolean earlier(Date d) |
returnerer true hvis this ligger før d |
boolean later(Date d) |
returnerer true hvis this ligger efter d |
Som eksempler på hvor let det er at regne med datoer, når man har den absoluttet kalender, følger her Javateksten for minus og earlier:
public int minus(Date d) { return dayno() - d.dayno(); } public boolean earlier(Date d) { return (dayno() < d.dayno()); }
public interface Month { static final int daysum[] = {0,31,59,90,120,151,181,212,243,273,304,334,365}; static String monthname[] = {"januar","februar","marts","april","maj","juni","juli", "august","september","oktober","november","december"}; int DayCheck(int d); //Kontrol af dagen indenfor en måned int MonthCheck(int m); //Kontrol af måneden int prevdays(int m); //returnerer antal dage forud for måned m int month(); //returnerer måneden int MonthLength(); //returnerer antal dage i måneden String MonthName(); //returnerer månedens navn }Ovenstående programstump viser hvilke data og metoder der er specificeret i grænsefladen for måned. De to arrays refererer specielt til den gregorianske kalender. Arrayet monthname kan evt. redefineres i andre kalendere, der har Month som interface. Arrayet daysum er final, så her er redefinering ikke mulig. Måske burde dette final slettes. Det står der, fordi alle de implementerede kalendere undtagen den islamiske bruger det samme array, og den islamiske kalender har en månedsstruktur, der gør det overflødigt. Da arrays imidlertid - efter hvad jeg ved - er public i et interface, kan final have en beskyttende virkning.
public interface Week { static final String dayname[] = {"søndag","mandag","tirsdag","onsdag","torsdag","fredag", "lørdag"}; int WeekCheck(int x); //Kontrol af ugen int week(); //returnerer ugenummeret String WeekDay(); //returnerer ugedagens navn }Grænsefladen for ugen er ret simpel; ovenstående programstump er vel selvforklarende.
public GregorianDate() //Skaber et objekt for dags dato
//Skaber et objekt med de viste parametre public GregorianDate(GregorianYear y,int m,int d) { my_year = y; //my_year er referencen til året this.m = MonthCheck(m); this.d = DayCheck(d); setDayno(); //Beskyttet metode i Date, der finder dagnummeret } public GregorianDate(int y,int m,int d) { //Kalder forrige konstruktør this(new GregorianYear(y),m,d); }
//Skaber et objekt ud fra en string med formen "å.m.d" public GregorianDate(String date) { parseDate(date); my_year = new GregorianYear(getPart(1)); m = MonthCheck(getPart(2)); d = DayCheck(getPart(3)); setDayno(); }
//Skaber et objekt ud fra et dagnummer GregorianDate(int n) { super(n); }Den næstsidste af disse konstruktører bruger en simpel parser i Date, der finder år, måned og dag ud fra formatet ÅÅÅÅ.MM.DD. Parseren kontrollerer ikke, om der er punktum mellem de enkelte dele af datoen, så man kan også bruge andre skilletegn. Delene omregnes til heltal og gemmes i et skjult array med tre elementer; det er disse elementer, der hentes med metoden getPart(int k).
protected int daynumber() { int m = month() - 1; int y = year() - 1; //daysum findes i interface Month int previous_days = daysum[m]; if (my_year.isLeapYear() && m > 1) previous_days++; return day() + previous_days + 365*y + y/4 - y/100 + y/400; }1. januar år 1 sættes til at være dag nummer 1. Først beregnes antallet af dage i de tidligere måneder af det aktuelle år (previous_days). Dagens nummer kan herefter beregnes som previous_days plus this.day() plus antallet af 365-dages år plus antallet af skudår, som må være antal 4-års perioder minus antal 100-års perioder plus antal 400-års perioder (jfr. skudårsreglerne i den Gregorianske kalender).
Dagnummeret n fortolkes i et talsystem med varierende grundtal, således:
n-1 = 146097*n400 + 36524*n100 + 1461*n4 + 365*n1 + d
(n-1 fordi nummereringen begynder med 1 og ikke med 0).
146097 er antallet af dage i en periode
på 400 år,
36524 er antallet af dage i en periode
på 100 år
1461 er antallet af dage i en periode
på 4 år og
365 er antallet af dage i et år.
De 5 cifrer i tallet [n400,n100,n4,n1,d] betyder derfor hhv.:
n400: antallet af 400-årige
perioder
n100: antallet af 100-årige
perioder
n4 : antallet
af 4-årige perioder, og
n1 : antallet
af enkelte år
d
: antallet af dage i det seneste år
regnet ud fra at 1. januar år 1 har nummer 1. Ved konverteringen
af n til de fire cifre er der to specialtilfælde:
(1) rest = 1460 efter division
med 1461, og
(2) rest = 146096 efter division med
146097.
I det første tilfælde er der tale om 31. december i et
almindeligt skudår, og i andet tilfælde er det 31. december
i et af de skudår, der indtræffer hvert 400 år. I begge
tilfælde er n1 1 for stor. Når man har fundet de fem cifre
i dette mærkelige tal, må det sidste ciffer, d, opdeles i måneder
og dage. Algoritmen ser derfor således ud:
protected void CalendarDate(int n) { int d1,d2,d3,d4,n400,n100,n4,n1; n400 = (n-1)/146097; d1 = (n-1)%146097; n100 = d1/36524; d2 = d1%36524; n4 = d2/1461; d3 = d2%1461; n1 = d3/365; d4 = d3%365; if (d3 == 1460 || d1 == 146096) { n1--; d4 += 365; } my_year = new GregorianYear(400*n400 + 100*n100 + 4*n4 + n1 + 1); m = 12; d4++; while (d4 <= prevdays(m)) m--; d = d4 - prevdays(m); setDayno(n); }Beregningerne for de andre kalendere følger samme melodi, men er noget simplere fordi den gregorianske kalender jo er meget indviklet. Reingold og Dershowitz bruger ikke denne type algoritmer, men anvender en eksperimenterende strategi, hvor man prøver sig frem. Jeg har valgt denne mere "matematiske" tilgang fordi det appellerer mere til mit gemyt, og delvis også fordi algoritmen udføres i "næsten" konstant tid (bortset fra while-sætningen i opdelingen af d), og man efter min mening altid bør vælge sådanne algoritmer, hvis det er muligt. Det er lidt vanskeligt at få alle detaljer på plads, men her har jeg haft megen glæde af et regneark, som er ideelt i sådanne situationer. Jeg lavede simpelt hen et regneark, hvor man i den ene ende indtaster en dato med år, måned og dag, hvorefter dagnummeret beregnes, jfr. daynumber(), og dette dagnummer indgår i de videre beregninger efter ovenstående algoritme for opsplitning i år, måned og dag. Når man indtaster en dato i den ene ende af regnearket, skal den jo genfindes i den anden ende. Hvis dette ikke er tilfældet, har man jo automatisk alle mellemresultaterne til rådighed, og kan forholdsvis let finde fejlen. Jeg har finpudset algoritmerne i alle kalenderne ved hjælp af sådanne regneark, og de har vist sig uhyre nyttige.
Her er en metode fra GregorianDate, der finder ugenummeret. Skønt denne metode findes i GregorianDate, viser den lidt om, hvilken type algoritmer man finder i ISODate.
/* Uge 1 er den uge der indeholder den første torsdag i januar. Det betyder at uge 1 starter med den mandag der falder på eller før 4. januar. Den 1. januar år 1 (dagnummer 1) hænder sig at være en mandag. */ public int weekno() { int n = new GregorianDate(year(),1,4).dayno(); n -= (n-1)%7; //Find første mandags nummer if (dayno() < n) return 52; return (dayno()-n)/7 + 1; };Denne algoritme vil måske forekomme enkelte læsere at være ret kryptisk, og jeg skal ikke nægte, at den var vanskelig at lave. Den første sætning finder den absolutte dato for den 4. januar i det aktuelle år (det år , hvori man ønsker at finde ugenummeret). Den lokale variable n er altså den absolutte dato for den 4. januar. Nu skal vi finde den absolutte dato for nærmest foregående mandag, som må være den første mandag i året. Det sker i anden sætning. Forklaring: Jeg fandt ud af, at absolut 1 var en mandag. Det gjorde jeg ved hjælp af mit regneark, hvor jeg valgte en vilkårlig mandag og fandt resten ved division med 7 (ugens længde) af dens absolutte dato. Denne rest viste sig at være 1. Da absolut 1 også giver rest 1 ved division med 7 følger heraf, at absolut 1 var en mandag. Hvis nu n-1 giver rest 0, må n være en mandag, og der trækkes ikke noget fra n. Hvis n-1 giver rest 1, må n være en tirsdag, og der trækkes 1 fra n, ... o.s.v. Det var anden sætning i algoritmen. Hvis nu den aktuelle absolutte dato (som er værdien af dayno()) er mindre end n, så betyder det, at den aktuelle dag ligger før første mandag i det gregorianske år year(), og følgelig er sidste uge i forrige ISO-år; derfor returneres 52 i dette tilfælde. I modsat fald returneres antallet af mandage fra dayno() til n.
Til slut viser jeg nedenfor nogle af de øvrige metoder i GregorianDate. Tilsvarende metoder findes i de øvrige kalendere. Jeg har indrettet konverteringsmetoderne sådan, at Der findes en konvertering til alle de øvrige kalendere i GregorianDate, mens der kun findes konvertering til GregorianDate i de øvrige kalendere. Det betyder desværre, at man må ændre GregorianDate hver gang man implementerer en ny kalender.
public Date plus(int n) { return new GregorianDate(dayno() + n); } public String toString() { String s = my_year.year() + "." + m + "." + d; return s; } public JulianDate toJulianDate() { return new JulianDate(dayno()); }Hvis du klikker på Start nedenfor, vil et simpelt demonstrationsprogram bliver kørt. Dette program har en brugerflade med nogle menuer, der fører til forskellige dialoger med brugeren, og som (synes jeg) er mere eller mindre selvforklarende. Bemærk dog, at når du bliver spurgt efter datoer, så skal du taste noget der har formen å.m.d, hvor å er årstallet (alle cifre), m er måneden (eller ugen i ISO-datoer) og d er dagen relativt til m (mandag = 1, tirsdag = 2, osv. for ISO-datoer).
Her er referencer til kildeteksten for alle stumper i selve programpakken. Referencerne til demonstrationsprogrammet er ikke med, da jeg synes dette er ret uinteressant i denne sammenhæng.
Klasserne Year, LeapYear, GregorianYear og
JulianYear
Den abstrakte klasse Date samt interfaces
Klassen GregorianDate
Klassen JulianDate
Klassen ISODate
Klasserne IslamicYear og IslamicDate