{-|

This module provides additional German public holidays that are not
covered by the [bank holidays]("Data.Time.Calendar.BankHoliday.Germany").

Public holidays – except for
'Data.Time.Calendar.BankHoliday.Germany.GermanUnityDay' – are under
federal obligations in Germany („Ländersache“).

Most bank holidays are also federal public holidays
(see 'Data.Time.Calendar.BankHoliday.Germany.isPublicHoliday').
But there are some additional extra holidays which may differ between
federal states.

For example, Heilige Drei Könige is not a bank holiday but it is a
public holiday in Bavaria.

Note: The extra holidays are currently only implemented for
Baden-Württemberg, Bayern, Berlin, Niedersachsen, Hessen, and
Nordrhein-Westfalen.

Example for computing all public holidays in Bavaria (Landkreis
Miesbach, Oberbayern) in the next couple years:

@
import Prelude
import Data.List
import Data.Time
import qualified Data.Time.Calendar.BankHoliday.Germany as BH
import qualified Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays as EH

start = fromGregorian 2024 1 1

end = fromGregorian 2025 12 31

holidays :: [[String]]
holidays = map (\(x,y) -> [show x, BH.germanHolidayName y]) (filter (BH.isPublicHoliday . snd) $ BH.holidaysBetween start end)
        ++ map (\(x,y) -> [show x, EH.germanHolidayName y]) (filter ((/= EH.Friedensfest) . snd) $ EH.holidaysBetween EH.Bayern start end)

main :: IO ()
main = putStrLn $ unlines $ sort $ map unwords holidays
@

@
2024-01-01 Neujahrstag
2024-01-06 Heilige Drei Könige
2024-03-29 Karfreitag
2024-04-01 Ostermontag
2024-05-01 Tag der Arbeit
2024-05-09 Christi Himmelfahrt
2024-05-20 Pfingstmontag
2024-05-30 Fronleichnam
2024-08-15 Mariä Himmelfahrt
2024-10-03 Tag der Deutschen Einheit
2024-11-01 Allerheiligen
2024-12-25 1. Weihnachtsfeiertag
2024-12-26 2. Weihnachtsfeiertag
2025-01-01 Neujahrstag
2025-01-06 Heilige Drei Könige
2025-04-18 Karfreitag
2025-04-21 Ostermontag
2025-05-01 Tag der Arbeit
2025-05-29 Christi Himmelfahrt
2025-06-09 Pfingstmontag
2025-06-19 Fronleichnam
2025-08-15 Mariä Himmelfahrt
2025-10-03 Tag der Deutschen Einheit
2025-11-01 Allerheiligen
2025-12-25 1. Weihnachtsfeiertag
2025-12-26 2. Weihnachtsfeiertag
@

Resources:

 - Übersicht: https://de.wikipedia.org/wiki/Gesetzliche_Feiertage_in_Deutschland
 - Weitere Übersicht: https://www.arbeitstage.org/
 - Bayern: https://www.stmi.bayern.de/suv/feiertage/
 - Baden-Württemberg: https://im.baden-wuerttemberg.de/de/service/feiertage
 - Niedersachsen: https://service.niedersachsen.de/portaldeeplink/?tsa_leistung_id=8664664&tsa_sprache=de_DE
 - Hessen: https://innen.hessen.de/buerger-staat/feiertage

-}

module Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays (
    ExtraHoliday(..),
    FederalState(..),
    holidaysBetween,
    fromDay,
    toDay,
    germanHolidayName,
    isHolidayInState
) where

import Prelude
import Data.Maybe
import Data.Time.Calendar
import Data.Time.Calendar.BankHoliday.Germany (calculateEasterSunday, yearFromDay)

