Kalendarz wieczny

Kalendarz wieczny, także kalendarz perpetualny – tabela lub wzór pozwalająca obliczyć określony dzień tygodnia w kalendarzu gregoriańskim dla każdej daty w postaci: dzień miesiąca, miesiąc, rok.

Wzory liczące dni tygodnia

Metoda konwergencji Zellera

Kalendarz stuletni daje sprowadzić się do dość prostego algorytmu, który w pierwotnej wersji został zaproponowany przez Christiana Zellera w kolejnych publikacjach, które ukazywały się w latach 18821886 (m.in. w Acta Mathematica, vol.9 (1886–1887), pp.131–6). Formuła zaproponowana dla Zellera działa dla kalendarza gregoriańskiego oraz juliańskiego, jednak była przeznaczona do liczenia przez ludzi, co może rodzić problemy przy liczeniu reszty z dzielenia dla niektórych dat[1].

Uproszczony wzór Keitha

Algorytm Zellera został uproszczony i opublikowany w 1990 roku przez matematyka Mike'a Keitha do postaci[2][1]:

nr dnia tygodnia = ([23m/9] + d + 4 + y + [z/4] - [z/100] + [z/400] - c) mod 7
gdzie
  • [ ] oznacza część całkowitą liczby
  • mod – funkcja modulo (reszta z dzielenia)
  • m – (ang. month) numer miesiąca (od stycznia = 1 do grudnia = 12)
  • d – (ang. day) numer dnia miesiąca
  • y – (ang. year) rok
  • z – rok z poprawką: z = y - 1 jeżeli m < 3; z = y, jeżeli m ⩾ 3
  • c – (ang. correction) korekta: c = 0, jeżeli m < 3; c = 2, jeżeli m ⩾ 3
  • nr dnia tygodnia należy przeliczać następująco: 0 – niedziela, 1 – poniedziałek, 2 – wtorek, 3 – środa, 4 – czwartek, 5 – piątek, 6 – sobota.

Zaletą wzoru Mike'a Keitha jest możliwość zapisania go w języku programowania C w jednej linii liczącej raptem 45 znaków[3], co w funkcji wygląda tak:

int keith(int d, int m, int y)
{
  return (d+=m<3?y--:y-2,23*m/9+d+4+y/4-y/100+y/400)%7;
}

Formuła ta dotyczy obliczeń kalendarza gregoriańskiego.

Wady uproszczonych wzorów

Należy zwrócić uwagę, że kalendarz gregoriański został wprowadzony w katolickich krajach w XVI wieku oraz stopniowo w innych krajach Europy w kolejnych wiekach[4][5]. Obliczenia kalendarza w uproszczonych wzorach nie biorą tego pod uwagę i zakładają, że liczy się dni wstecznie niezależnie tego, czy dany kalendarz obowiązywał, czy nie. Analogiczne obliczenia można wykonać dla kalendarza juliańskiego, a faktyczny dzień tygodnia musiałby być obliczony zależnie od tego który kalendarz obowiązywał w danym tygodniu[6].

Mimo ew. uwzględnienia obowiązującego kalendarza algorytm Zellera i pokrewne nie biorą pod uwagę anomalii takich jak krótki wrzesień w 1752 roku, który również musiałby być uwzględniony przy datach sprzed XIX wieku m.in. w Wielkiej Brytanii[7].

Przykładowe implementacje

Poniżej podane są przykładowe implementacje w podstawowych językach programowania.

Implementacja w Pascalu

Zapis w języku Pascal algorytmu obliczania dnia tygodnia w kalendarzu gregoriańskim (bez ww. poprawki dla kalendarza juliańskiego):

function dzien_tygodnia(Year,Month,Day:word):string;
var M,C,D,N:integer;
const week:array[0..6]of string[12]=('Niedziela','Poniedziałek','Wtorek','Środa','Czwartek','Piątek','Sobota');
begin
	M := 1 + (Month + 9) mod 12 ; if M>10 then Dec(Year) ;
	C := Year div 100 ; D := Year mod 100 ;
	N := ((13*M-1) div 5 + D + D div 4 + C div 4 + 5*C + Day) mod 7 ;
	dzien_tygodnia:=week[N];
