Kalendarz GREGORIAŃSKI
Rachuba dni
Czasami potrzeba dowiedzieć się jaka data będzie N dni po zadanej dacie. Aby w dowolnym przypadku odpowiedzieć na to pytanie, potrzeba potrafić przeliczać datę na ciągłą rachubę dni oraz odwrotnie.
Poniższe algorytmy dotyczą kalendarza gregoriańskiego, początek ciągłej rachuby dni jest wybrany jako naturalny dla tego kalendarza. Liczbowo jest mniejsza od ciągłej rachuby dni stosowaniej w astronomii o 1721119.5 (część ułamkowa tej liczby jest konsekwencją faktu, że dni juliańskie zaczynają się w południe).
Algorytmy zgadazają sie z historią od 1582-10-15. Wcześniejsze daty przeliczane tymi algorytmami nie zgadzają się z historią. Można je traktować jedynie jako formalną ekstrapolację. O ile można przewidzieć przyszłość, będą zgodne z historią co najmniej do 2800-02-28. Istnieją propozycje ulepszeń kalenarza gregorańskiego. Jeżeli któraś z nich stanie się faktem, będzie to dotyczyć istnienia lub nie daty nie wcześniejszej, niż 2800-02-29.
Kalendarz gregoriański jest cykliczny. Długość jednego cyklu wynosi 146097 dni. Prawdopodobnie przez przypadek (? a może synchroniczność) jest to dokładnie 20871 pełnych tygodni. Dokładniej, liczba 146097 po rozłożeniu na czynniki pierwsze przedstawia się jako: 3^3*7*773.
Kalendarz jest dosyć starym pomysłem. Na tyle dawnym, że nie znano wtedy liczby zero i na mocy tradycji nadal notorycznie stosowane jest przesunięcie (offset) jeden. (Nie jestem pewien, czy nie znano. Podejrzewam, że odczuwano przemożny, nieświadomy, metafizyczny lęk przed symbolicznym manipulowaniem obiektami nieistniejącymi!) Stąd bezpośrednio przed rokiem 1 n.e. był rok 1 p.n.e.; zaś Dzień Dziecka to 06.01 zamiast 05.00. Dokąd istnieje potrzeba liczenia obiektów całkowitych, spór o wyższość lub jej brak stosowania przesunięcia jeden jest niemal jałowy -- przeważają nawyki. Inaczej jawi się rzeczywistość, gdy myślimy (mierzymy) bardziej precyzyjnymi (mniejszymi) jednostkami. O zdarzeniu sprzed paru minut nie myślimy i nie mówimy, że od niego minęło 1 lat lub chociażby 1 dni!
Formalnie, rok zaczyna się z początkiem pierwszego stycznia i kończy się z końcem trzydziestego pierwszego grudnia. Dla zliczania dni nie jest to wygodne. Zapiszmy długości poszczególnych miesięcy.
Styczeń 31, Luty 28 lub 29, Marzec 31,
Kwiecień 30, Maj 31, Czerwiec 30,
Lipiec 31, Sierpień 31, Wrzesień 30,
Październik 31, Listopad 30, Grudzień 31.Jest to prawie regularne, lecz nie widać dokładnie na czym ta regularność polega. Zobaczmy, zaczynając od Marca (dawniej miesięcy było 10 i od Marca zaczynał się rok).
Mar 31, Kwi 30; Maj 31, Cze 30; Lip 31;; Sie 31, Wrz 30; Paź 31, Lis 30; Gru 31;; Sty 31, Lut 28 lub 29.Same pogrupowane ilości dni:
( ( (31, 30), (31, 30), 31 ), ( (31, 30), (31, 30), 31 ), (31, 28 lub 29) )Korzystając z powyższych cykli łatwiej jest obliczyć ilość dni od ostatniego pierwszego marca poprzedzającego zadaną datę, niż numer dnia roku. Jeżeli numer miesiąca m jest większy niż 2 to zmodyfikowany miesiąc k wyniesie
k = m - 3
oraz zmodyfikowany rok r i numer roku y będą sobie równer = y
. W przeciwnym wypadkuk = m - 3 + 12
orazr = y - 1
. Można to zapisać zwięźle, bez konstrukcji warunkowych:
s = (12 - m)/10; r = y - s; k = m - 3 + 12*s;
Na potrzeby algorytmów jako rok lepiej rozumieć rok zmodyfikowany, czyli ciąg dni zaczynający sie pierwszego marca o godzinie 00:00 i kończący się razem z końcem dnia poprzedzającego następnego pierwszego marca. Dzień przestępny jest dodawany do okresów czteroletnich z wyjątkiem ostatniego w okresach stuletnich nie podzielnych przez cztery.
Mamy następujące okresy:
- Okres 31 dni.
- Podstawowy okres miesięczny.
- Okres 61 dni.
- Okres dwumiesięczny: 31+30 dni. Drugi miesiąc to niepełny podstawowy okres miesięczny.
- Okres 153 dni.
- Okres pięciomiesięczny: 2*61+31 dni. Dwa pełne okresy dwumiesięczne plus niepełny okres dwumiesięczny.
- Okres 365 lub dni.
- Rok bez dnia przestępnego: 2*153+31+28 dni. Dwa pełne okresy pięciomiesięczne plus niepełny okres pięciomiesięczny. Zaczyna się pierwszego marca. Ponieważ dzień przestępny zawsze objawia się wystąpieniem daty 29 lutego, możemy go dołączać lub nie do cykli wieloletnich zachowując nienaruszone okresy 365, 153, 61 oraz 31 dniowe.
- Okres 1461 dni.
- Czterolecie z dniem przestępnym: 4*365+1 dni. Zaczyna się 1 marca roku, którego numer jest bez reszty podzielny przez 4.
- Okres 36524 dni.
- Stulecie z 25 okresów czteroletnich bez ostatniego dnia przestępnego: 25*1461-1 dni. Początkowe 24 okresy są pełne. Ostatni, dwudziesty piąty nie zawiera dnia przestępnego. Zaczyna się 1 marca roku, którego numer jest bez reszty podzielny przez 100.
- Okres 146097 dni.
- Najdłuższy, pełny okres ma stałą ilość dni. Składa się z 4 okresów stuletnich oraz dnia przestępnego: 4*36524+1 dni. Zaczyna się 1 marca roku, którego numer jest bez reszty podzielny przez 400.
Kody źródłowe
UWAGA!
Wszystkie obliczenia muszą być realizowane w arytmetyce całkowitej (stałopozycyjnej). Operacje mnożenia * i dzielenia / mają jednakowy priorytet oraz łączą od lewej do prawej.
To znaczy, że:
(X OP Y OP Z)
znaczy((X OP Y) OP Z)
.Dzień gregoriański na podstawie daty gregoriańskiej
Wersja dłuższa, czytelniejsza:
#!/usr/bin/bash function gdzienx() { declare -i y=$1; declare -i m=$2; declare -i d=$3; local s=$(( (12-$m)/10 )); # korekta roku local r=$(( $y-$s )); # odejmij korektex roku local gg=$(( $r/400 )); # czterostulecia local ff=$(( $r%400/100 )); # stulecia w czterostuleciu local ee=$(( $r%100/4 )); # czteroletnie w stuleciu local dd=$(( $r%4 )); # jednoletnie w czteroleciu local k=$(( $m-3+12*s )); # zmodyfikuj miesiaxc local cc=$(( $k/5 )); # piexciomiesiexczne local bb=$(( $k%5/2 )); # dwumiesiexczne local aa=$(( $k%5%2 )); # jednomiesiexczne # RAZEM local g=$(( $d-1+31*$aa+61*$bb+153*$cc )); (( g+=365*$dd+1461*$ee+36524*$ff+146097*gg )) echo $g } export -f gdzienxWersja druga, zwięźlejsza.
#!/usr/bin/bash function gregday() { declare -i y=$1; declare -i m=$2; declare -i d=$3; local g=$(( 275*$m )); (( g /= 9 )); (( g += $d - 91 + 367*$y )); (( g += -7*($y + ($m + 9)/12)/4 )); (( g += -3*(1 + ($y + ($m - 9)/7)/100)/4 )); echo $g } export -f gregdayWyrażenie
(m-9)/7
znaczy -1 dlam = 1..2
oraz 0 dlam = 3..12
.Zaś
(m+9)/12
znaczy 0 dlam = 1..2
oraz 1 dlam = 3..12
.Stała 367 to
2*153 + 61
. Jeżeli zdefiniować rok obliczeniowy z lutym mającym 30 dni, to będzie zachowana pełna regularność okresów 31, 61, 153. Wtedy trzeba odjąć dwa od każdego roku niepodzielnego przez cztery oraz tylko jeden od podzielnego(-7*(...)/4)
. Trzeba też odjąć jeden od każdego stulecia niepodzielnego przez cztery oraz zero od podzielnego(-3*(1+(...))/4
).Powtórnie to samo, ale w języku C.
long gregday(int y, int m, int d) { return d + 275*m/9 - 91 + 367L*y - 7*(y + (m + 9)/12)/4 - 3*(1 + (y + (m - 9)/7)/100)/4; }Stała -91 jest liczbą, która zapewnia, że szósty pełny okres zaczyna się razem z początkiem dnia 2000-03-01.
Data gregoriańska na podstawie dnia gregoriańskiego
Algorytm odwrotny jest bardziej skomplikowany.
#define SY 365 #define QY (1 + 4*SY) #define SC (25L*QY - 1L) #define QC (1L + 4L*SC) int gregrmd(int *rok, int *miesiaxc, int *dzienx, long f) { int d, /* zlicza dni */ l, /* 29 Luty, co 4 lata */ m, /* zlicza miesiaxce */ q, /* 29 Luty, co 400 lat */ w = (int)( (3+f)%7 ), /* 0 Niedziela, ..., 6 Sobota */ y = 400 * (int)( f/QC ); y += q = 100 * (int)( (f %= QC)/SC ); y += 4 * (int)( (f = (q /= 400)*QC + f%SC)/QY ); y += l = (d = (int)( f%QY ))/SY; m = (d = (l /= 4)*SY + d%SY)/153*5; m += (d %= 153)/61*2; m += (d %= 61)/31; *rok = y - q*400 - l + m/10; *miesiaxc = 3 + m - m/10*12; *dzienx = 1 + d%31 + q*2; return w; }Data za N dni
Teraz policzenie daty odległej o N dni od zadanej daty nie jest trudne. Dodatkowo, możemy po drodze pośrednio zweryfikować poprawność daty oraz określić jej dzień tydodnia.
int zandni(int *rok, int *miesiaxc, int *dzienx, long n) { int r, m, d; long g = gregday(*rok, *miesiaxc, *dzienx); gregrmd(&r, &m, &d, g); if(*rok != r) return -1; if(*miesiaxc != m) return -1; if(*dzienx != d) return -1; return gregrmd(rok, miesiaxc, dzienx, g + n); }
(C) Aleksander Nabagło, gregrmd(730825)