Contributor: RUDOLF WEEBER


======================================
Datumsarithmetik mit den PC und Pascal
======================================

Dieser Informationstext soll allen Programmierern helfen, die mit
der Berechnung von Tagesdaten oder der Logik der kirchlichen Feier-
tage kaempfen. Diese Text kann frei verteilt werden, solange die
folgenden Informationen nicht daraus entfernt werden.

Copyright (c) 1992 fuer diese Zusammenstellung und den erlaeuternden
Text sowie evtl. erfolgte Korrekturen by Armin Hanisch (2:246/41)
Ich moechte allen danken, die durch ihre e-Mails und sonstige Bei-
traege, insbesondere der Veroeffentlichung von Sources diese Zu-
sammenstellung erst moeglich gemacht haben. Nach Informationsstand
des Autors haben alle Autoren diese Routinen als Public Domain frei-
gegeben. Ich uebernehme allerdings weder dafuer noch fuer die korekte
Berechnung irgendwelcher Daten eine Garantie. Ich habe allerdings
einige Stunden an Testarbeit investiert, die Daten stimmen!

Sources und Alogrithmen von folgenden Personen wurden ausgewertet,
zur Verfuegung gestellt und teilweise korrigert oder an einen fuer
diesen Text einheitlichen Stil bzw. Datentyp angepasst:

Armin Hanisch - Feiertagsberechnungen
Bernd Strehuber - Feiertagsberechnungen
Carley Phillips - Jul. Berechnungen
Jeff Duntemann - Wochentagsberechnung
Judson McClendon - Osterberechnung
Martin Austermeier - Tagesberechnungen
Paul Schlyter - Osterberechnung
Pit Biernath - Jul. Berechnungen
Scott Bussinger - Jul. Berechnungen


OSTERBERECHNUNGEN:
==================

Dieser Algorithmus basiert nicht auf der Berechnung von Gauss und
kommt ohne Ausnahmen aus (lt. Paul Schlyter). Werte ueber 31 be-
zeichnen den Tag im April-31, Werte darunter bezeichnen den Tag
im Maerz.

FUNCTION Easter(year : INTEGER) : INTEGER;
VAR  a, b, c, d, e, f, g, h, i, k, l, m : INTEGER;
BEGIN
   a  :=  year MOD 19;
   b  :=  year DIV 100;
   c  :=  year MOD 100;
   d  :=  b DIV 4;
   e  :=  b MOD 4;
   f  :=  ( b + 8 ) DIV 25;
   g  :=  ( b - f + 1 ) DIV 3;
   h  :=  ( 19 * a + b - d - g + 15 ) MOD 30;
   i  :=  c DIV 4;
   k  :=  c MOD 4;
   l  :=  ( 32 + 2 * e + 2 * i - h - k ) MOD 7;
   m  :=  ( a + 11 * h + 22 * l ) DIV 451;
   Easter :=  h + l - 7 * m + 22;
END{FUNC};


Eine weitere Moeglichkeit, Ostern sehr schnell zu berechnen, besteht
darin, den auf das juedische Passahfest folgenden Sonntag zu berechnen.


Der sog. Passah-Vollmond wird berechnet, in dem das Jahr durch 19 ge-
teilt wird und der Rest mit der folgenden Tabelle verglichen wird:

    0: Apr 14       5: Apr 18      10: Mrz 25      15: Mrz 30
    1: Apr 03       6: Apr 08      11: Apr 13      16: Apr 17
    2: Mrz 23       7: Mrz 28      12: Apr 02      17: Apr 07
    3: Apr 11       8: Apr 16      13: Mrz 22      18: Mrz 27
    4: Mrz 31       9: Apr 05      14: Apr 10

Faellt dieses Datum auf einen Sonntag, ist Ostern der naechste Sonntag!

Beispiel: 1992 MOD 19 = 16, daraus folgt 17.04., der naechste Sonntag
          ist dann der 19. April (Ostersonntag)


FEIERTAGE:
==========

Massgebend fuer die kirchlichen Feiertage ist sowohl das Osterdatum
als auch der 1. Advent, der Beginn des Krichenjahres. Wie man Ostern
berechnet, wurde oben erlaeutert. Hier nun also die Berechnungen der
restlichen Feiertage.

Aschermittwoch:      40 Tage vor dem Ostersonntag,
                     dann zurückgehen bis zum Mittwoch
                     Bsp.:  result := GetOstern;
                            Dec(result,40);
                            WHILE DayOfWeek(result) <> 3 DO
                               Dec(result);

Palmsonntag:         Der Sonntag vor dem Ostersonntag, die Berechnung
                     ist damit trivial.

Weisser Sonntag:     Der Sonnrtag nach Ostern, ebenfalls simpel.

Christi Himmelfahrt: 39 Tage nach dem Ostersonntag oder anders gesagt,
                     der zweite Donnerstag vor Pfingsten.

Pfingsten:           49 Tage nach dem Ostersonntag.

