Chyba zetknąłeś się Czytelniku z problemem uwzględniania dni świątecznych w systemach zależnych od czasu/daty. Jeśli nie, to się zetkniesz, a dziś zapraszam Cię na skosztowanie tego co może Ci się przydać.
Zaczniemy od wygenerowania danych dotyczących stałych świąt. Użyjemy do tego następującego zapytania (numer 1):
SELECT CONCAT(rok,dm) AS data
FROM
(SELECT '-01-01' AS dm UNION ALL SELECT '-01-06' UNION ALL SELECT '-05-01' UNION ALL SELECT '-05-03' UNION ALL SELECT '-08-15' UNION ALL SELECT '-11-01' UNION ALL SELECT '-11-11' UNION ALL SELECT '-12-25' UNION ALL SELECT '-12-26') AS dni,
(SELECT 2018 AS rok) AS lata
Utworzyliśmy tu zapytanie korzystające z wyniku iloczynu karezjańskiego (każdy z każdym) dwóch podzapytań. Jednym zapytaniem generujemy dni i miesiące Świąt, drugim rok. W tym przypadku rok ustaliliśmy na stałe, choć można użyć wyrażenia: YEAR(CURDATE()). Jeśli zapytanie będzie wybierać kilka lat (poprzez UNION ALL), wtedy wynik zapytania wygeneruje dni świąteczne dla tych kilku lat.
Teraz będziemy musieli dodać do wyniku Święta ruchome, tj. Wielkanoc (Niedziela i Poniedziałek), Boże Ciało i Zielone Świątki. Dwa ostatnie Święta są zależne od dnia Wielkanocy, ale wiemy, że Boże Ciało zaczyna się 60 dni po Wielkiejnocy, a Zielone Świątki 49 dni po.
Będziemy używać algorytmu Gaussa do obliczenia daty Wielkanocy. Opiera się on na kalkulacji dni od 22 marca danego roku. Skoro tak, to dodajmy do naszego zapytania tą datę, datę + 1 dzień (Wielki Poniedziałek), 22 marca + 49 dni oraz 22 marca + 60 dni. W dalszym kroku po wyliczeniu dni algorytmem Gaussa, do każdej z tych dat dodamy uzyskaną ilość. Przy okazji dodajmy pole o wartości 0/1 i oznaczmy jedynką te daty (będzie wiadomo, że trzeba je zmodyfikować, a dat stałych Świąt nie).
SELECT CONCAT(rok,dm) AS data,wielkanoc
FROM
(SELECT '-01-01' AS dm,0 AS wielkanoc UNION ALL SELECT '-01-06',0 UNION ALL SELECT '-03-22',1 UNION ALL SELECT '-03-23',1 UNION ALL SELECT '-05-01',0 UNION ALL SELECT '-05-10',1 UNION ALL SELECT '-05-03',0 UNION ALL SELECT '-05-21',1 UNION ALL SELECT '-08-15',0 UNION ALL SELECT '-11-01',0 UNION ALL SELECT '-11-11',0 UNION ALL SELECT '-12-25',0 UNION ALL SELECT '-12-26',0) AS dni,
(SELECT 2018 AS rok) AS lata)
Z tego zapytania (numer 2) uzyskamy wynik:
data | wielkanoc |
---|---|
2018-01-01 | 0 |
2018-01-06 | 0 |
2018-03-22 | 1 |
2018-03-23 | 1 |
2018-05-01 | 1 |
2018-05-01 | 0 |
2018-05-10 | 1 |
2018-05-03 | 0 |
2018-08-15 | 0 |
2018-11-01 | 0 |
2018-11-11 | 0 |
2018-12-25 | 0 |
2018-12-26 | 0 |
Przystąpmy teraz do obliczenia daty Wielkanocy.
Całość algorytmu znajdziesz w przydatnych linkach, natomiast my go trochę skrócimy. W obliczeniach algorytmu Gaussa musimy dostarczyć dwie stałe (A i B), które są zależne od roku, którego dotyczy obliczenie. Całe szczęście, że stałe dotyczą zakresów lat, a nie zmieniają się z roku na rok. W przypadku zmiennej A ustalimy zakres na 1900-2199, a w przypadku zmiennej B będziemy operować na zakresie 1900-2099. Wartości zmiennych będą wynosić odpowiednio: 24 oraz 5.
Zapytanie obliczające zgodnie z naszymi ustaleniami będzie wygladać tak:
SELECT (((YEAR(CURDATE()) MOD 19)*19+24) MOD 30)+((2*(YEAR(CURDATE()) MOD 4)+4*(YEAR(CURDATE()) MOD 7)+6*(((YEAR(CURDATE()) MOD 19)*19+24) MOD 30)+5) MOD 7) AS za_ile_dni
Ale to jeszcze nie koniec. Jeśli wynik jest mniejszy niż 31 dni, to nic nie robimy. Jeśli większy, to odejmujemy 31 dni. Jeśli różnica da 25 lub 26 dni, to odejmujemy jeszcze 7 dni (w sumie 38), jeśli nie, to tylko 31. Zapytanie operuje na aktualnej dacie (roku). W momencie pisania tego postu jest rok 2017, a wynikiem zapytania jest wartość 25 (nie musimy jej więc już modyfikować), co oznacza, że Wielkanoc w roku 2017 była 25 dni od 22 marca 2017 (czyli 16 kwietnia 2017).
Teraz należy połączyć zapytanie wybierające start Świąt oraz zapytanie obliczające dni Wielkanocy. Najpierw może pseudoSQL, aby było łatwiej analizowac docelowe zapytanie:
SELECT IF(wielkanoc=1,DATE_ADD(data,korekta_dni_z_algorytmu_gaussa),data)
FROM (
SELECT data,wielkanoc,oblicz_dni_z_algorytmu_gaussa FROM(
SELECT zapytanie_numer_2_z_datami_startowymi
)
)
Zapytanie numer 2 odnajdziesz wyżej - ustala ono start dni świątecznych oraz przypisuje im znacznik dzięki, któremu będziemy wiedzieć czy to Święto ruchome zależne od dnia Wielkiejnocy.
Zapytanie nadrzędne wyciąga informacje z zapytania numer 2 i dodatkowo oblicza ilość dni wg algorytmu Gaussa. Zapytanie zewnętrzne natomiast, w zależności od znacznika (0/1) dodaje do daty startowej ilość dni wyliczonych i poddanych korekcie lub pozostawia tą datę niezmienioną.
A to już docelowe zapytanie i jego wynik:
SELECT IF(wielkanoc=1, DATE_ADD(data, INTERVAL IF(za_ile_dni>31, IF(za_ile_dni-31=25 OR za_ile_dni-31=26,za_ile_dni-38,za_ile_dni-31),za_ile_dni) DAY), data) AS data
FROM (
SELECT data,wielkanoc,(((YEAR(data) MOD 19)*19+24) MOD 30)+((2*(YEAR(data) MOD 4)+4*(YEAR(data) MOD 7)+6*(((YEAR(data) MOD 19)*19+24) MOD 30)+5) MOD 7) AS za_ile_dni
FROM(
SELECT CONCAT(rok,dm) AS data,wielkanoc
FROM(
(SELECT '-01-01' AS dm,0 AS wielkanoc UNION ALL SELECT '-01-06',0 UNION ALL SELECT '-03-22',1 UNION ALL SELECT '-03-23',1 UNION ALL SELECT '-05-01',0 UNION ALL SELECT '-05-10',1 UNION ALL SELECT '-05-03',0 UNION ALL SELECT '-05-21',1 UNION ALL SELECT '-08-15',0 UNION ALL SELECT '-11-01',0 UNION ALL SELECT '-11-11',0 UNION ALL SELECT '-12-25',0 UNION ALL SELECT '-12-26',0) AS dni,
(SELECT 2018 AS rok) AS lata)
) AS dni
) AS swieta
data |
---|
2018-01-01 |
2018-01-06 |
2018-04-01 |
2018-04-02 |
2018-05-01 |
2018-05-20 |
2018-05-03 |
2018-05-31 |
2018-08-15 |
2018-11-01 |
2018-11-11 |
2018-12-25 |
2018-12-26 |
Łącząc ostateczne zapytanie z zapytaniem generującym kalendarz możesz wyznaczyć dni, które nie są dniami świątecznymi.
Obydwa zapytania są dosyć rozbudowane, aby nie zaprzątać Ci już głowy przedstawię znów pseudoSQL:
SELECT kalendarz AS k
LEFT JOIN (zapytanie_swiateczne) AS zs ON zs.data=k.data
WHERE zs.data IS NULL
Mam nadzieję, że przyda Ci się powyższe zapytanie, jednocześnie trzymam kciuki, aby nie odciągnęło Cię (na dłużej) od wigilijnego karpia lub wielkanocnego jajka.
Przydatne linki:
Obliczanie Wielkanocy algorytmem Gaussa
Jak wygenerować kalendarz w MySQL