end;
  • gdzie Month, Day = numer miesiąca i dnia miesiąca, Year = czterocyfrowy zapis roku, N = kod dnia tygodnia poczynając od niedzieli (0) do soboty (6),
  • mod = funkcja modulo, div = funkcja dzielenia liczb całkowitych bez reszty z zaokrągleniem w dół, if ... then - funkcja warunkowa

Często wzór Zellera jest podawany w formie, w której występuje wartość 2*C zamiast 5*C, która to forma prowadzi jednak przy niektórych latach do wartości N - ujemnych oraz nie sprawdza się dla niektórych dat.

Implementacja w C

Funkcja napisana w C na podstawie algorytmu Zellera:

char* week[7]={"Niedziela","Poniedzialek","Wtorek","Sroda","Czwartek","Piatek","Sobota"};
int zeller(int d, int m, int y, int gr){
	int Y,C,M,N,D;
	M=1+(9+m)%12;
	Y=y-(M>10);
	C=Y/100;
	D=Y%100;
	if (gr==1) N=((13*M-1)/5+D+D/4+C/4+5*C+d)%7;
	else N=((13*M-1)/5+D+D/4+6*C+d+5)%7;
	return (7+N)%7;
}

Funkcja zwraca indeks do tablicy „week” (0 dla niedzieli). Parametr „gr” oznacza rodzaj kalendarza:

  • gr=1 dla gregoriańskiego,
  • gr≠1 dla juliańskiego.

Inny algorytm (z tym samym efektem) na podstawie analizy tablic zamieszczonych w Małej Encyklopedii Powszechnej PWN z 1959 r.

char* week[7]={"Niedziela","Poniedzialek","Wtorek","Sroda","Czwartek","Piatek","Sobota"};
int dow(int d, int m, int y, int gr)
{
  int mon[12]={0,1,1,2,5,6,2,3,4,0,1,4};
  int leap;
  int a,b,c;
  leap=(gr!=1&&y%4==0||gr==1&&(y%4==0&&y%100!=0||y%400==0));
  a=(y%100)%28;
  b=(gr!=1)*(4+(y%700)/100+2*(a/4)+6*((!leap)*(1+(a%4))+(leap)*((9+m)/12)))%7+
    (gr==1)*(2*(1+(y%400)/100+(a/4))+6*((!leap)*(1+(a%4))+(leap)*((9+m)/12)))%7;
  c=(3*mon[m-1]+d)%7;
  return (c+6*b)%7;
}

Tabele

Na podstawie wzoru Zellera można w prosty sposób utworzyć tabele, które mogą one praktycznie obejmować dowolny okres.

Przykładowy kalendarz dla lat 1901-2040:

OpisPrzykład dla: 31 I 1901
1. W tabeli Lata - Miesiące szukaj cyfry na przecięciu roku i miesiąca wybranej daty.1901/I → 1
2. Do odszukanej cyfry dodaj dzień miesiąca otrzymując kod.1+31=32
3. W tabeli Dni tygodnia szukaj kodu wskazującego dzień tygodnia.32 → czwartek
Lata Miesiące
Pochyła czcionka oznacza lata przestępneIIIIIIIVVVIVIIVIIIIXXXIXII
19011929195719852013144025036146
19021930195819862014255136140250
19031931195919872015366240251361
19041932196019882016401462403513
19051933196119892017622503514624
19061934196219902018033614625035
19071935196319912019144025036146
19081936196419922020256240251361
19091937196519932021400351362402
19101938196619942022511462403513
19111939196719952023622503514624
19121940196819962024034025036146
19131941196919972025255136140250
19141942197019982026366240251361
19151943197119992027400351362402
19161944197220002028512503514624
1917194519732001 2029033614625035
1918194619742002 2030144025036146
1919194719752003 2031255136140250
1920194819762004 2032360351362402
1921194919772005 2033511462403513
1922195019782006 2034622503514624
1923195119792007 2035033614625035
1924195219802008 2036145136140250
1925195319812009 2037366240251361
1926195419822010 2038400351362402
1927195519832011 2039511462403513
1928195619842012 2040623614625035
Pochyła czcionka oznacza lata przestępneIIIIIIIVVVIVIIVIIIIXXXIXII
Dni tygodnia
Poniedziałek1815222936
Wtorek2916233037
Środa310172431
Czwartek411182532
Piątek512192633
Sobota613202734
Niedziela714212835