Fronleichnam:        60 Tage nach dem Ostersonntag.

Maria Himmelfahrt:   Fest am 15. August (nicht ueberall Feiertag!)

1. Advent:           Vom 24.12. zurück bis zum nächsten Sonntag,
                     dann noch drei Wochen zurück.
                     Bsp.:  result := MakeDate(24,12,year);
                            WHILE DayOfWeek(result) <> 0 DO
                               Dec(result);
                            Dec(result,21);

Buss- und Bettag:    Der vorvorige Mittwoch vor dem 1. Advent, also
                     vom 1. Advent aus den Mittwoch suchen, dann noch
                     eine Woche zurück.
                     Bsp:     <-- wie oben
                           WHILE DayOfWeek(result) <> 3 DO
                              Dec(result);
                           Dec(result,7);


Hl. drei Köinige:    Fest am 06.01.

Allerheiligen:       Fest am 01.11.

Tag der Arbeit:      Fest am 01.05.

Tag der dt. Einheit: Fest am 03.10. Hier wird im Zuge von Sparmassnahmen
                     für die einzuführende Pflegeversicherung allerdings
                     überlegt, diesen Feiertag immer auf den ersten Sonn-
                     tag im Oktober zu legen, man sollte hier also die
                     politischen Nachrichten verfolgen!


DATUMSARITHMETIK:
=================

Berechnung eines Schaltjahres
-----------------------------

FUNCTION LeapYear(year : WORD) : BOOLEAN;
BEGIN
   LeapYear := ((year MOD 4 = 0) AND (year MOD 100 <> 0))
               OR (year MOD 400 = 0);
END;


Berechnung des Wochentages
--------------------------

FUNCTION DayOfWeek(Day,Month,Year: Integer): INTEGER;
VAR century,yr,dw: Integer;
BEGIN
  IF Month < 3 THEN BEGIN
    Inc(Month,10);
    Dec(Year);
  END{IF} ELSE
    Dec(Month,2);
  century := Year div 100;
  yr := year mod 100;
  dw := (((26*month-2) div 10)+day+yr+(yr div 4)
        +(century div 4)-(2*century)) mod 7;
  IF dw < 1 THEN Inc(dw,7);
  DayOfWeek:=dw;
END{FUNC};

Als Ergebnis erhaelt man den Wochentag in folgender Reiehenfolge:
0=Sonntag, 1=Montag ..... 6=Samstag


Berechnung der Kalenderwoche
----------------------------

Die Woche 1 ist die Woche, die den ersten Donnerstag des Jahres
enthaelt, also mehr als die Haelfte diesem Jahr angehoert.
Ist der 01.01. ein Mo-Mi, dann liegt der 01.01. in der letzten
Woche des vergangenen Jahres. (DIN 1355)

FUNCTION WeekOfYear (Day,Month,Year:WORD) : WORD;
CONST
  table1 : ARRAY [0..6] OF ShortInt = ( -1,  0,  1,  2,  3, -3, -2);
  table2 : ARRAY [0..6] OF ShortInt = ( -4,  2,  1,  0, -1, -2, -3);
VAR
  doy1 ,
  doy2 : INTEGER;
BEGIN
  doy1 := DayofYear (Day,Month,Year) + table1[DayOfWeek (1,1,Year)];
  doy2 := DayofYear (Day,Month,Year) + table2[DayOfWeek(Day,Month,Year)];
  IF doy1 <= 0 THEN WeekOfYear := WeekOfYear(31,12,Year-1)
   ELSE IF doy2 >= DayofYear(31,12,Year) THEN WeekOfYear:=1
     ELSE WeekOfYear := (doy1-1) DIV 7 + 1;
END;


Berechnung der Tage im Monat
----------------------------

FUNCTION DaysInMonth(month,year : WORD) : INTEGER;
VAR ly : BOOLEAN;  { leap year? }
BEGIN
   ly := ((year MOD 4 = 0) AND (year MOD 100 <> 0)) OR (year MOD 400 = 0);
   IF (month IN [04,06,09,11]) THEN  { even month }
     DaysInMonth := 30
   ELSE
     IF month <> 2 THEN  { rest except february }
       DaysInMonth := 31
     ELSE
       IF ly THEN  { leap year? }
         DaysInMonth := 29
       ELSE
         DaysInMonth := 28;
END{FUNC};


Berechnung des Tages im Jahr
----------------------------

Diese Methode gilt fuer alle Jahre ab 1582, der Einführung des
gregorianischen Kalenders.

FUNCTION DayOfYear (day,month,year : WORD) : INTEGER;
VAR
  i, tage : Integer;
BEGIN
  tage := 0;
  FOR i := 1 TO Pred(month) DO Inc (tage, DaysInMonth(i,year));
     Inc (tage,day);
  DayOfYear := tage;
END;

Eine andere Methode kommt ohne die Berechnung der Tage im Monat aus
und bezieht ebenfalls Schaltjahre ein. Der Gültigkeitsbereich dieses
Alogorithmus liegt von 1901 bis 2099.

