{-|
Description: Calculation of bank holidays in Germany.

This module computes general bank holidays.
Most of these bank holidays are also public aka legal holidays
throughout Germany. You can use 'isPublicHoliday' to check if a
holiday is also a legal holiday.

Note: There are even more public holidays in each federal state which
are covered by the [@ExtraHolidays@]("Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays")
module of this package.

You can test this package or just calculate a few bank holidays with GHCi:

@
$ stack ghci --package time --package bank-holiday-germany
ghci> import Data.Time
ghci> import Data.Time.Calendar.BankHoliday.Germany
ghci> isBankHoliday (fromGregorian 2024 5 1)  -- Tag der Arbeit
True
ghci> isPublicHoliday ChristmasEve
False
ghci> holidaysBetween (fromGregorian 2024 12 1) (fromGregorian 2024 12 26)
[(2024-12-24,ChristmasEve),(2024-12-25,ChristmasDay),(2024-12-26,SecondChristmasDay)]
@

Resources:

 - https://de.wikipedia.org/wiki/Bankfeiertag

-}

module Data.Time.Calendar.BankHoliday.Germany (
    BankHoliday(..),
    isBankHoliday,
    isPublicHoliday,
    calculateEasterSunday,
    holidaysBetween,
    fromDay,
    toDay,
    germanHolidayName,
    yearFromDay
) where

import Prelude
import Data.Time.Calendar
import Data.Maybe

-- | Data type specifying German bank holidays including Christmas Eve and New Year's Eve.
--
-- Note: This type cannot be an instance of class 'Ord' because due to
-- Easter day calculation the order can change from year to year.
data BankHoliday
    = NewYearsDay        -- ^ Neujahrstag
    | GoodFriday         -- ^ Karfreitag
    | EasterMonday       -- ^ Ostermontag
    | LabourDay          -- ^ Tag der Arbeit
    | AscensionDay       -- ^ Christi Himmelfahrt
    | WhitMonday         -- ^ Pfingstmontag
    | GermanUnityDay     -- ^ Tag der Deutschen Einheit
    | ChristmasEve       -- ^ Heilig Abend
    | ChristmasDay       -- ^ 1​. Weihnachtsfeiertag
    | SecondChristmasDay -- ^ 2​. Weihnachtsfeiertag
    | NewYearsEve        -- ^ Silvestertag
    deriving (Int -> BankHoliday
BankHoliday -> Int
BankHoliday -> [BankHoliday]
BankHoliday -> BankHoliday
BankHoliday -> BankHoliday -> [BankHoliday]
BankHoliday -> BankHoliday -> BankHoliday -> [BankHoliday]
(BankHoliday -> BankHoliday)
-> (BankHoliday -> BankHoliday)
-> (Int -> BankHoliday)
-> (BankHoliday -> Int)
-> (BankHoliday -> [BankHoliday])
-> (BankHoliday -> BankHoliday -> [BankHoliday])
-> (BankHoliday -> BankHoliday -> [BankHoliday])
-> (BankHoliday -> BankHoliday -> BankHoliday -> [BankHoliday])
-> Enum BankHoliday
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: BankHoliday -> BankHoliday
succ :: BankHoliday -> BankHoliday
$cpred :: BankHoliday -> BankHoliday
pred :: BankHoliday -> BankHoliday
$ctoEnum :: Int -> BankHoliday
toEnum :: Int -> BankHoliday
$cfromEnum :: BankHoliday -> Int
fromEnum :: BankHoliday -> Int
$cenumFrom :: BankHoliday -> [BankHoliday]
enumFrom :: BankHoliday -> [BankHoliday]
$cenumFromThen :: BankHoliday -> BankHoliday -> [BankHoliday]
enumFromThen :: BankHoliday -> BankHoliday -> [BankHoliday]
$cenumFromTo :: BankHoliday -> BankHoliday -> [BankHoliday]
enumFromTo :: BankHoliday -> BankHoliday -> [BankHoliday]
$cenumFromThenTo :: BankHoliday -> BankHoliday -> BankHoliday -> [BankHoliday]
enumFromThenTo :: BankHoliday -> BankHoliday -> BankHoliday -> [BankHoliday]
Enum, BankHoliday -> BankHoliday -> Bool
(BankHoliday -> BankHoliday -> Bool)
-> (BankHoliday -> BankHoliday -> Bool) -> Eq BankHoliday
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BankHoliday -> BankHoliday -> Bool
== :: BankHoliday -> BankHoliday -> Bool
$c/= :: BankHoliday -> BankHoliday -> Bool
/= :: BankHoliday -> BankHoliday -> Bool
Eq, BankHoliday
BankHoliday -> BankHoliday -> Bounded BankHoliday
forall a. a -> a -> Bounded a
$cminBound :: BankHoliday
minBound :: BankHoliday
$cmaxBound :: BankHoliday
maxBound :: BankHoliday
Bounded, Int -> BankHoliday -> ShowS
[BankHoliday] -> ShowS
BankHoliday -> String
(Int -> BankHoliday -> ShowS)
-> (BankHoliday -> String)
-> ([BankHoliday] -> ShowS)
-> Show BankHoliday
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BankHoliday -> ShowS
showsPrec :: Int -> BankHoliday -> ShowS
$cshow :: BankHoliday -> String
show :: BankHoliday -> String
$cshowList :: [BankHoliday] -> ShowS
showList :: [BankHoliday] -> ShowS
Show, ReadPrec [BankHoliday]
ReadPrec BankHoliday
Int -> ReadS BankHoliday
ReadS [BankHoliday]
(Int -> ReadS BankHoliday)
-> ReadS [BankHoliday]
-> ReadPrec BankHoliday
-> ReadPrec [BankHoliday]
-> Read BankHoliday
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS BankHoliday
readsPrec :: Int -> ReadS BankHoliday
$creadList :: ReadS [BankHoliday]
readList :: ReadS [BankHoliday]
$creadPrec :: ReadPrec BankHoliday
readPrec :: ReadPrec BankHoliday
$creadListPrec :: ReadPrec [BankHoliday]
readListPrec :: ReadPrec [BankHoliday]
Read)