-- | Germany's federal states – Deutsche Bundesländer.
data FederalState
  = BadenWuerttemberg
  | Bayern
  | Berlin
  | Brandenburg
  | Bremen
  | Hamburg
  | Hessen
  | MecklenburgVorpommern
  | Niedersachsen
  | NordrheinWestfalen
  | RheinlandPfalz
  | Saarland
  | Sachsen
  | SachsenAnhalt
  | SchleswigHolstein
  | Thueringen
  deriving (Int -> FederalState
FederalState -> Int
FederalState -> [FederalState]
FederalState -> FederalState
FederalState -> FederalState -> [FederalState]
FederalState -> FederalState -> FederalState -> [FederalState]
(FederalState -> FederalState)
-> (FederalState -> FederalState)
-> (Int -> FederalState)
-> (FederalState -> Int)
-> (FederalState -> [FederalState])
-> (FederalState -> FederalState -> [FederalState])
-> (FederalState -> FederalState -> [FederalState])
-> (FederalState -> FederalState -> FederalState -> [FederalState])
-> Enum FederalState
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 :: FederalState -> FederalState
succ :: FederalState -> FederalState
$cpred :: FederalState -> FederalState
pred :: FederalState -> FederalState
$ctoEnum :: Int -> FederalState
toEnum :: Int -> FederalState
$cfromEnum :: FederalState -> Int
fromEnum :: FederalState -> Int
$cenumFrom :: FederalState -> [FederalState]
enumFrom :: FederalState -> [FederalState]
$cenumFromThen :: FederalState -> FederalState -> [FederalState]
enumFromThen :: FederalState -> FederalState -> [FederalState]
$cenumFromTo :: FederalState -> FederalState -> [FederalState]
enumFromTo :: FederalState -> FederalState -> [FederalState]
$cenumFromThenTo :: FederalState -> FederalState -> FederalState -> [FederalState]
enumFromThenTo :: FederalState -> FederalState -> FederalState -> [FederalState]
Enum, FederalState -> FederalState -> Bool
(FederalState -> FederalState -> Bool)
-> (FederalState -> FederalState -> Bool) -> Eq FederalState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FederalState -> FederalState -> Bool
== :: FederalState -> FederalState -> Bool
$c/= :: FederalState -> FederalState -> Bool
/= :: FederalState -> FederalState -> Bool
Eq, FederalState
FederalState -> FederalState -> Bounded FederalState
forall a. a -> a -> Bounded a
$cminBound :: FederalState
minBound :: FederalState
$cmaxBound :: FederalState
maxBound :: FederalState
Bounded, Int -> FederalState -> ShowS
[FederalState] -> ShowS
FederalState -> String
(Int -> FederalState -> ShowS)
-> (FederalState -> String)
-> ([FederalState] -> ShowS)
-> Show FederalState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FederalState -> ShowS
showsPrec :: Int -> FederalState -> ShowS
$cshow :: FederalState -> String
show :: FederalState -> String
$cshowList :: [FederalState] -> ShowS
showList :: [FederalState] -> ShowS
Show, ReadPrec [FederalState]
ReadPrec FederalState
Int -> ReadS FederalState
ReadS [FederalState]
(Int -> ReadS FederalState)
-> ReadS [FederalState]
-> ReadPrec FederalState
-> ReadPrec [FederalState]
-> Read FederalState
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS FederalState
readsPrec :: Int -> ReadS FederalState
$creadList :: ReadS [FederalState]
readList :: ReadS [FederalState]
$creadPrec :: ReadPrec FederalState
readPrec :: ReadPrec FederalState
$creadListPrec :: ReadPrec [FederalState]
readListPrec :: ReadPrec [FederalState]
Read)

-- TODO: Remove note below when all federal states are fully implemented.

