Et Java-program til kalenderberegninger

Baggrund

Kalenderprogrammet er en bearbejdelse af et program, udviklet af Nachum Dershowitz & Edward M. Reingold: Calendrial Calculations. Software-Practice and Experience, vol 20(9), september 1990. Deres program er skrevet i Lisp. Da jeg gik på pension kendte jeg hverken meget til Lisp eller til Java, og jeg tænkte, at det kunne være en god øvelse, for at lære både Lisp og Java, at læse deres Lisp-program og "oversætte" det til Java. Man kan sige, at Lispprogrammet fungerer som en programspecifikation for Javaprogrammet.

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.

Den absolutte kalender

Den absolutte kalender er meget simpel; den består i at tælle dage. Hver dag får simpelthen et nummer. I de konkrete kalendere hakkes dette nummer op i år, måneder (uger) og dage. Programmets enhed for tid er altså dage, og det kan derfor ikke bruges, hvis man har brug for at regne med timer, minutter, sekunder eller finere tidsenheder. Dagene ligger på en lineær talakse, og spørgsmålet er så, hvor nulpunktet for denne akse befinder sig. Det er i princippet fuldstændig ligegyldigt, hvor man begynder nummereringen af dage, men Dershowitz og Reingold har valgt, at den 1. januar år 1 i den gregorianske kalender skal være dag nummer 1, så det vælger jeg også. Denne dato eksisterer strengt taget ikke i den gregorianske kalender, så man må tænke sig denne kalender ekstrapoleret bagud i tiden fra 1582, årstallet for indførelsen af den gregorianske kalender. For at simplificere opgaven vælger jeg samtidig, at alle dage har et positivt nummer. Man kan altså ikke bruge programmet til beregninger med begivenheder, der ligger før Kristi fødsel.

Omregningen mellem den absolutte kalender og de konkrete kalendere sker ved hjælp af to funktioner, der henholdsvis omregner en konkret dato til et absolut dagnummer, og et absolut dagnummer til en konkret dato. Disse funktioner må nødvendigvis være karakteristiske for hver kalender, og må altså programmeres for hver af de kalendere, man ønsker at implementere. Her har vi altså de første kandidater til metoder i en kalenderklasse, og der antydes et klassehierarki med konkrete kalendere, der arver fra den absolutte kalender. Det er disse metoder, der implementerer de forskellige kalenderes karakteristika; de er kernen i hele systemet.

Strukturen af et objekt i en konkret kalender

Hvordan ser et objekt i en konkret kalender ud? Der er selvfølgelig nogle attributter og nogle metoder. Attributterne er de data, der definerer en konkret dato, altså fx året, måneden og dagen, og metoderne definerer de dataprocesser, man kan anvende på disse data. Her har jeg allerede forudsat, at kalendere altid regner med år, måneder og dage, hvilket måske er lidt optimistisk. Tænk blot på ISO kalenderen, der er nævnt ovenfor. Jeg ved desuden ikke, hvordan fx Mayaindianernes kalender eller Kinesernes kalender ser ud, men jeg satser på at de i hvert fald har en struktur, der bevirker, at en dato kan repræsenteres ved tre hele tal. Jeg kunne bruge et array med tre elementer til at repræsentere selve datoen (og så kunne antallet af elementer til og med være justérbar). Det ville indebære den fordel, at man kunne tælle sig frem gennem en dato. Det har jeg nu ikke gjort, men der er dog tilløb til det i mit program, idet der findes en parser, der kan konvertere en String med format som en dato til tre heltal, der lagres i et array.

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.

Overordnet struktur af et år-objekt


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.

Kontrol

Årstallet kan kun sættes ved hjælp af konstruktørerne. Det har jeg valgt for at have hånd i hanke med kontrollen af, om året kan være korrekt. Hvis man bruger konstruktøren Year(int x), så vil værdien af x blive kontrolleret, og der genereres eventuelt en exception (OutOfRangeException har jeg kaldt den). Alle kontroller i hele programmet anvender denne exception, der er programmeret således:
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;
}