FUNCTION DayNumber(Day,Month,Year : INTEGER ) : INTEGER;
VAR
  term1 ,
  term2 ,
  term3 : INTEGER;
BEGIN
   term1 := ( 275 * month ) div 9;
   term2 := ( month + 9 ) div 12;
   term3 := ( ( year mod 4 ) + 2 ) div 3;
   DayNumber := term1 - term2 * ( 1 +  term3 ) + day - 30;
END;

Um aus dem Tag im jahr wieder das Datum zu erhalten, kann die folgende
Routine verwendet werden:

FUNCTION YearDayToDMY(GYear,DayNumber : INTEGER; VAR Day,Month,Year : WORD);
CONST
  MonthDays : Array [1..12] of integer=
                    (31,28,31,30,31,30,31,31,30,31,30,31);
VAR
  I    : integer;
  done ,
    ly : boolean;
BEGIN
   I := 1;
   done:=false;
   ly := ((Gyear MOD 4 = 0) AND (Gyear MOD 100 <> 0))
         OR (Gyear MOD 400 = 0);
   IF ly THEN MonthDays[2] := 29; { correct for leap year february }
   REPEAT
      If DayNumber > MonthDays[i] THEN BEGIN
        DayNumber := DayNumber - MonthDays[i];
        Inc(i);
      END{IF} ELSE BEGIN
        year  := GYear;
        month := i;
        day   := DayNumber;
        done  := TRUE;
      END{ELSE};
   UNTIL (i > 12) OR done;
   IF i > 12 THEN BEGIN
     year:=GYear;
     month:=12;
     day:=31;
    END{IF};
END;


Berechnung des julianischen Datums
----------------------------------

Diese Routinen dienen der Umwandlung des Datums in eine serielle
julianische Zahl im Bereich von 01.01.1900 bis zum 31.12.2078,
wobei 0 fuer den 01.01.1900 steht (uebringens: 1900 war kein Schalt-
jahr und der 01.01. war ein Montag).

FUNCTION DateOk(day,month,year : WORD) : BOOLEAN;
VAR
   ly,ok : BOOLEAN;
  maxday : WORD;
BEGIN
  ok := (year >= 1900) AND (year <= 2078);
  ly := ((year MOD 4 = 0) AND (year MOD 100 <> 0)) OR (year MOD 400 = 0);
  IF ok THEN
    ok := (month >= 01) AND (month <= 12);
   IF ok THEN BEGIN
     IF month IN [01,03,05,07,08,10,12] THEN
       maxday := 31
     ELSE
       IF month <> 2 THEN
         maxday := 30
       ELSE
         IF ly THEN
           maxday := 29
         ELSE
           maxday := 28;
     ok := (day >= 01) AND (day <= maxday);
   END{IF};
   DateOK := ok;
END{FUNC};

FUNCTION DMYtoDate(day,month,year : WORD) : WORD;
VAR
  jul : Word;
BEGIN
   IF NOT DateOK(day,month,year) THEN BEGIN
     DMYToDate := $FFFF { signal an invalid date }
   END{IF} ELSE BEGIN  { convert back to DMY }
    IF (Year = 1900) AND (Month < 3) THEN
      IF Month = 1 THEN
        jul := Pred(Day)
      ELSE
        jul := Day + 30
    ELSE BEGIN
      IF Month > 2 THEN
        Dec (Month,3)
      ELSE BEGIN
        Inc (Month,9);
        Dec (Year);
      END{ELSE};
      Dec(year,1900);
      jul := ((1461 * LONGINT(Year)) div 4) +
             ((153 * Month+2) div 5) + Day + 58;
    END{ELSE};
  END{ELSE};
  DMYToDate := jul;
END;

PROCEDURE DateToDMY(jul : WORD; VAR day,month,year: WORD);
VAR
  LongTemp ,
  Temp     : LONGINT;
BEGIN
   IF jul <= 58 THEN BEGIN
     year := 1900;
     IF jul <= 31 THEN BEGIN
       month := 1;
       day := Succ(jul);
     END ELSE BEGIN
       month := 2;
       day := jul - 30;
     END{ELSE}
   END{IF} ELSE BEGIN
     IF jul < $FF63 THEN BEGIN
       LongTemp := (4 * LONGINT(jul-58)) - 1;
       year := LongTemp DIV 1461;
       temp := ((LongTemp MOD 1461) DIV 4) * 5 + 2;
       month := temp DIV 153;
       day := ((temp MOD 153) + 5) DIV 5;
       Inc(year,1900);
       IF month < 10 THEN
         Inc(month,3)
       ELSE BEGIN
         Dec(month,9);
         Inc(year);
       END{ELSE};
     END{IF} ELSE BEGIN  { error in date range }
       year := 0;
       month := 0;
       day := 0;
     END{ELSE};
   END{ELSE};
END;