-- | Extra federal holidays, no overlap with
-- 'Data.Time.Calendar.BankHoliday.Germany.BankHoliday'.
-- Spezielle Feiertage der Bundesländer.
--
-- Note: Currently, only some federal states' extra holidays are implemented.
-- See module description above for details.
--
-- \*regional holiday, only applies in parts of the federal state
data ExtraHoliday
  = HeiligeDreiKoenige     -- ^ Heilige Drei Könige (Bayern, Baden-Württemberg, …)
  | Fronleichnam           -- ^ Fronleichnam (Bayern, Baden-Württemberg, Nordrhein-Westfalen, Hessen, …)
  | Friedensfest           -- ^ Friedensfest (Bayern*, …)
  | MariaeHimmelfahrt      -- ^ Mariä Himmelfahrt (Bayern*, …)
  | Allerheiligen          -- ^ Allerheiligen (Bayern, Baden-Württemberg, Nordrhein-Westfalen, …)
  | Reformationstag        -- ^ Reformationstag (Niedersachsen, …)
  | InternationalerFrauentag -- ^ Internationaler Frauentag (Berlin, …)
  deriving (Int -> ExtraHoliday
ExtraHoliday -> Int
ExtraHoliday -> [ExtraHoliday]
ExtraHoliday -> ExtraHoliday
ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
ExtraHoliday -> ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
(ExtraHoliday -> ExtraHoliday)
-> (ExtraHoliday -> ExtraHoliday)
-> (Int -> ExtraHoliday)
-> (ExtraHoliday -> Int)
-> (ExtraHoliday -> [ExtraHoliday])
-> (ExtraHoliday -> ExtraHoliday -> [ExtraHoliday])
-> (ExtraHoliday -> ExtraHoliday -> [ExtraHoliday])
-> (ExtraHoliday -> ExtraHoliday -> ExtraHoliday -> [ExtraHoliday])
-> Enum ExtraHoliday
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 :: ExtraHoliday -> ExtraHoliday
succ :: ExtraHoliday -> ExtraHoliday
$cpred :: ExtraHoliday -> ExtraHoliday
pred :: ExtraHoliday -> ExtraHoliday
$ctoEnum :: Int -> ExtraHoliday
toEnum :: Int -> ExtraHoliday
$cfromEnum :: ExtraHoliday -> Int
fromEnum :: ExtraHoliday -> Int
$cenumFrom :: ExtraHoliday -> [ExtraHoliday]
enumFrom :: ExtraHoliday -> [ExtraHoliday]
$cenumFromThen :: ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
enumFromThen :: ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
$cenumFromTo :: ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
enumFromTo :: ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
$cenumFromThenTo :: ExtraHoliday -> ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
enumFromThenTo :: ExtraHoliday -> ExtraHoliday -> ExtraHoliday -> [ExtraHoliday]
Enum, ExtraHoliday -> ExtraHoliday -> Bool
(ExtraHoliday -> ExtraHoliday -> Bool)
-> (ExtraHoliday -> ExtraHoliday -> Bool) -> Eq ExtraHoliday
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ExtraHoliday -> ExtraHoliday -> Bool
== :: ExtraHoliday -> ExtraHoliday -> Bool
$c/= :: ExtraHoliday -> ExtraHoliday -> Bool
/= :: ExtraHoliday -> ExtraHoliday -> Bool
Eq, ExtraHoliday
ExtraHoliday -> ExtraHoliday -> Bounded ExtraHoliday
forall a. a -> a -> Bounded a
$cminBound :: ExtraHoliday
minBound :: ExtraHoliday
$cmaxBound :: ExtraHoliday
maxBound :: ExtraHoliday
Bounded, Int -> ExtraHoliday -> ShowS
[ExtraHoliday] -> ShowS
ExtraHoliday -> String
(Int -> ExtraHoliday -> ShowS)
-> (ExtraHoliday -> String)
-> ([ExtraHoliday] -> ShowS)
-> Show ExtraHoliday
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ExtraHoliday -> ShowS
showsPrec :: Int -> ExtraHoliday -> ShowS
$cshow :: ExtraHoliday -> String
show :: ExtraHoliday -> String
$cshowList :: [ExtraHoliday] -> ShowS
showList :: [ExtraHoliday] -> ShowS
Show, ReadPrec [ExtraHoliday]
ReadPrec ExtraHoliday
Int -> ReadS ExtraHoliday
ReadS [ExtraHoliday]
(Int -> ReadS ExtraHoliday)
-> ReadS [ExtraHoliday]
-> ReadPrec ExtraHoliday
-> ReadPrec [ExtraHoliday]
-> Read ExtraHoliday
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS ExtraHoliday
readsPrec :: Int -> ReadS ExtraHoliday
$creadList :: ReadS [ExtraHoliday]
readList :: ReadS [ExtraHoliday]
$creadPrec :: ReadPrec ExtraHoliday
readPrec :: ReadPrec ExtraHoliday
$creadListPrec :: ReadPrec [ExtraHoliday]
readListPrec :: ReadPrec [ExtraHoliday]
Read)

