Ermittlung
der Kalenderwoche
Ermittlung des Osterdatums
Tagesdifferenz ermitteln
Wochentagsermittlung für den
julianischen Kalender
Verbesserungen an den
Funktionen
Ermittlung des Wochentages von
historischen Daten
Es fing damit an, dass
ich unter Windows in der Eingabeaufforderung ein Utility ähnlich
wie unter Linux
(Unix) Kommando "cal" vermisste.
Es gibt zwar ein Utility namens
"dcal", was genau das gleiche unter Windows kann, aber die Ausgabe
lässt
zu wünschen übrig:
C:\> dcal 3 2008
M∑rz 2008
So Mo Di Mi Do Fr
Sa
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
30 31
Der Umlaut 'ä' im Monat
März (und Jänner) wird als Summenzeichen dargestellt und die
Spalten sind nicht
optimal formatiert. Außerdem wäre es nicht schlecht, den
Kalender auf Wunsch
auch mit Montag beginnen zu lassen. Da hilft nur eines: selbst
programmieren!
Dazu benötigt man aber eine Funktion, die den Wochentag ermitteln
kann.
Anfang der 80er Jahre gab
es im ORF eine Sendung namens "Computerbox" und es wurde so ein
Programm in Basic für den Commodore C64 vorgestellt.
Dieses habe
ich später
dann in Pascal konvertiert und jetzt in C:
// 1 = Sonntag, 2 = Montag, ... , 7 = Samstag
int dayofweek(int d,int m,int y)Die Funktion erfüllt zwar ihren Zweck, aber sie ist ziemlich kompliziert und undurchsichtig. Es muss also noch einfacher gehen und eine reine Integer-Arithmetik ausreichen. In der Newsgroup "de.comp.lang.c" habe ich meinen Wunsch geäußert und tatsächlich eine Antwort erhalten. Eine Datumsplausibilitätsprüfung wurde noch ergänzt und das ganze sieht dann so aus:
// gibt Wochentag zurückZur Info: Nicht alle
Länder haben 1582 den Gregoranischen Kalender eingeführt.
England und seine
Kolonien, somit auch Nordamerika haben erst 1752 umgestellt.
( Details siehe Wikipedia:
http://de.wikipedia.org/wiki/Gregorianischer_Kalender
)
Noch kurz zur Schaltjahresregel: Ein Jahr ist ein
Schaltjahr, wenn es ohne Rest durch 4 teilbar ist. Somit sind z. B. die
Jahre 1992, 1996, 2004 und 2008 Schaltjahre. Aber es gibt eine
Ausnahme: Ist ein Jahr durch 100 ohne Rest teilbar, dann ist es kein
Schaltjahr, es sei denn es ist durch 400 ohne Rest teilbar. Somit waren
1700, 1800 und 1900 keine Schaltjahre und 2100 wird ebenfalls keines
sein; wohl aber 1600 und 2000.
Dadurch gibt es im Gregorianischem Kalender gibt es einen 400 Jahres-Rhythmus:
400 Jahre * 365 Tage sind 146000 Tage. Dazu noch 97 Schalttage (100 weniger 3) ergibt 146097 Tage. Das entspricht genau 20871 Wochen. Somit fallen sowohl der 1.1.1601 als auch der 1.1.2001 auf einen Montag! Und der 1.1.2401 wird ebenfalls wieder ein Montag sein.
Mit dieser Info und ein bisschen Denksport sollte die Funktionsweise der Berechnung durchschaubar werden. Nach der Plausibilitätsprüfung des Datums wird der "Jahresanfang" offensichtlich auf 1. März verschoben (exakt "0. März", weil ja der Tag noch addiert wird). Dadurch braucht man nur den Wochentag des "0. März" ermitteln und kann davon den Rest des Jahres berechnen ohne darauf Rücksicht nehmen zu müssen, ob es ein Schaltjahr ist oder nicht. Somit ergibt sich:
if (m < 3) { m += 13; y--; } else m++;
1 2008 =
14 2007
2
2008
= 15 2007
3
2008
= 4
2008
4
2008
= 5
2008
…
11 2008
= 12 2008
12
2008
= 13 2008
Jetzt wird es
interessant: 26 * m / 10
(Man kann diese Berechnung auch auf 13 * m / 5 kürzen – funktioniert genauso.)
Tja, was macht diese
Berechnung? Man beachte, dass es reine Integer-Arithmetik ist.
Beginnen
wir mit
März, weil der Jahresanfang auf den "0. März" verschoben
wurde:
Monat |
m |
*26 |
/10 |
Differenz zum nächsten Monat |
März |
4 |
104 |
10 |
3 |
April |
5 |
130 |
13 |
2 |
Mai |
6 |
156 |
15 |
3 |
Juni |
7 |
182 |
18 |
2 |
Juli |
8 |
208 |
20 |
3 |
August | 9 |
234 |
23 |
3 |
September |
10 |
260 |
26 |
2 |
Oktober |
11 |
286 |
28 |
3 |
November |
12 |
312 |
31 |
2 |
Dezember |
13 |
338 |
33 |
3 |
Jänner (Januar) |
14 |
364 |
36 |
3 |
Februar |
15 |
390 |
39 |
- |
Interessant ist hierbei
die Differenz zwischen zwei benachbarten Monaten: Hat ein Monat 30 Tage
ergibt sich
eine 2,
hat es dagegen 31 Tage dann eine 3. Hier ist wieder gut, dass der
Februar das
letzte Monat ist. 28 Tage sind exakt 4 Wochen; die 2 und die 3 sind die
Differenz zu 30 und 31 Tagen. Somit wird geschickt der Bezug des
Wochentages
zum imaginären Jahresbeginn hergestellt. Somit ist die Formel klar:
s = d + 26* m/10 + y + y/4 - y/100 + y/400 + 6;
Addiere also den Tag (d) plus den Versatz zum Jahresanfang (26*m/10) plus das Jahr (y) plus die Schaltjahreskorrektur (y/4 – y/100 + y/400) plus einen Korrekturwert (6 wenn Sonntag der erste Tag der Woche sein soll).
return(s % 7 + 1);
Das ganze wird noch durch 7 geteilt und hier ist der Divisionsrest ausschlaggebend, weil sich der Wochentagsrhythmus ja in 7 Tagen wiederholt. Das heißt mathematisch "modulo". In C steht dafür das % - Zeichen; in anderen Programmiersprachen z.B. die Funktion MOD. Somit ergibt sich eine 0 für Sonntag, eine 1 für Montag usw.
Wenn man noch eine 1 dazuzählt, dann erleichtert man sich den Umgang mit dem Ergebnis dieser Funktion und man kann die 0 dazu verwenden, um ein ungültiges Datum anzuzeigen. Die Plausibilitätsprüfung des Datums muss vor der Berechnung erfolgen, weil die eigentliche Berechnungsroutine alle Werte "schluckt" und die Modulo-Funktion mit 7 immer nur Zahlen von 0 bis 6 ausgeben kann.
Die neue unkomplizierte Funktion ist ziemlich genau 3x so schnell die alte komplizierte. In der Praxis fällt der Unterschied kaum auf; zum Test habe ich den 13. eines jeden Monats von 1601 bis 2000 berechnet und das ganze 32000 (!) mal wiederholt. Dennoch dauerten diese fast 154 Millionen Berechnungen auf einem aktuellen PC erwartungsgemäß deutlich weniger als eine Minute.
Wenn man genau hinsieht, dann macht die alte Version im Prinzip auch dasselbe; es ist alles nur komplizierter formuliert: (auszugsweise)
z=(0.6+(1.0/m)); k=(int) z;
k wird bei Jänner und Februar 1; ab März 0. -> verschiebe also den Jahresanfang
z=(13.0*(o+1)/5.0); z4=(int) z;
ist im Prinzip 26
* m /
10 gekürzt auf 13 * m / 5
Nachdem wir jetzt eine Funktion haben, welche den Wochentag ermittelt kann das Kalenderprogramm entworfen werden. Eine Möglichkeit ist: Frage die Wochentage aller Tage von 1 bis 31 ab und warte bis ein ungültiges Datum zurückgemeldet wird. Somit ist auch klar erkennbar ob der Februar 28 oder 29 Tage hat.
Hier die Kernfunktion des Programmes:#include <stdio.h>
main()
{
int
i,j,s,t=0,monat,jahr;
monat=2;
// gewünschtes Monat
jahr=2008;
// gewünsches Jahr
printf("\n\n So
Mo Di Mi Do Fr
Sa\n");
for
(i=1; i<32; i++)
{
s=dayofweek(i,monat,jahr);
if
(s)
// wenn Datum
gültig ist
{
if
(t==0)
// ersten Tag des Monats der
richtigen Spalte zuordnen
{
printf(" ");
for (j=1;
j<s;
j++)
printf(" ");
t=1;
}
printf(" %2d ",i);
if (s==7) printf("\n "); //
wenn
Samstag erreicht dann eine neue Zeile beginnen
}
}
printf("\n");
}
Man kann jetzt auch noch
die Frage beantworten, wie viele Freitage tatsächlich auf einen
13. fallen.
Die
Berechnung eines 400-Jahres-Rhythmuses von 1601 bis 2000 ergibt
folgendes
Ergebnis:
687 Tage fallen auf einen Sonntag - das
sind 14.31250 Prozent
685 Tage fallen auf einen Montag - das
sind 14.27083 Prozent
685 Tage fallen auf einen Dienstag - das
sind 14.27083 Prozent
687 Tage fallen auf einen Mittwoch - das
sind 14.31250 Prozent
684 Tage fallen auf einen Donnerstag -
das sind 14.25000 Prozent
688 Tage fallen auf einen Freitag - das
sind 14.33333 Prozent
684 Tage fallen auf einen Samstag - das
sind 14.25000 Prozent
Tatsächlich ist der
Freitag der 13. überdurchschnittlich oft vertreten, obwohl es von
Jahr zu Jahr
starke Schwankungen gibt! Aber ehrlich gesagt, in der Praxis fällt
das nicht ins Gewicht.
An und für sich würde
sich ja alle 28 Jahre (= das kleinste gemeinsame Vielfache von 7
Wochentagen und 4 Jahren [3 "normale" Jahre und 1 Schaltjahr]) der
Kalender wiederholen, wenn nicht durch den Ausfall von 3 Schalttagen in
400 Jahren dieser Zyklus gestört werden würde. Zum Beispiel
sind die Jahre 1901, 1929, 1957, 1985 und (2000 ist ein Schaltjahr
daher auch) 2013, 2041, 2069 und 2097 (hier Ende weil 2100 kein
Schaltjahr mehr ist) bzgl. der Wochentage identisch. Daher ist
innerhalb einer solchen "kleinen Periode" wie z.B. von 1901 - 1928 (28
Jahre) und von 1901 - 2096 (7*28 = 196 Jahre) jeder Wochentag exakt
gleich - also zu 14,28571 % (exakt 1/7) vertreten!
Ermittlung der
Kalenderwoche
Noch ein Anwendungsbeispiel ist die
Ermittlung der Kalenderwoche. Zuerst einmal zu der Definition der
Kalenderwoche, wie sie im deutschsprachigem Raum ermittelt wird:
(heißt exakt "Kalenderwoche nach DIN 1355 / ISO 8601")
- jeden Montag und nur montags
beginnt eine neue Kalenderwoche
- die erste Kalenderwoche ist diejenige, die mind. 4 Tage des neuen
Jahres enthält (d.h. muss der erste Donnerstag im Jahr in der
ersten Kalenderwoche liegen)
- der 29., 30. und 31. Dezember können schon zur ersten
Kalenderwoche des Folgejahres gehören
- der 1., 2. und 3. Jänner können noch in der letzten
Kalenderwoche des Vorjahres liegen
- jedes Jahr hat daher entweder 52 oder 53 vollständige
Kalenderwochen zu genau 7 Tagen
- ein Jahr hat genau dann 53 Kalenderwochen, wenn es mit einem
Donnerstag beginnt oder endet
- ist es ein Schaltjahr und beginnt mit einem Mittwoch, dann hat
es auch 53 Kalenderwochen (war z. B. 1992 der Fall)
(wenn ein Schaltjahr mit Mittwoch beginnt, dann endet es an einem
Donnerstag)
So, jetzt erstellen wir einen
Algorithmus zur Ermittlung der Kalenderwoche. Wir benötigen neben
der Funktion "dayofweek" zur Ermittlung des Wochentages, die wir schon
kennen, noch die Funktion "dayofyear" zur Ermittlung der fortlaufenden
Nummer des Tages im Jahr. Diese ist relativ einfach zu programmieren:
int dayofyear(int d, int m, int
y)
// Tag, Monat, Jahr; Jahr unbedingt vierstellig
{
int t=0,s=0,
mtag[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
// Anzahl der Tage pro
Monat
if ((y % 4==0 && y
% 100!=0) || y % 400==0)
s=1;
// Schaltjahrescheck
if (y<1583 ||
y>9999)
return(-1);
// Jahr auf sinnvollen Bereich
einschränken
if (m<1 || m>12)
return(0);
// Monat auf Gültigkeit
prüfen
if (s)
mtag[2]=29;
// Bei einem Schaltjahr hat der Februar 29
Tage
if (d<1 ||
d>mtag[m])
return(0);
// Tag auf Gültigkeit
prüfen
do
{
m--;
t+=mtag[m];
// Tage der Vormonate in einer Schleife
addieren
} while (m>1);
return(t+d);
// Ergebnis = Tage aller Vormonate + Tag des aktuellen Monats
}
So und hier ist die eigentliche Funktion zur Ermittlung der
Kalenderwoche:
int
weekofyear(int d, int m, int
y)
// Tag, Monat, Jahr; Jahr unbedingt vierstellig
{
int r=0,s,t,w,t1,w1,
mtag[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; // Anzahl der
Tage pro
Monat
if ((y % 4==0 && y
% 100!=0) || y % 400==0)
r=1;
//
Schaltjahrescheck
if (y<1583 ||
y>9999)
return(-1);
// Jahr auf sinnvollen Bereich
einschränken
if (m<1 || m>12)
return(0);
// Monat auf Gültigkeit
prüfen
if (d<1 ||
d>mtag[m]+r*(m==2))
return(0); // Tag
auf Gültigkeit
prüfen
t=dayofyear(d,m,y);
// laufenden Tag des Jahres
ermitteln
w=dayofweek(1,1,y)-2;
// Wochentag des 1. Jänners (1. Januars) ermitteln und
anpassen
if (w>3)
w=w-7;
// Wochentag
anpassen
s=((t-1+w)/7)+1;
// Kalenderwoche
ermitteln
if
(s==1)
// wenn KW 1 prüfen ob nicht doch letzte KW vom
Vorjahr
{
w=w*(-1);
if (w>=d)
{
t1=dayofyear(31,12,y-1);
// KW vom 31.12. des Vorjahres
ermitteln
w1=dayofweek(1,1,y-1)-2;
if
(w1>3) w1=w1-7;
s=((t1-1+w1)/7)+1;
}
}
else
{
// wenn KW 53 erreicht prüfen ob nicht doch KW 1 vom nächsten
Jahr
if (s==53
&& w==2 &&
r==1)
// Jahr ist Schaltjahr
und beginnt mit Mittwoch, dann passt KW 53
s=53;
else
if
(s==53 && w!=3)
s=1;
// Jahr beginnt nicht mit Donnerstag, dann KW 1 nächstes
Jahr
}
return(s);
}
Erklärung des Algorithmusses:
Sollte sich eine 53. Kalenderwoche ergeben, dann
wird geprüft, ob die Bedingungen erfüllt sind. Entweder das
Jahr beginnt mit einem Mittwoch und ist ein Schaltjahr (wie z.B. 1992)
dann gibt es eine 53. KW oder das Jahr beginnt mit einem Donnerstag,
ungeachtet dessen ob es ein Schaltjahr ist oder nicht. Ansonsten gibt
es keine KW 53 sondern der Tag gehört schon zur KW 1 des folgenden
Jahres.
Um den Kalender noch
vollständig zu machen, müssen auch die beweglichen Feiertage
ermittelt werden. Diese basieren auf dem Osterdatum. Dafür gibt es
schon einen Algorithmus:
Feiertag |
Offset
zum Ostersonntag |
Rosenmontag |
-48 Tage |
Faschingdienstag |
-47 Tage |
Aschermittwoch |
-46 Tage |
Gründonnerstag |
-3 Tage |
Karfreitag |
-2 Tage |
Ostermontag |
+1 Tag |
Christi Himmelfahrt |
+39 Tage |
Pfingstsonntag |
+49 Tage |
Pfingstmontag |
+50 Tage |
Fronleichnam |
+60 Tage |
iDay=eastersunday(iYear); // Ostersonntag ermitteln
s=0;
if ((iYear %
4==0 && iYear % 100!=0) ||
iYear % 400==0) s=1; // Schaltjahr ermitteln
//
Aschermittwoch = 46 Tage vor Ostersonntag
iMonth=3;
iDay-=46; // bzw. 47 Tage für
Faschingdienstag, 48 Tage für Rosenmontag
if (iDay<1)
{iMonth--; iDay=iDay+28+s;}
Hier ist auch eine Funktion, welche bis zum Jahr
1582 den Julianischen Ostersonntag ermittelt:
int eastersunday(int y)
{
int a,b,c,d,e,i,j,k,p,q,m,n,s;
if (y<1 || y>10000) return(0);
a=y % 19;
if (y<1583) // Julianischer Kalender
{
i=(19*a + 15) % 30;
j=(y + y/4 + i) % 7;
return(i-j+28);
}
// Gregorianischer Kalender
b=y % 4;
c=y % 7;
k=y / 100;
p=(8*k + 13) / 25;
q=k / 4;
m=(15+k-p-q) % 30;
n=(4+k-q) % 7;
d=(19*a + m) % 30;
e=(2*b + 4*c + 6*d + n) % 7;
s=22+d+e;
if (s==57) s=50;
if (d==28 && e==6 && (11*m + 11) % 30 < 19)
s=49;
return(s);
}
Jean Meeus hat in seinem Buch folgende Formel
veröffentlicht:
int eastersunday(int y)
{
int a,b,c,d,e,f,g,h,i,k,l,m,q,r,s,x,z;
z = y % 19;
if (y<1583) // Julianischer Kalender
{
a = y % 4;
b = y % 7;
d = (19*z + 15) % 30;
e = (2*a + 4*b - d + 34) % 7;
q = d + e + 114;
}
else
{
// Gregorianischer Kalender
b = y / 100;
c = y % 100;
d = b / 4;
e = b % 4;
f = (b + 8) / 25;
g = (b - f + 1) / 3;
h = (19*z + b - d - g + 15) % 30;
i = c / 4;
k = c % 4;
l = (32 + 2*e + 2*i - h - k) % 7;
m = (z + 11*h + 22*l) / 451;
q = h + l - 7*m + 114;
}
r = q / 31;
s = q % 31;
x = (r - 3)*31 + s + 1;
return(x);
}
Während man das Jahr durchaus
noch mit zwei Kommastellen ausgeben kann, ist beim Monat wegen der
starken Schwankungen nur eine Kommastelle empfehlenswert.
Auch für den Julianischen Kalender existiert ein Algorithmus, mit
dem man den Wochentag ermitteln kann:
// gibt
Wochentag
zurück
// 1 = Sonntag, 2 = Montag,
... , 7 =
Samstag
// 0 = Datum ungültig
z.B. 29.2.2007,
32.12.2007
// Julianischer Kalender (auch ab
1583!)
int dayofweekjul(int d, int m,
int y)
{
int s=0,
mtag[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
if (y<100 ||
y>10000) return(-1);
if (m<1 || m>12)
return(0);
if (y % 4==0) s=1;
if (d<1 ||
d>mtag[m]+s*(m==2)) return(0);
if (m < 3) { m += 13;
y--; } else m++;
s = d + 26*m/10 + y + y/4
+ 4;
return(s % 7 + 1);
}
Es ist im Prinzip die gleiche Formel, nur dass
aufgrund der einfacheren Schaltjahresregelung diese etwas kürzer
ist.
Beides kombiniert sieht dann so aus:
(bis 4. 10. 1582 julianischer Kalender, danach gregorianischer Kalender
- Vorsicht, gilt nicht für alle Länder!)
int
dayofweekjulandgreg(int
d, int m, int y)
{
int s=0, greg=0,
mtag[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
if (y<100 || y>10000) return(-1);
if (m<1 || m>12) return(0);
if (y==1582 && m==10 && d>4 &&
d<15) return(0); // verbotener (nicht existenter) Bereich
5.10.-14.10.1582
if (y>1582 || (y==1582 && m>10) || (y==1582
&& m==10 && d>14)) greg=1;
if (greg) // gregorianisch
{
if ((y % 4==0 && y % 100!=0) || y % 400==0)
s=1;
if (d<1 || d>mtag[m]+s*(m==2)) return(0);
if (m < 3) { m += 13; y--; } else m++;
s = d + 26*m/10 + y + y/4 - y/100 + y/400 + 6;
}
else // julianisch
{
if (y % 4==0) s=1;
if (d<1 || d>mtag[m]+s*(m==2)) return(0);
if (m < 3) { m += 13; y--; } else m++;
s = d + 26*m/10 + y + y/4 + 4;
}
return(s % 7 + 1);
}
Der Oktober 1582 sieht dann so aus:
Mo Di
Mi Do Fr Sa So
1 2
3 4 15 16 17
18 19 20
21 22 23 24
25 26 27
28 29 30 31
Noch ein Hinweis:
Ich wurde auch schon wegen dieser Formulierung
gefragt:
if (d<1 ||
d>mtag[m]+s*(m==2)) return(0);
Natürlich könnte man diese abändern
in (und die Variable s einsparen)
if ((y % 4==0 && y %
100!=0) || y % 400==0) mtag[2]=29;
if (d<1 || d>mtag[m]) return(0);
Diese Methode ist für C-Einsteiger
wahrscheinlich am leichtesten nachvollziehbar.
Es gibt halt immer mehrere Möglichkeiten ein
Ziel zu erreichen.
Der Ausdruck s*(m==2) ist
immer dann 1 (und symbolisiert den Schalttag im Februar) wenn
ein Schaltjahr vorliegt (s also 1 ist) UND m 2 ist (Februar), dann
ergibt m==2 (man beachte das doppelte =) den Wert 1, weil die Bedingung
erfüllt, also wahr ist.
Sonst ist immer mindestens einer der beiden
Multiplikatoren 0 und damit das Ergebnis ebenso 0 und hat dann keine
Auswirkung auf mtag[m].
Und hier die Luxusversion:
int
dayofweekjulandgreg(int d, int m, int y, int
*sch, int *greg)
{
int mtag[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
/* nur wenn
automatisch korrigiert werden
soll, falls Tag und Jahr vertauscht sind
int c;
if (d>y) {c=d; d=y; y=c;}
*/
*sch=0; *greg=0;
if (y<100 ||
y>10000) return(-1);
if (y==1582 &&
m==10 && d>4 &&
d<15) {*greg=-1; return(0);} // verbotener (nicht existenter)
Bereich 5.10.-14.10.1582
if (y>1582 || (y==1582
&& m>10) || (y==1582
&& m==10 && d>14)) *greg=1;
if (*greg) //
gregorianisch
{
if ((y % 4==0
&& y % 100!=0) || y % 400==0)
{*sch=1; mtag[2]=29;}
if (m<1 ||
m>12) return(0);
if (d<1 ||
d>mtag[m]) return(0);
if (m < 3)
{ m += 13; y--; } else m++;
s = d +
26*m/10 + y + y/4 - y/100 + y/400 + 6;
}
else // julianisch
{
if (y % 4==0)
{*sch=1; mtag[2]=29;}
if (m<1 ||
m>12) return(0);
if (d<1 ||
d>mtag[m]) return(0);
if (m < 3)
{ m += 13; y--; } else m++;
s = d +
26*m/10 + y + y/4 + 4;
}
return(s % 7 + 1);
}
Aufruf mit
wo=dayofweekjulandgreg(tag, monat, jahr, &sch,
&greg); // alle Variablen Integer
Ergebnis:
wo: 1 = Sonntag, 2 = Montag, ...
, 7 = Samstag, 0 = ungültiger Tag, -1 = Jahr außerhalb des
Bereiches
sch: 1 = Schaltjahr, 0 = kein Schaltjahr
greg: 1 = gregorianisch, 0 = julianisch, -1 = verbotener
Bereich
Es wergen Zeiger verwendet, weil eine Funktion in
C nur einen Wert zurückgeben kann.
Man beachte auch, dass zuerst auf das Schaltjahr geprüft wird und
dann erst das Monat, damit auch bei einem ungültigem Monat
wenigstens zurückgegeben wird, ob das Jahr ein Schaltjahr ist.
Folgende Erweiterung kann man noch einbauen wenn
man möchte:
(gilt auch für alle vorhergehenden Funktionen)
int c;
if (d>y) {c=d; d=y; y=c;}
In diesem Fall ist es egal, ob man dayofweek(tag,
monat, jahr); oder dayofweek(jahr, monat, tag); schreibt. Man braucht
dann beim Programmieren nicht daran denken ob man den Tag oder das Jahr
zuerst aufruft. Das Monat ist sowieso in der Mitte. Der Wert für
den Tag ist sowieso kleiner als der Wert für das Jahr, das mind.
dreistellig sein sollte; also 100 - 9999. Ist der Wert für den Tag
plötzlich größer als der Wert für das Jahr, dann
sind beide offensichtlich vertauscht worden und es werden die beiden
Werte wieder zurückgetauscht.
Da ein direkter Tausch nicht möglich ist, benötigt man die Variable c als Zwischenspeicher. Zuerst wird der Wert von d in c abgelegt und d übernimmt dann direkt den Wert von y. Dann kann y den Wert von c und somit den ursprünglichen Wert von d übernehmen.
Der erste Schritt war hier, die
Anzahl der Tage eines Monats nicht mehr aus einer Tabelle zu entnehmen,
sondern zu berechnen. Bis Juli sind alle ungeraden Monate lang und alle
geraden Monate kurz, ab August ist es umgekehrt. Der Februar braucht
sowieso eine eigene Behandlung mit Schaltjahresermittlung.
if (m <
3)
{ m += 13; y--; } else m++;
if (m++ < 3) {m += 12; y--;} // gekürzte Version
ist sowohl bei Julianisch als auch Gregorianisch
gleich und kann außerdem noch gekürzt werden.
Weiters wird zuerst der Wochentag so berechnet, wie wenn es nur einen
Julianischen Kalender gäbe und dann erst auf Gregorianisch
angepasst:
if (greg)
s = d +
26*m/10 + y + y/4 - y/100 + y/400 + 6;
else
s = d +
26*m/10 + y + y/4 + 4;
wird zu
s = d +
26*m/10 + y + y/4 + 4; // julianisch
if (y>1582 || (y==1582 && m>11) || (y==1582
&& m==11 && d>14))
s += y/400 - y/100 +
2; // gregorianisch
wobei man bei der Erkennung, ob es
sich
um ein Gregorianisches Datum handelt aufpassen muss, dass das Monat
bereits um 1 von 10 auf 11 erhöht worden ist!
Und hier die Funktion, die den
letzten Tag (also auch die Anzahl der Tage) eines Monats ermittelt:
int
lastdayofmonth(int m, int y)
{
int s = m % 2; if (m>7) s=1-s;
if (m==2) {s-=2; if (!(y % 4)) {s++;
if (y>1582 && !(y % 100) && y %
400) s--;}}
if (m<1 || m>12 || y<-32000 || y>32000)
return(0); else return(s+30);
}
Diese Funktion wird zum
Herzstück für einige folgende Funktionen werden. Zum Beispiel
für diese beiden:
int checkschaltjahr(int y)
{
return(lastdayofmonth(2,
y)-28);
} // 1 wenn Schaltjahr, sonst 0
int dayofyear(int d, int m, int y)
{
int i,s,mtag[13];
for (i=1; i<13; i++)
{
s = i % 2; if (i==2) s-=2;
if (i>7) s=1-s; mtag[i]=s+30;
}
mtag[2]+=checkschaltjahr(y);
if (d<1 || d>lastdayofmonth(m, y)) return(0);
if (y==1582) {mtag[10]=21; if (m==10 && d>4) d-=10;}
mtag[0]=s=0; do {s += mtag[--m];} while (m);
return(s+d);
}
Auch die Funktion weekofyear(),
welche die Kalenderwoche liefert wurde verbessert:
void weekofyearhelp(int d, int m, int
y, int *w, int *s)
{
*w=((dayofweek(1,1,y)+1)%7)-3;
*s=((dayofyear(d,m,y)-1+*w)/7)+1;
}
int weekofyear(int d, int m, int
y)
{
int s,w,x;
if (y<1583 || d<1 ||
d>lastdayofmonth(m, y)) return(0);
weekofyearhelp(d, m, y,
&w, &s);
// falls KW 1 checken ob
nicht doch letzte KW Vorjahr
if (s==1) {if (-w>=d)
weekofyearhelp(31, 12, y-1, &x, &s);}
// falls KW 53 checken ob
nicht doch erste KW nächstes Jahr
else if (s==53 &&
(w!=2 || !checkschaltjahr(y)) && w!=3) s=1;
return(s);
}
Sieht etwas zusammengestaucht aus,
liefert aber die gleichen Ergebnisse wie die Funktion weiter oben in
diesem Artikel.
Im englischsprachigem Raum
(Großbritannien und USA) wird die Kalenderwoche noch einem
anderen Schema ermittelt. Dort beginnt die Woche mit Sonntag und der 1.
Jänner ist immer in der Woche 1, egal an welchem Wochentag er
beginnt. Somit kann da Jahr im Extremfall 54 Kalenderwochen haben, wenn
es an einem Samstag beginnt und ein Schaltjahr ist (2028 ist so ein
Jahr).
int weekofyearukus(int d, int m, int y)
{
if (y<1583 || d<1 ||
d>lastdayofmonth(m, y)) return(0);
return (dayofyear(d,m,y) +
dayofweek(1,1,y)-2)/7 + 1;
}
Ein etwas "verrückter" Ansatz
ist, den Ostersonntag als Basis für die Wochentagsberechnung eines
Jahres zu verwenden. Da ja der Ostersonntag immer ein Sonntag ist, kann
man ausrechnen, an welchen Tag des Jahres dieser fällt und an
welchen Tag des Jahres der Tag des zu ermittelnden Wochentages. Aus der
Differenz der beiden Werte kann der Wochentag abgeleitet werden:
int
dayofweek(int d, int m, int y)
{
int e,o=3,s,z;
s=dayofyear(d, m, y);
// verbotener Bereich 5.10. - 14.10.1582
if (!s || (y==1582 && m==10 && d>4 &&
d<15)) return(0);
e=eastersunday(y);
if (e>31) {o++; e-=31;}
z=dayofyear(e, o, y);
return((s-z+350) % 7 + 1);
}
Hier überprüft
dayofyear() gleichzeitig, ob das Datum gültig ist. Nur für
den unzulässigen Bereich vom 5.10. - 14.10. 1582 ist eine eigene
Prüfung erforderlich. Wichtig ist auch, dass zur Differenz
zwischen dem Wert s (Tag des Jahres des zu suchenden Tages) und dem
Wert z (Tag des Jahres des Ostersonntags) ein ausreichend hoher durch 7
ohne Rest teilbarer Wert addiert wird, damit die Modulo-Funktion
für Tage vor dem Ostersonntag keine negativen Werte liefert. (Da
der Ostersonntag max. der 25. April sein kann und dieser in einem
Schaltjahr der Tag Nummer 31 + 29 + 31 + 25 = 116 wäre, ist die
nächste ganz durch 7 teilbare Zahl 119 als kleinster zu
addierender Wert ausreichend.)
Ermittlung des Wochentages
von historischen Daten
Bisher wurde nur der Wochentag von
Jahren n. Chr. ([n.] u.Z.) ermittelt. Um auch den Wochentag von Daten
v. Chr. (v.u.Z.) braucht man spezielle Funktionen und Informationen.
Zuerst wird angenommen, dass es den Julianischen Kalender in der
bekannten Abfolge der Monate schon immer gegeben hat.Auf das Jahr 1
v.u.Z. folgt direkt das Jahr 1 n.u.Z., ein Jahr 0 gibt es nicht.
Eine gute Erklärung, dass es
eigentlich kein Jahr 0 gibt ist: Eine Minute vor dem Fußballspiel
wurde der letzte Schnee vom Spielfeld entfernt, eine Minute nach Beginn
des Spiels fiel bereits das erste Tor.
Die Astronomen aber verwenden ein
Jahr 0, daher gilt:
2 v.u.Z. = Astronomisches Jahr -1
1 v.u.Z. = Astronomisches Jahr 0
1 n.u.Z = Astronomisches Jahr 1
Die Wochentagsberechnung beruht auf das (Modifizierte) Julianische Datum, das die Astronomen verwenden:
int
gregorianisch(int y, int m, int d)
{
int g=0;
if (y>1582 || (y==1582 && m>10) || (y==1582
&& m==10 && d>14)) g++; // g=1;
return(g);
}
/*
* jmt2mjd liefert zum Datum das modifizierte Julianische
Datum,
* also die Anzahl der Tage seit dem 17.11.1858.
* Dabei ist ajahr das astronomische Jahr.
* Ungültige Datumsangaben führen zu undefinierten
Ergebnissen.
* Erlaubt sind Daten
* vom 1.3.-32768 = 1.03.32769 vC
--> mjd = -12647395
* bis
zum
31.12.32767 nC --> mjd = 11289324.
*/
long jmt2mjd(int ajahr, int monat, int tag)
{
static int m[] = { 0, 428, 459, 122, 153, 183, 214,
244, 275, 306, 336, 367, 397 };
int hjahr,s;
hjahr=ajahr;
if (monat<3) hjahr--;
if (gregorianisch(ajahr, monat, tag))
s = hjahr/400 - hjahr/100 + hjahr/4;
else
if (hjahr < 0L) s = -(-(hjahr+1))/4-1 - 2;
else
s = hjahr /4 - 2;
return(365L * (long) hjahr - 679004L + (long)(s + m[monat] +
tag));
}
int
lastdayofmonth(int m, int y)
{
int s = m % 2;
if (m>7) s=1-s;
if (m==2) {s-=2; if (!(y % 4)) {s++;
if (y>1582 && !(y % 100) && y %
400) s--;}}
if (m<1 || m>12 || y<-32765 || y>32765)
return(0);
else
return(s+30);
}
// 1 .. Sonntag, 2 .. Montag usw.
// 0 .. ungültiger Tag
int dayofweekmjd(int tag, int
monat, int ajahr)
{
long mjd = jmt2mjd(ajahr, monat, tag);
if (tag<1 || tag>lastdayofmonth(monat, ajahr)) return(0);
return (int) ((mjd+14000003L) % 7 + 1);
}
Beispiel: Julius Cäsar wurde
am 15. März 44 v.u.Z. ermordet. Das war astronomisch der 15.
März -43 und ergibt das Modifizierte Julianische Datum -694575.
Addiert man dazu 2400001 erhält man das (Historische) Julianische
Datum von 1705426. Dieses modulo 7 ergibt als Rest 2 + 1 dazu addiert =
Mittwoch. In der Funktion wird durch Zusätze eine Modulo-Funktion
von negativen Werten, welche auch wieder negative Zahlen ergibt,
vermieden. Daher wird 14 Mio. zum Modifizierten Julianischem Datum
addiert, damit es sicher positiv ist. Die 3 wird als Offset addiert,
weil der 17. November 1858 ein Mittwoch war.
Jetzt kann das Ergebnis wieder Modulo 7 genommen werden und 1 wird dazu
addiert (weil die 0 ja für ein ungültiges Datum reserviert
ist.)
Im Beispiel: -694575 + 14000003 =
13305428 modulo 7 = 3. Dazu noch 1 addiert = 4 also ein Mittwoch
Übrigens: Das (Modifizierte)
Julianische Datum eignet sich auch dafür, Tagesdifferenzen
zwischen zwei Tagen zu ermitteln. Einfach von jeden der beiden Tage das
Julianische Datum ausrechnen und dann einfach die Differenz der beiden
ermitteln.
Alle C-Beispielprogramme
inkl. Sourcecode können hier
heruntergeladen
werden:
Neu: Auch als
32Bit-Windows-Programm namens "Osterdatum und
Kalender"
und noch mehr Astronomie mit dem Programm "AstroFritz"
(sollte aber auch unter Linux mit wine funktionieren)