Skudårsklassen

 LeapyYear har konstruktører, der er analoge til Years, men når objekterne dannes, beregnes en skjult boolean, der angiver om this er et skudår. Jeg har valgt at placere skudårsberegningen i konstruktøren og ikke i en speciel metode. Det kan diskuteres, om det er et klogt valg. Man bruger tid på beregningen én gang for hvert nyt objekt, men hvis der havde været tale om en eksplicit metode ville man bruge tiden hver gang man ønsker at undersøge, om this er et skudår. Spørgsmålet er derfor: hvor ofte har man brug for at vide om et år er et skudår? Hvis man ikke undersøger et år for skudår, har man jo spildt beregningen i konstruktøren. Hvorom alting er, så vil en metode med navnet isLeapYear() simpelthen returnere denne skjulte boolean, mens metoden leapYear() er den (skjulte) metode, der udfører selve skudårsberegningen, og som kaldes i konstruktøren. Det er klart at denne sidste metode skal være abstrakt, da skudårsreglerne er forskellige for de forskellige kalendere.

Det gregorianske år

 GregorienYear arver fra LeapyYear, der igen arver fra Year. Derfor findes erstatningskonstruktøren allerede. Konstruktøren med årstallet som parameter må derimod angives eksplicit, sådan er reglerne for Java. Så skal man selvfølgelig afsløre skudårsreglen i leapYear(), svarende til den abstrakte metode i LeapyYear.

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 julianske og islamiske år

Det julianske år er helt analogt til det gregorianske, blot er beregningen af skudår og påskedag lidt anderledes. Den påskealgoritme, jeg benytter, skyldes Gauss, og jeg har fundet den i Den Store Danske Encyklopædi under opslaget påske.

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;
}

Struktur af datoklasserne

Min programstruktur for datoerne ser lidt kompliceret ud. Jeg kom tidligere frem til en omtrentlig struktur baseret på, at år og måned var selvstændige klasser, som en dato refererede til. Ved nærmere eftertanke forlod jeg imidlertid denne struktur. Jeg bibeholdt året som en klasse men skippede måneden. Grunden er, at der findes en mulighed for pseudonedarvning i Java, nemlig interface. Dels ville jeg gerne afprøve denne mulighed, og dels forekommer det mig at være naturligt, at en dato har en grænseflade til en måned. Resultatet blev som vist på hosstående figur, hvor de fede pile betyder ægte nedarvning, mens de tynde pile betyder "nedarvning" gennem et interface. Jeg har implementeret fire kalendere: den gregorianske kalender, den julianske kalender, den islamiske kalender og ugekalenderen. Ugekalenderen opfatter jeg som en specialisering af den gregorianske kalender. I de tre første kalendere identificeres en dato ved {år,måned,dag} og i ugekalenderen består en dato af {år,uge,ugedag}, men måneden eksisterer alligevel i baggrunden. Kalenderne er så tilpas forskellige, at jeg tror på, at en udvidelse med yderligere kalendere, såsom den hebraiske, den kinesiske eller Mayaindianernes kalender, vil kunne lade sig gøre uden ændring af den overordnede struktur.

Klassen Date

Den abstrakte klasse Date er tænkt som en fællesnævner for datoer i alle kalendere. Dog er den gregorianske kalender et særtilfælde, idet erstatningskonstruktøren (uden parametre) skaber et objekt der repræsenterer dags dato i denne kalender. En dato tænkes at være repræsenteret ved en tredelt størrelse. Jeg kalder de tre dele for år, måned og dag. Den vigtigste attribut i Date er heltallet dnr, der er dagens nummer i den absolutte kalender, men også Year my_year, int m og int d er attributter. Der regnes ikke med negative dagnumre, så systemet kan ikke bruges for datoer før Kristi  fødsel.

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()); }

Grænsefladen for måned

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.

Grænsefladen for uge

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.

Den gregorianske kalender

Der er fem konstruktører i den gregorianske kalender, se nedenfor. De samme konstruktører går igen i de øvrige kalendere. Jeg har angivet kroppene for flertallet af konstruktørerne for at vise princippet.
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).

Beregning af den absolutte dato ud fra år, måned, dag

Metoden setDayno(), der kaldes i konstruktør nummer to, befinder sig i Date og kalder den abstrakte metode daynumber(), der er kalenderafhængig og altså skal defineres i GregorianDate (samt i de øvrige kalendere i systemet). Lad os se hvordan denne metode tager sig ud i den gregorianske kalender.
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).

Beregning af år, måned, dag ud fra den absolutte dato

Den sidste datokonstruktør i GregorianDate finder år, måned og dag vha. den abstrakte metode CalendarDate, der kaldes fra Date. Lad os se nærmere på denne centrale metode.

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