Istnieje też inna wersja tabeli, mogąca obejmować wiele stuleci, począwszy od 1 roku n.e. Aby odnaleźć dzień tygodnia dla dowolnej daty, odnajdujemy:

1. W tablicy I a wiek, w tablicy I b rok, na skrzyżowaniu linii odczytamy cyfrę.

2. W tablicy II odczytamy cyfrę na skrzyżowaniu odnalezionej z tablicy I liczby oraz miesiąca. Dla lat przestępnych, oznaczonych kolorem czerwonym, styczeń i luty bierzemy również w ten sposób oznaczone.

3. W tablicy III na skrzyżowaniu liczby z tablicy II oraz daty, odnajdujemy dzień tygodnia poszukiwanej daty.

b) Lata
00 01 02 03 04 05
06 07 08 09 10 11
12 13 14 15 16
17 18 19 20 21 22
23 24 25 26 27
28 29 30 31 32 33
34 35 36 37 38 39
40 41 42 43 44
45 46 47 48 49 50
51 52 53 54 55
56 57 58 59 60 61
62 63 64 65 66 67
68 69 70 71 72
73 74 75 76 77 78
79 80 81 82 83
84 85 86 87 88 89
a) Wieki 90 91 92 93 94 95
I Juliański Gregoriański 96 97 98 99
0 7 14 17 21 25 6 0 1 2 3 4 5
1 8 15 5 6 0 1 2 3 4
2 9 18 22 26 4 5 6 0 1 2 3
3 10 3 4 5 6 0 1 2
4 11 15 19 23 27 2 3 4 5 6 0 1
5 12 16 20 24 28 1 2 3 4 5 6 0
6 13 0 1 2 3 4 5 6
II Miesiące III Daty
Maj
Sie
Lut
Lut
Mar
Lis
Cze Wrz
Gru
Kwi
Lip
Sty
Sty
Paź
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
1 2 3 4 5 6 0 1 1 Nie Pon Wto Śro Czw Pią Sob
2 3 4 5 6 0 1 2 2 Pon Wto Śro Czw Pią Sob Nie
3 4 5 6 0 1 2 3 3 Wto Śro Czw Pią Sob Nie Pon
4 5 6 0 1 2 3 4 4 Śro Czw Pią Sob Nie Pon Wto
5 6 0 1 2 3 4 5 5 Czw Pią Sob Nie Pon Wto Śro
6 0 1 2 3 4 5 6 6 Pią Sob Nie Pon Wto Śro Czw
0 1 2 3 4 5 6 0 0 Sob Nie Pon Wto Śro Czw Pią

Przykład: 18 listopada 2010.

Z tablicy I skrzyżować a) Wieki „20” i b) Lata „10” (tj. 2010) = 6.

Z tablicy II skrzyżować cyfrę 6 i miesiąc „Lis” (listopad) = 2.

Z tablicy III skrzyżować cyfrę 2 z cyfrą 18 tj. dzień = „Czw”, czyli że dzień 18 listopada 2010 był czwartkiem.

Zobacz też

Przypisy

  1. 1 2 J R Stockton, Zeller's Calendrical Works [online], merlyn.demon.co.uk, 7 września 2015 [dostęp 2023-06-22] [zarchiwizowane z adresu 2015-09-07] (ang.).
  2. Journal of Recreational Mathematics, Vol. 22, No. 4, 1990, p. 280
  3. Perpetual Calendar Algorithm [online], c2.com, 2013 [dostęp 2023-06-22] (ang.).
  4. Albert Van Helden, Gregorian Calendar - Chronology - The Galileo Project [online], galileo.rice.edu, 1995 [dostęp 2023-06-22].
  5. Gregorian calendar - Definition & Facts [online], www.britannica.com, 20 czerwca 2023 [dostęp 2023-06-22] (ang.).
  6. Andrew Smith, The kalendarium Package [online], Comprehensive TeX Archive Network, s. 23 [dostęp 2023-06-23] (ang.).
  7. Allan Kochis, COSC 1315 Fundamentals of Programming [online], www.austincc.edu [dostęp 2023-06-22] (ang.).

Bibliografia