-- | Compute the date for a given year and extra holiday.
--
-- >>> toDay 2024 HeiligeDreiKoenige
-- 2024-01-06
toDay :: Year -> ExtraHoliday -> Day
toDay :: Year -> ExtraHoliday -> Day
toDay Year
year ExtraHoliday
HeiligeDreiKoenige      = Year -> Int -> Int -> Day
fromGregorian Year
year Int
1 Int
6
toDay Year
year ExtraHoliday
Fronleichnam            = Year -> Day -> Day
addDays Year
60 (Day -> Day) -> Day -> Day
forall a b. (a -> b) -> a -> b
$ Year -> Day
calculateEasterSunday Year
year
toDay Year
year ExtraHoliday
Friedensfest            = Year -> Int -> Int -> Day
fromGregorian Year
year Int
8 Int
8
toDay Year
year ExtraHoliday
MariaeHimmelfahrt       = Year -> Int -> Int -> Day
fromGregorian Year
year Int
8 Int
15
toDay Year
year ExtraHoliday
Allerheiligen           = Year -> Int -> Int -> Day
fromGregorian Year
year Int
11 Int
1
toDay Year
year ExtraHoliday
InternationalerFrauentag = Year -> Int -> Int -> Day
fromGregorian Year
year Int
3 Int
8
toDay Year
year ExtraHoliday
Reformationstag          = Year -> Int -> Int -> Day
fromGregorian Year
year Int
10 Int
31

-- | Compute 'Maybe' the holiday for a given date.
--
-- Note: In some years, two extra holidays may fall on the same
-- day. In such cases this function returns the holiday
-- that is defined first in the 'ExtraHoliday' 'Enum'.
--
-- >>> fromDay (fromGregorian 2024 11 1)
-- Just Allerheiligen
--
-- >>> fromDay (fromGregorian 2024 5 5)
-- Nothing
fromDay :: Day -> Maybe ExtraHoliday
fromDay :: Day -> Maybe ExtraHoliday
fromDay Day
day = [ExtraHoliday] -> Maybe ExtraHoliday
forall a. [a] -> Maybe a
listToMaybe ([ExtraHoliday] -> Maybe ExtraHoliday)
-> [ExtraHoliday] -> Maybe ExtraHoliday
forall a b. (a -> b) -> a -> b
$ (ExtraHoliday -> Bool) -> [ExtraHoliday] -> [ExtraHoliday]
forall a. (a -> Bool) -> [a] -> [a]
filter (\ExtraHoliday
d -> Day
day Day -> Day -> Bool
forall a. Eq a => a -> a -> Bool
== Year -> ExtraHoliday -> Day
toDay (Day -> Year
yearFromDay Day
day) ExtraHoliday
d) [ExtraHoliday
forall a. Bounded a => a
minBound..ExtraHoliday
forall a. Bounded a => a
maxBound]