-- | Check if a given day is a 'BankHoliday'.
--
-- >>> isBankHoliday (fromGregorian 2024 1 1)
-- True
isBankHoliday :: Day -> Bool
isBankHoliday :: Day -> Bool
isBankHoliday = Maybe BankHoliday -> Bool
forall a. Maybe a -> Bool
isJust (Maybe BankHoliday -> Bool)
-> (Day -> Maybe BankHoliday) -> Day -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Day -> Maybe BankHoliday
fromDay

-- | Helper to extract the year from a date.
--
-- >>> yearFromDay $ fromGregorian 2020 1 1
-- 2020
yearFromDay :: Day -> Year
yearFromDay :: Day -> Year
yearFromDay = (\(Year
y, Int
_, Int
_) -> Year
y) ((Year, Int, Int) -> Year)
-> (Day -> (Year, Int, Int)) -> Day -> Year
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Day -> (Year, Int, Int)
toGregorian

-- | Calculate Easter Sunday using Spencer's algorithm.
calculateEasterSunday :: Year -> Day
calculateEasterSunday :: Year -> Day
calculateEasterSunday Year
year =
    let
        a :: Year
a = Year
year Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
19
        b :: Year
b = Year
year Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
100
        c :: Year
c = Year
year Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
100
        d :: Year
d = Year
b Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
4
        e :: Year
e = Year
b Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
4
        f :: Year
f = (Year
b Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
8) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
25
        g :: Year
g = (Year
b Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
f Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
1) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
3
        h :: Year
h = (Year
19 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
a Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
b Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
d Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
g Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
15) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
30
        i :: Year
i = Year
c Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
4
        k :: Year
k = Year
c Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
4
        l :: Year
l = (Year
32 Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
2 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
e Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
2 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
i Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
h Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
k) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
7
        m :: Year
m = (Year
a Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
11 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
h Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
22 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
l) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
451
        n :: Year
n = (Year
h Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
l Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
7 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
m Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
114) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`quot` Year
31
        o :: Year
o = (Year
h Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
l Year -> Year -> Year
forall a. Num a => a -> a -> a
- Year
7 Year -> Year -> Year
forall a. Num a => a -> a -> a
* Year
m Year -> Year -> Year
forall a. Num a => a -> a -> a
+ Year
114) Year -> Year -> Year
forall a. Integral a => a -> a -> a
`rem` Year
31
    in
        Year -> Int -> Int -> Day
fromGregorian Year
year (Year -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Year
n) (Year -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Year
o Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)

-- | Compute the date for a given year and bank holiday.
--
-- >>> toDay 2024 LabourDay
-- 2024-05-01
toDay :: Year -> BankHoliday -> Day
toDay :: Year -> BankHoliday -> Day
toDay Year
year BankHoliday
NewYearsDay        = Year -> Int -> Int -> Day
fromGregorian Year
year Int
1 Int
1
toDay Year
year BankHoliday
GoodFriday         = Year -> Day -> Day
addDays (-Year
2) (Year -> Day
calculateEasterSunday Year
year)
toDay Year
year BankHoliday
EasterMonday       = Year -> Day -> Day
addDays Year
1 (Year -> Day
calculateEasterSunday Year
year)
toDay Year
year BankHoliday
LabourDay          = Year -> Int -> Int -> Day
fromGregorian Year
year Int
5 Int
1
toDay Year
year BankHoliday
AscensionDay       = Year -> Day -> Day
addDays Year
39 (Year -> Day
calculateEasterSunday Year
year)
toDay Year
year BankHoliday
WhitMonday         = Year -> Day -> Day
addDays Year
50 (Year -> Day
calculateEasterSunday Year
year)
toDay Year
year BankHoliday
GermanUnityDay     = Year -> Int -> Int -> Day
fromGregorian Year
year Int
10 Int
3
toDay Year
year BankHoliday
ChristmasEve       = Year -> Int -> Int -> Day
fromGregorian Year
year Int
12 Int
24
toDay Year
year BankHoliday
ChristmasDay       = Year -> Int -> Int -> Day
fromGregorian Year
year Int
12 Int
25
toDay Year
year BankHoliday
SecondChristmasDay = Year -> Int -> Int -> Day
fromGregorian Year
year Int
12 Int
26
toDay Year
year BankHoliday
NewYearsEve        = Year -> Int -> Int -> Day
fromGregorian Year
year Int
12 Int
31

