Die Berechnung des Osterdatums Tobias Barth Wolfgang Erlenkotter Frank Kammer 25. Mai 1998 Inhaltsverzeichnis 1 Einleitung 2 Die Erzeugung des Datentyps Date 1 1 3 Die Ableitung von Enum 3 4 Weitere Funktionen 5 Die Berechnung des Osterdatums 6 Die Programmdateien 4 4 5 2.1 Modizierung fur Ord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Modizierung von Num . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Die Funktion toI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Die Funktion toD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Die Listenfunktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Dates.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Ostern.hs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 3 3 4 5 9 1 Einleitung Die eigentliche Aufgabe bestand lediglich aus der Konstruktion eines Datentyps Date in Haskell. Wenn man aber diese Funktionen geschrieben hat, bieten sich einige weitergehende Datumsberechnungen an: die Bestimmung der Kalenderwoche, des Wochentags und die Berechnung einiger christlicher Feiertage, die sich aus dem Osterdatum ableiten lassen. Dies sei im folgenden dargestellt. 2 Die Erzeugung des Datentyps Date Der Datentyp Date wird in unserem Fall von den Klassen: Eq, das alles? 1 Show, Ord, Num, Enum abgeleitet. Wozu Eq Ord Show Num Enum Damit kann die Gleich- bzw. Ungleichheit festgestellt werden. Ist ein Datum groer oder vielleicht auch kleiner? Ich mochte mein Ergebnis angezeigt bekommen! Hier wird sichergestellt, da ich mit den Datumswerten rechnen kann. Das war schlielich die Aufgabe. Date ist ein Aufzahlungstyp. 2.1 Modizierung fur instance Ord Date compare x y | | | x x x x <= < >= > where toI x == toI y toI x <= toI y otherwise = GT y y y y max x y min x y Ord = = = = | | | | = EQ = LT compare compare compare compare toI x >= toI y otherwise = y toI x <= toI y otherwise = y x x x x y y y y /= == /= == GT LT LT GT = x = x Damit eine Ordnung (Reihenfolge) erkannt werden kann, mu der Typ Date von Ord abgeleitet werden. Dabei kann die Denition aus der Prelude.hs ubernommen werden. Die einzige Aktualisierung ist, da zuerst die Umwandlung des Datumstyps in eine Zahl geschehen mu. 2.2 Modizierung von Num Um Datumswerte zu addieren, subtrahieren bzw. z. B. ein Int auf ein Date zu addieren, ist es auch notwendig, Date von Num abzuleiten und extra Funktionen fur dieses zu schreiben: Die Modikation von Num: instance Num Date where (+) (-) fromInt = addDate -- jetzt kann man Tage addieren! = subDate -- und subtrahieren = toD ... addDate a b =toD ((toI a) + (toI b)) subDate a b = toD (toI a - toI b) ist deshalb notwendig, da ja einer der beiden Summanden vom Typ Integer sein kann. Bei der Addition (Subtraktion) wandelt Hugs dann automatisch den Integerwert in ein Datumswert um. Prima, oder? fromInt 2 3 Die Ableitung von Enum Nachdem nun Date bereits mehrfach uberladen wurde, machen wir es auch noch zur Instanz von Enum, was ja schlielich die Aufgabe war. In Haskell sieht das dann so aus: instance Enum Date where fromEnum = toI toEnum = toD enumFrom = fromD enumFromThen = fromDT fromEnum = toI toEnum = toD enumFrom = fromD enumFromThen = fromDT Wandelt einen Datumswert in einen Integerwert um. Wandelt einen Integerwert in ein Datumswert um. Erzeugt eine Liste, die mit dem angegebenen Datumswert beginnt:[n..] Erzeugt eine Liste, derart: [n,m..] 3.1 Die Funktion toI Die Umwandlung eines Datumswertes in eine Zahl ist nicht ganz so schwierig. Man berechnet die Anzahl der Jahre seit (bzw. bis) 1970, die Anzahl der Monate und der Tage und schon hat man den dem Datumswert entsprechenden Integerwert. Beispiele: Dates> bspTag Date{year=1998, month=5, day=16} Dates> toI bspTag 10362 3.2 Die Funktion toD Das war schlielich das schwierigste. Das Vorgehen hier: 1. Wieviel ganze Jahre "passen in\ die Zahl? 2. Wieviel ganze Monate, und 3. Wieviel Tage bleiben ubrig? Dies leisten im einzelnen die Funktionen: rechneJahr, rechneJahr_, rechneMJahr_, rechneMonat, rechneMonat_, rechneMonatM Die Anwendung: Dates> toD 10364 Date{year=1998, month=5, day=18} Dates> toD (-5000) Date{year=1956, month=4, day=24} 3 3.3 Die Listenfunktionen Hier war es nur wichtig, fromD a = map toEnum [fromEnum a .. ] fromDT a b = map toEnum [fromEnum a, fromEnum b .. ] zu implementieren. Dann kann Hugs bzw. Haskell auch Listen von Datumswerten erzeugen. Dates> [bspTag..] [Date{year=1998, month=5, day=16}, Date{year=1998, month=5, day=17}, Date{year= 1998, month=5, day=18}, Date{year=1998, month=5, day=19}, Date{year=1998, month= 5,,{Interrupted!} Dates> [mbspTag, bspTag..] [Date{year=1950, month=7, day=20}, Date{year=1998, month=5, day=16}, Date{year= 2046, month=3, day=12}, Date{year=2094, month=1, day=6}, Date{year=2141, month= 11, day=3}, Date{year=2189, month=8, day=30}, Date{year={Interrupted!} Damit war die eigentliche Aufgabe fertig. 4 Weitere Funktionen Wenn ersteinmal die Hauptarbeit gemacht wurde, kann mit den Funktionen wTag der Wochentag und mit kw die Kalenderwoche bestimmt werden. Beispiele: Dates> kw (Date 1998 5 21) 21 Dates> wTag (Date 1998 5 21) "Donnerstag" 5 Die Berechnung des Osterdatums Gau entwickelte eine Formel, um das Osterdatum zu berechnen. Dabei nutzt er aus, da Ostern auf den ersten Sonntag nach dem ersten Fruhjahrsvollmond fallt. In Haskell sieht das so aus: -- Der Gausche Osteralgorithmus ostern j = let h = div j 100 l = 4 + h - div h 4 m = 15 + h - div h 4 - div (8*h+13) 25 a = mod j 4 b = mod j 7 c = mod j 19 d = mod (19*c + m) 30 e = mod (2*a+4*b+6*d+l) 7 in if (d==29 && e ==6) then (Date j 4 19) else if (d==28 && e==6 && c>=11) then (Date j 4 18) 4 else if (22+d+e<=31) then (Date j 3 (22+d+e)) else (Date j 4 (d+e-9)) Die restlichen Feiertage berechnen sich dann mit: aschermittwoch j = (ostern j - 46) pfingsten j = (ostern j + 49) christiHim j = (ostern j + 39) fronleichnam j = (ostern j + 60) und eine U bersicht uber all` diese, bekommt man mit: Ostern> feierTage 1998 [(" Aschermittwoch:", Date{year=1998, month=2, day=25}), (" Ostern: ", Date{year =1998, month=4, day=12}), (" Christi Himmelfahrt: ", Date{year=1998, month=5, day =21}), (" Pfingsten: ", Date{year=1998, month=5, day=31}), (" Fronleichnam: ", Date{year=1998, month=6, day=11})] Ostern> 6 Die Programmdateien 6.1 Dates.hs ------------------------------------------------------------------- Die Aufgabe von Blatt 4 -erstellt von -- Wolfgang Erlenkoetter, Tobias Barth und Frank Kammer ----------------------------------------------------------------module Dates ( Date, toD, toI, bspTag, mbspTag, wTag, distDays, kw, fst95, fst98) where data Date = Date {year :: Int, month :: Int, day :: Int} deriving (Eq, Show) instance Ord Date compare x y | | | x x x x <= < >= > y y y y max x y where toI x == toI y toI x <= toI y otherwise = GT = = = = = EQ = LT compare compare compare compare | toI x >= toI y x x x x y y y y /= == /= == = x 5 GT LT LT GT min x y | otherwise = y | toI x <= toI y | otherwise = y = x instance Num Date where (+) = addDate -- jetzt kann man Tage addieren! (-) = subDate -- und subtrahieren fromInt = toD -----------------------------------------------------instance Enum Date where fromEnum = toI toEnum = toD enumFrom = fromD enumFromThen = fromDT -------------------------------------------------------- Umwandlung von einem Datum in eine fortlaufende Zahl -- Diese Funktion berechnet die Anzahl der Tage zwischen -- den beiden Jahren a und b, beide Jahre werden dabei -- inklusive gerechnet. -- a ist also das Startjahr und b das Endjahr anzYDays a b = foldr (+) 0 (map (\x->(foldr (+) 0 (monthLengths x))) [a..b]) -- Die Funktion berechnet die Tage in einem Jahr, gegeben das Jahr, -- der Monat und der Tag. anzDDays j m t = foldr (+) 0 ( take (m-1) (monthLengths j)) + t anzDDays_ j m t = (anzYDays j j)-(anzDDays j m t)+1 ---- Die Funktion toI berechnet nun zu einem geg. Datum den Tag, -- gerechnet ab bzw. bis 1.1.1970, dabei werden die eben definierten -- Funktionen anzDDays und anzYDays benutzt. toI :: Date -> Int toI t = let jahr = year t mon = month(t) tag = day (t) in if jahr > 1969 then (anzYDays 1970 (jahr-1))+(anzDDays jahr mon tag)-1 else - ((anzYDays (jahr+1) 1969)+(anzDDays_ jahr mon tag)) ----------------------------------------------------------------- Die Umwanldung einer fortlaufenden Zahl in ein gueltiges Datum -- erst mal die Hilfe-Funktionen: 6 ----- Die Funktion rechneJahr, rechneJahr_ (ab 1970) und rechneMJahr (bis 1970) berechnen, wie viele Jahre in die gegebene Zahl passen. Dabei wird ein Tupel (wieviel Jahre, RestTage) zurueckgegeben. rechneJahr a = if a> (-1) then if (a < yearLengths 1970) then (1970,a) else rechneJahr_ (a-yearLengths(1970)) 1971 1 else let b = -a in if (b<=yearLengths 1969) then (1969,a) else rechneMJahr_ (b-yearLengths(1969)) 1968 1 rechneMJahr_ a zs ws = if (a <= yearLengths(zs)) then (1969-ws,(-a)) else rechneMJahr_ (a-yearLengths(zs)) (zs-1) (ws+1) rechneJahr_ a zs ws = if (a < yearLengths(zs)) then (1970+ws,a) else rechneJahr_ (a-yearLengths(zs)) (zs+1) (ws+1) -- Analog zu rechneJahr, berechnen die folgenden Funktionen die Anzahl der -- Monate. Rueckgabe ist wiederum ein Tupel, diesmal (Anzahl Monate, Tage) rechneMonat a j [] = rechneMonat_ a j 1 (monthLengths j) rechneMonat_ a j c (x:xs) = if a > (-1) then if (a < x) then (c,a+1) else rechneMonat_ (a-x) j (c+1) xs else let b = (-a) in rechneMonatM b j 1 (reverse (x:xs)) rechneMonatM a j c (x:xs) = if (a <= x) then (13-c, x-a+1) else rechneMonatM (a-x) j (c+1) (xs) -------------------------------- Nachdem die rechne.. Funktionen die Hauptarbeit leisten, ruft toD 7 -- diese geschickt auf. toD :: Int -> Date toD a = let (jahr,b) = rechneJahr a (monat,t) = rechneMonat b jahr [] in Date jahr monat t fromD a = map toEnum [fromEnum a .. ] fromDT a b = map toEnum [fromEnum a, fromEnum b .. ] ----------------------------------------- Die Berechung fuers Schaltjahr sind aus der Calendar.hs genau so -- uebernommen. leap year = if year`mod`100 == 0 then year`mod`400 == 0 else year`mod`4 == 0 -- Informationen ueber die Monate im Jahr: monthLengths year = [31,feb,31,30,31,30,31,31,30,31,30,31] where feb | leap year = 29 | otherwise = 28 -- Die Funktion yearLengths macht einfach ein fold ueber monthLengths -- und kann so ganz einfach die Anzahl der Tage zu einem vorgegeben Jahr -- berechnen. yearLengths year = foldr (+) 0 (monthLengths year) ----------------------------------------------- weitere huebsche Funktionen: --- Der Abstand zwischen zwei Datumswerten: distDays a b = toI b - toI a ---Hier die zwei ultimativen Funktionen: zum Addieren und Subtrahieren -- beliebiger Datumswerte, es ist auch moeglich, zu einem Datum eine Zahl -- zu Zahl (z. B. Tage zu addieren/subtr.) addDate a b =toD ((toI a) + (toI b)) subDate a b = toD (toI a - toI b) 8 --------------------------------------------- wTag liefert zu einem Datum den Wochentag zurueck. -- wTag :: Date -> Int wTag a = wTag_ (wTagI a) wTagI a = 1 + mod ((toI a) + 3 ) 7 wTag_ a | | | | | | | a a a a a a a == == == == == == == 7 1 2 3 4 5 6 = = = = = = = "Sonntag" "Montag" "Dienstag" "Mittwoch" "Donnerstag" "Freitag" "Samstag" ------------------------------------------- Bestimmung der Kalenderwoche fstDay a = Date (year a) 1 1 kw a = let fDay = (fstDay a) -tmp = toI fDay + (wTagI fDay) tmp = toI a - (wTagI a) tagiJ = tmp - (toI fDay) woche = div tagiJ 7 in if (wTagI fDay) < 5 then woche + 2 else if (woche == (-1)) then 53 else woche +1 ------------------------------------------- Hier ein paar Beispiele, damit man die Funktionen testen kann: startTag = Date { year = 1970, month = 1, day =1 } fst69 fst68 fst71 bspTag mbspTag fst98 fst95 = = = = = = = Date Date Date Date Date Date Date {year {year {year {year {year {year {year = = = = = = = 1969, 1968, 1971, 1998, 1950, 1998, 1995, month month month month month month month = = = = = = = 1, 1, 1, 5, 7, 1, 1, day day day day day day day -- 6.2 Ostern.hs -- 9 = = = = = = = 1} 1} 1} 16} 20} 1} 1} module Ostern ( ostern ) where import Dates -- Der Gausche Osteralgorithmus ostern j = let h = div j 100 l = 4 + h - div h 4 m = 15 + h - div h 4 - div (8*h+13) 25 a = mod j 4 b = mod j 7 c = mod j 19 d = mod (19*c + m) 30 e = mod (2*a+4*b+6*d+l) 7 in if (d==29 && e ==6) then (Date j 4 19) else if (d==28 && e==6 && c>=11) then (Date j 4 18) else if (22+d+e<=31) then (Date j 3 (22+d+e)) else (Date j 4 (d+e-9)) aschermittwoch j = (ostern j - 46) pfingsten j = (ostern j + 49) christiHim j = (ostern j + 39) fronleichnam j = (ostern j + 60) feierTage j = let a = b = c = d = e = in [(" (" (" (" (" aschermittwoch j ostern j christiHim j pfingsten j fronleichnam j Aschermittwoch:", a), Ostern: ", b), Christi Himmelfahrt: ", c), Pfingsten: ", d), Fronleichnam: ", e)] -- 10