-- | Compute pairs of date and holiday from start to end (inclusive) for the given federal state.
--
-- >>> map snd $ holidaysBetween Bayern (fromGregorian 2024 8 8) (fromGregorian 2024 8 15)
-- [Friedensfest,MariaeHimmelfahrt]
holidaysBetween :: FederalState -> Day -> Day -> [(Day, ExtraHoliday)]
holidaysBetween :: FederalState -> Day -> Day -> [(Day, ExtraHoliday)]
holidaysBetween FederalState
state Day
start Day
end = ((Day, ExtraHoliday) -> Bool)
-> [(Day, ExtraHoliday)] -> [(Day, ExtraHoliday)]
forall a. (a -> Bool) -> [a] -> [a]
filter (FederalState -> ExtraHoliday -> Bool
isHolidayInState FederalState
state (ExtraHoliday -> Bool)
-> ((Day, ExtraHoliday) -> ExtraHoliday)
-> (Day, ExtraHoliday)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Day, ExtraHoliday) -> ExtraHoliday
forall a b. (a, b) -> b
snd) ([(Day, ExtraHoliday)] -> [(Day, ExtraHoliday)])
-> [(Day, ExtraHoliday)] -> [(Day, ExtraHoliday)]
forall a b. (a -> b) -> a -> b
$ [Maybe (Day, ExtraHoliday)] -> [(Day, ExtraHoliday)]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe (Day, ExtraHoliday)] -> [(Day, ExtraHoliday)])
-> [Maybe (Day, ExtraHoliday)] -> [(Day, ExtraHoliday)]
forall a b. (a -> b) -> a -> b
$ (Day -> Maybe (Day, ExtraHoliday))
-> [Day] -> [Maybe (Day, ExtraHoliday)]
forall a b. (a -> b) -> [a] -> [b]
map (\Day
d -> (Day
d,) (ExtraHoliday -> (Day, ExtraHoliday))
-> Maybe ExtraHoliday -> Maybe (Day, ExtraHoliday)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Day -> Maybe ExtraHoliday
fromDay Day
d) [Day
start..Day
end]

-- | Translate the holiday name to German.
germanHolidayName :: ExtraHoliday -> String
germanHolidayName :: ExtraHoliday -> String
germanHolidayName ExtraHoliday
d = case ExtraHoliday
d of
  ExtraHoliday
HeiligeDreiKoenige     -> String
"Heilige Drei Könige"
  ExtraHoliday
Fronleichnam           -> String
"Fronleichnam"
  ExtraHoliday
Friedensfest           -> String
"Friedensfest"
  ExtraHoliday
MariaeHimmelfahrt      -> String
"Mariä Himmelfahrt"
  ExtraHoliday
Allerheiligen          -> String
"Allerheiligen"
  ExtraHoliday
Reformationstag          -> String
"Reformationstag"
  ExtraHoliday
InternationalerFrauentag -> String
"Internationaler Frauentag"

-- | Check if 'ExtraHoliday' is a holiday in the given federal state.
--
-- >>> isHolidayInState Bayern Allerheiligen
-- True
--
-- >>> isHolidayInState Berlin Allerheiligen
-- False
isHolidayInState :: FederalState -> ExtraHoliday -> Bool
isHolidayInState :: FederalState -> ExtraHoliday -> Bool
isHolidayInState FederalState
BadenWuerttemberg ExtraHoliday
HeiligeDreiKoenige = Bool
True
isHolidayInState FederalState
BadenWuerttemberg ExtraHoliday
Fronleichnam = Bool
True
isHolidayInState FederalState
BadenWuerttemberg ExtraHoliday
Allerheiligen = Bool
True
isHolidayInState FederalState
Bayern ExtraHoliday
HeiligeDreiKoenige = Bool
True
isHolidayInState FederalState
Bayern ExtraHoliday
Fronleichnam = Bool
True
isHolidayInState FederalState
Bayern ExtraHoliday
Friedensfest = Bool
True
isHolidayInState FederalState
Bayern ExtraHoliday
MariaeHimmelfahrt = Bool
True
isHolidayInState FederalState
Bayern ExtraHoliday
Allerheiligen = Bool
True
isHolidayInState FederalState
Berlin ExtraHoliday
InternationalerFrauentag = Bool
True
isHolidayInState FederalState
NordrheinWestfalen ExtraHoliday
Fronleichnam = Bool
True
isHolidayInState FederalState
NordrheinWestfalen ExtraHoliday
Allerheiligen = Bool
True
isHolidayInState FederalState
Niedersachsen ExtraHoliday
Reformationstag = Bool
True
isHolidayInState FederalState
Hessen ExtraHoliday
Fronleichnam = Bool
True
isHolidayInState FederalState
_ ExtraHoliday
_ = Bool
False