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 1882–1886 (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[potrzebny przypis].

Przykładowy kalendarz dla lat 1901-2040:

Opis Przykł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ępne I II III IV V VI VII VIII IX X XI XII
1901 1929 1957 1985 2013 1 4 4 0 2 5 0 3 6 1 4 6
1902 1930 1958 1986 2014 2 5 5 1 3 6 1 4 0 2 5 0
1903 1931 1959 1987 2015 3 6 6 2 4 0 2 5 1 3 6 1
1904 1932 1960 1988 2016 4 0 1 4 6 2 4 0 3 5 1 3
1905 1933 1961 1989 2017 6 2 2 5 0 3 5 1 4 6 2 4
1906 1934 1962 1990 2018 0 3 3 6 1 4 6 2 5 0 3 5
1907 1935 1963 1991 2019 1 4 4 0 2 5 0 3 6 1 4 6
1908 1936 1964 1992 2020 2 5 6 2 4 0 2 5 1 3 6 1
1909 1937 1965 1993 2021 4 0 0 3 5 1 3 6 2 4 0 2
1910 1938 1966 1994 2022 5 1 1 4 6 2 4 0 3 5 1 3
1911 1939 1967 1995 2023 6 2 2 5 0 3 5 1 4 6 2 4
1912 1940 1968 1996 2024 0 3 4 0 2 5 0 3 6 1 4 6
1913 1941 1969 1997 2025 2 5 5 1 3 6 1 4 0 2 5 0
1914 1942 1970 1998 2026 3 6 6 2 4 0 2 5 1 3 6 1
1915 1943 1971 1999 2027 4 0 0 3 5 1 3 6 2 4 0 2
1916 1944 1972 2000 2028 5 1 2 5 0 3 5 1 4 6 2 4
1917 1945 1973 2001 2029 0 3 3 6 1 4 6 2 5 0 3 5
1918 1946 1974 2002 2030 1 4 4 0 2 5 0 3 6 1 4 6
1919 1947 1975 2003 2031 2 5 5 1 3 6 1 4 0 2 5 0
1920 1948 1976 2004 2032 3 6 0 3 5 1 3 6 2 4 0 2
1921 1949 1977 2005 2033 5 1 1 4 6 2 4 0 3 5 1 3
1922 1950 1978 2006 2034 6 2 2 5 0 3 5 1 4 6 2 4
1923 1951 1979 2007 2035 0 3 3 6 1 4 6 2 5 0 3 5
1924 1952 1980 2008 2036 1 4 5 1 3 6 1 4 0 2 5 0
1925 1953 1981 2009 2037 3 6 6 2 4 0 2 5 1 3 6 1
1926 1954 1982 2010 2038 4 0 0 3 5 1 3 6 2 4 0 2
1927 1955 1983 2011 2039 5 1 1 4 6 2 4 0 3 5 1 3
1928 1956 1984 2012 2040 6 2 3 6 1 4 6 2 5 0 3 5
Pochyła czcionka oznacza lata przestępne I II III IV V VI VII VIII IX X XI XII
Dni tygodnia
Poniedziałek 1 8 15 22 29 36
Wtorek 2 9 16 23 30 37
Środa 3 10 17 24 31
Czwartek 4 11 18 25 32
Piątek 5 12 19 26 33
Sobota 6 13 20 27 34
Niedziela 7 14 21 28 35

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. a b J RJ.R. Stockton J RJ.R., 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 VanA.V. Helden Albert VanA.V., 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. AndrewA. Smith AndrewA., The kalendarium Package [online], Comprehensive TeX Archive Network, s. 23 [dostęp 2023-06-23]  (ang.).
  7. AllanA. Kochis AllanA., COSC 1315 Fundamentals of Programming [online], www.austincc.edu [dostęp 2023-06-22]  (ang.).

Bibliografia

  • Omówienie wzoru Zellera. merlyn.demon.co.uk. [zarchiwizowane z tego adresu (2013-07-29)]. (ang.). na stronie Dr J R Stockton.
  • LCCN: sh85018842
  • GND: 4198916-8
  • BnF: 156191236
  • BNCF: 31403
  • J9U: 987007293778605171
  • Catalana: 0165811