-- | Compute 'Maybe' the holiday for a given date.
--
-- Note: In some years, two bank holidays can fall on the same
-- day. E.g. 'LabourDay' and 'AscensionDay' in 2008 are both on
-- 2008-05-01. In such cases this function returns the bank holiday
-- that is defined first in the 'BankHoliday' 'Enum'.
--
-- >>> fromDay (fromGregorian 2024 1 1)
-- Just NewYearsDay
--
-- >>> fromDay (fromGregorian 2024 5 5)
-- Nothing
fromDay :: Day -> Maybe BankHoliday
fromDay :: Day -> Maybe BankHoliday
fromDay Day
day = [BankHoliday] -> Maybe BankHoliday
forall a. [a] -> Maybe a
listToMaybe ([BankHoliday] -> Maybe BankHoliday)
-> [BankHoliday] -> Maybe BankHoliday
forall a b. (a -> b) -> a -> b
$ (BankHoliday -> Bool) -> [BankHoliday] -> [BankHoliday]
forall a. (a -> Bool) -> [a] -> [a]
filter (\BankHoliday
d -> Day
day Day -> Day -> Bool
forall a. Eq a => a -> a -> Bool
== Year -> BankHoliday -> Day
toDay (Day -> Year
yearFromDay Day
day) BankHoliday
d) [BankHoliday
forall a. Bounded a => a
minBound..BankHoliday
forall a. Bounded a => a
maxBound]

-- | Compute pairs of date and holiday from start to end (inclusive).
--
-- Note: In some years, two bank holidays can fall on the same
-- day. In such cases only one of them is in the resulting list.
-- See 'fromDay' for more information.
--
-- >>> map snd $ holidaysBetween (fromGregorian 2024 12 25) (fromGregorian 2024 12 26)
-- [ChristmasDay,SecondChristmasDay]
holidaysBetween :: Day -> Day -> [(Day, BankHoliday)]
holidaysBetween :: Day -> Day -> [(Day, BankHoliday)]
holidaysBetween Day
start Day
end = [Maybe (Day, BankHoliday)] -> [(Day, BankHoliday)]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe (Day, BankHoliday)] -> [(Day, BankHoliday)])
-> [Maybe (Day, BankHoliday)] -> [(Day, BankHoliday)]
forall a b. (a -> b) -> a -> b
$ (Day -> Maybe (Day, BankHoliday))
-> [Day] -> [Maybe (Day, BankHoliday)]
forall a b. (a -> b) -> [a] -> [b]
map (\Day
d -> (Day
d,) (BankHoliday -> (Day, BankHoliday))
-> Maybe BankHoliday -> Maybe (Day, BankHoliday)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Day -> Maybe BankHoliday
fromDay Day
d) [Day
start..Day
end]

-- | Translate the holiday name to German.
germanHolidayName :: BankHoliday -> String
germanHolidayName :: BankHoliday -> String
germanHolidayName BankHoliday
d = case BankHoliday
d of
  BankHoliday
NewYearsDay        -> String
"Neujahrstag"
  BankHoliday
GoodFriday         -> String
"Karfreitag"
  BankHoliday
EasterMonday       -> String
"Ostermontag"
  BankHoliday
LabourDay          -> String
"Tag der Arbeit"
  BankHoliday
AscensionDay       -> String
"Christi Himmelfahrt"
  BankHoliday
WhitMonday         -> String
"Pfingstmontag"
  BankHoliday
GermanUnityDay     -> String
"Tag der Deutschen Einheit"
  BankHoliday
ChristmasEve       -> String
"Heilig Abend"
  BankHoliday
ChristmasDay       -> String
"1. Weihnachtsfeiertag"
  BankHoliday
SecondChristmasDay -> String
"2. Weihnachtsfeiertag"
  BankHoliday
NewYearsEve        -> String
"Silvestertag"

-- | True only for German public holidays aka legal holidays.
-- Chrismas Eve and New Year's Eve are bank holidays but not public holidays.
isPublicHoliday :: BankHoliday -> Bool
isPublicHoliday :: BankHoliday -> Bool
isPublicHoliday BankHoliday
ChristmasEve = Bool
False
isPublicHoliday BankHoliday
NewYearsEve = Bool
False
isPublicHoliday BankHoliday
_ = Bool
True