[Back to DATETIME SWAG index] [Back to Main SWAG index] [Original]

    ======================================
    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:  <adventberechnung>   <-- 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;

[Back to DATETIME SWAG index] [Back to Main SWAG index] [Original]