packages feed

bank-holiday-germany 1.3.1.0 → 2.0.0.0

raw patch · 7 files changed

+597/−523 lines, 7 filesdep ~hedgehogPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: hedgehog

API changes (from Hackage documentation)

- Data.Time.Calendar.BankHoliday.Germany: AscensionDay :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: ChristmasDay :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: ChristmasEve :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: EasterMonday :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: GermanUnityDay :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: GoodFriday :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: LabourDay :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: NewYearsDay :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: NewYearsEve :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: SecondChristmasDay :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: WhitMonday :: BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: calculateEasterSunday :: Year -> Day
- Data.Time.Calendar.BankHoliday.Germany: data BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: fromDay :: Day -> Maybe BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: germanHolidayName :: BankHoliday -> String
- Data.Time.Calendar.BankHoliday.Germany: holidaysBetween :: Day -> Day -> [(Day, BankHoliday)]
- Data.Time.Calendar.BankHoliday.Germany: instance GHC.Classes.Eq Data.Time.Calendar.BankHoliday.Germany.BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: instance GHC.Enum.Bounded Data.Time.Calendar.BankHoliday.Germany.BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: instance GHC.Enum.Enum Data.Time.Calendar.BankHoliday.Germany.BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: instance GHC.Read.Read Data.Time.Calendar.BankHoliday.Germany.BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: instance GHC.Show.Show Data.Time.Calendar.BankHoliday.Germany.BankHoliday
- Data.Time.Calendar.BankHoliday.Germany: isBankHoliday :: Day -> Bool
- Data.Time.Calendar.BankHoliday.Germany: isPublicHoliday :: BankHoliday -> Bool
- Data.Time.Calendar.BankHoliday.Germany: toDay :: Year -> BankHoliday -> Day
- Data.Time.Calendar.BankHoliday.Germany: yearFromDay :: Day -> Year
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Allerheiligen :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: BadenWuerttemberg :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Bayern :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Berlin :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Brandenburg :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Bremen :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: BussUndBettag :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Friedensfest :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Fronleichnam :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Hamburg :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: HeiligeDreiKoenige :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Hessen :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: InternationalerFrauentag :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: MariaeHimmelfahrt :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: MecklenburgVorpommern :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Niedersachsen :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: NordrheinWestfalen :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Reformationstag :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: RheinlandPfalz :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Saarland :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Sachsen :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: SachsenAnhalt :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: SchleswigHolstein :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Thueringen :: FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: Weltkindertag :: ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: data ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: data FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: fromDay :: Day -> Maybe ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: germanHolidayName :: ExtraHoliday -> String
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: holidaysBetween :: FederalState -> Day -> Day -> [(Day, ExtraHoliday)]
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Classes.Eq Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Classes.Eq Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Enum.Bounded Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Enum.Bounded Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Enum.Enum Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Enum.Enum Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Read.Read Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Read.Read Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Show.Show Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.ExtraHoliday
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: instance GHC.Show.Show Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays.FederalState
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: isHolidayInState :: FederalState -> ExtraHoliday -> Bool
- Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays: toDay :: Year -> ExtraHoliday -> Day
+ Data.Holiday.Germany: Allerheiligen :: Holiday
+ Data.Holiday.Germany: BadenWuerttemberg :: FederalState
+ Data.Holiday.Germany: Bayern :: FederalState
+ Data.Holiday.Germany: Berlin :: FederalState
+ Data.Holiday.Germany: Brandenburg :: FederalState
+ Data.Holiday.Germany: Bremen :: FederalState
+ Data.Holiday.Germany: BussUndBettag :: Holiday
+ Data.Holiday.Germany: ChristiHimmelfahrt :: Holiday
+ Data.Holiday.Germany: ErsterMai :: Holiday
+ Data.Holiday.Germany: ErsterWeihnachtsfeiertag :: Holiday
+ Data.Holiday.Germany: Friedensfest :: Holiday
+ Data.Holiday.Germany: Fronleichnam :: Holiday
+ Data.Holiday.Germany: Hamburg :: FederalState
+ Data.Holiday.Germany: Heiligabend :: Holiday
+ Data.Holiday.Germany: HeiligeDreiKoenige :: Holiday
+ Data.Holiday.Germany: Hessen :: FederalState
+ Data.Holiday.Germany: InternationalerFrauentag :: Holiday
+ Data.Holiday.Germany: Karfreitag :: Holiday
+ Data.Holiday.Germany: MariaeHimmelfahrt :: Holiday
+ Data.Holiday.Germany: MecklenburgVorpommern :: FederalState
+ Data.Holiday.Germany: Neujahrstag :: Holiday
+ Data.Holiday.Germany: Niedersachsen :: FederalState
+ Data.Holiday.Germany: NordrheinWestfalen :: FederalState
+ Data.Holiday.Germany: Ostermontag :: Holiday
+ Data.Holiday.Germany: Pfingstmontag :: Holiday
+ Data.Holiday.Germany: Reformationstag :: Holiday
+ Data.Holiday.Germany: RheinlandPfalz :: FederalState
+ Data.Holiday.Germany: Saarland :: FederalState
+ Data.Holiday.Germany: Sachsen :: FederalState
+ Data.Holiday.Germany: SachsenAnhalt :: FederalState
+ Data.Holiday.Germany: SchleswigHolstein :: FederalState
+ Data.Holiday.Germany: Silvestertag :: Holiday
+ Data.Holiday.Germany: TagDerDeutschenEinheit :: Holiday
+ Data.Holiday.Germany: Thueringen :: FederalState
+ Data.Holiday.Germany: Weltkindertag :: Holiday
+ Data.Holiday.Germany: ZweiterWeihnachtsfeiertag :: Holiday
+ Data.Holiday.Germany: calculateEasterSunday :: Year -> Day
+ Data.Holiday.Germany: data FederalState
+ Data.Holiday.Germany: data Holiday
+ Data.Holiday.Germany: fromDay :: Day -> [Holiday]
+ Data.Holiday.Germany: germanHolidayName :: Holiday -> String
+ Data.Holiday.Germany: holidaysBetween :: Day -> Day -> [(Day, Holiday)]
+ Data.Holiday.Germany: instance GHC.Classes.Eq Data.Holiday.Germany.FederalState
+ Data.Holiday.Germany: instance GHC.Classes.Eq Data.Holiday.Germany.Holiday
+ Data.Holiday.Germany: instance GHC.Enum.Bounded Data.Holiday.Germany.FederalState
+ Data.Holiday.Germany: instance GHC.Enum.Bounded Data.Holiday.Germany.Holiday
+ Data.Holiday.Germany: instance GHC.Enum.Enum Data.Holiday.Germany.FederalState
+ Data.Holiday.Germany: instance GHC.Enum.Enum Data.Holiday.Germany.Holiday
+ Data.Holiday.Germany: instance GHC.Read.Read Data.Holiday.Germany.FederalState
+ Data.Holiday.Germany: instance GHC.Read.Read Data.Holiday.Germany.Holiday
+ Data.Holiday.Germany: instance GHC.Show.Show Data.Holiday.Germany.FederalState
+ Data.Holiday.Germany: instance GHC.Show.Show Data.Holiday.Germany.Holiday
+ Data.Holiday.Germany: isBankHoliday :: Holiday -> Bool
+ Data.Holiday.Germany: isFederalPublicHoliday :: FederalState -> Holiday -> Bool
+ Data.Holiday.Germany: isGermanPublicHoliday :: Holiday -> Bool
+ Data.Holiday.Germany: toDay :: Year -> Holiday -> Day
+ Data.Holiday.Germany: yearFromDay :: Day -> Year

Files

CHANGELOG.md view
@@ -4,32 +4,41 @@ and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). -## [1.0.0.0] - 2024-03-14+## [2.0.0.0] - 2025-04-13 -Initial release+Breaking changes due to extensive refactoring! -## [1.0.0.1] - 2024-03-14+### Fixed -### Added+- The Weltkindertag on September 20 is a public holiday in Thüringen+  (since 2019). It was missing in this package until now. -- Added version bounds of dependencies+### Changed -## [1.0.0.2] - 2024-03-18+- Renamed all type constructors to their German names,+  e.g. `Heiligabend` instead of `ChristmasEve`. This was changed to be+  consistent with the former `ExtraHoliday` type.+- Changed `fromDay` to return a `[a]` instead of `Maybe a` because+  there might be different holidays from different federal states on+  the same day in future.+- Combined modules `Data.Time.Calendar.BankHoliday.Germany`+  and `Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays`+  into `Data.Holiday.Germany`.+- Combined types `BankHoliday` and `ExtraHoliday` into new type `Holiday`.+- Adapted all functions for new types. -### Added+## [1.3.1.0] - 2025-04-13 -- Add module `ExtraHolidays` for additional public holidays-- Export `dayToYear` helper function since it is also used by the new module-- Add tests-- Enhance docs+### Fixed -## [1.1.0.0] - 2024-03-19+- Added missing Weltkindertag for Thüringen (September 20) -### Added+## [1.3.0.0] - 2024-04-01 -- Add `ExtraHoliday` for Bundesland Berlin-- Add and enhance docs+### Added +- Add extra holidays for all remaining federal states.+- Update doc ## [1.2.0.0] - 2024-03-22  ### Added@@ -38,16 +47,28 @@   Hessen, and Niedersachsen - Update doc -## [1.3.0.0] - 2024-04-01+## [1.1.0.0] - 2024-03-19  ### Added -- Add extra holidays for all remaining federal states.-- Update doc+- Add `ExtraHoliday` for Bundesland Berlin+- Add and enhance docs +## [1.0.0.2] - 2024-03-18 -## [1.3.1.0] - 2025-04-13+### Added -### Fixed+- Add module `ExtraHolidays` for additional public holidays+- Export `dayToYear` helper function since it is also used by the new module+- Add tests+- Enhance docs -- Added missing Weltkindertag for Thüringen (September 20)+## [1.0.0.1] - 2024-03-14++### Added++- Added version bounds of dependencies++## [1.0.0.0] - 2024-03-14++Initial release
README.md view
@@ -7,10 +7,9 @@  Most bank holidays are also public aka legal holidays throughout Germany. You can use `isPublicHoliday` to check if a-bank holiday is also a public holiday.+holiday is also a public holiday. -There are even more public holidays in each federal state which-are covered by the `ExtraHolidays` module of this package.+There are even more public holidays in each federal state.  Public holidays are generally off for all employees. Bank holidays that are not public holidays are generally only off for bank employees.@@ -29,7 +28,7 @@ ein Bankfeiertag auch ein gesetzlicher Feiertag ist.  Darüber hinaus gibt es je nach Bundesland weitere gesetzliche Feiertage.-Diese finden sich im Modul `ExtraHolidays`.+ **Für alle 16 Bundesländer sind damit die jeweiligen Feiertage vollständig implementiert (Stand 2024-03-31).** @@ -45,19 +44,145 @@ [Nationalfeiertag](https://www.bmi.bund.de/DE/themen/verfassung/staatliche-symbole/nationale-feiertage/nationale-feiertage-node.html) *Tag der Deutschen Einheit*. -Ein Code-Beispiel im Modul `ExtraHolidays` zeigt, wie alle Feiertage-für ein bestimmtes Bundesland berechnet werden können.+## A rewrite to version 2 -## Sample code+Sorry for the incompatible changes introduced by the rewrite to version 2.+The rational for the big refactoring was to simplify the library's+interface by unifying `BankHoliday` and `ExtraHoliday` types. +### How to migrate to version 2++The following functions haven't changed semantically:++```haskell+toDay :: Year -> Holiday -> Day+fromDay :: Day -> [Holiday]+holidaysBetween :: Day -> Day -> [(Day, Holiday)]+germanHolidayName :: Holiday -> String+```++However, they are now returning more holidays – not only bank holidays+but the union of all kind of holidays of all federal states.++It's now on you to filter for the holidays you're interested in. You+can use the following functions:++```haskell+isBankHoliday :: Holiday -> Bool+isGermanPublicHoliday :: Holiday -> Bool+isFederalPublicHoliday :: FederalState -> Holiday -> Bool+```++Changes:++- `isPublicHoliday` has been renamed to `isGermanPublicHoliday`.+- `isBankHoliday` now takes a `Holiday` instead of a `Day`.+- The namespace changed from `Data.Time.Calendar.BankHoliday.Germany`+  to `Data.Holiday.Germany`.+- `fromDay` now returns a list of holidays instead of a `Maybe`.++### Why didn't you get the design right in the first place?++For version 1, I looked on+[hackage](https://hackage.haskell.org/packages/search?terms=bank%20holiday)+for other holiday packages and there have been two conventions:++1. `bank-holiday-*` with modules like `Data.Time.Calendar.BankHoliday.*`+2. `*-holidays` with modules like `Data.Holiday.*`++I settled with the first scheme because I initially only wanted to+provide bank holidays and had no plans to support all federal+holidays. The library evolved and over time we added support for+public holidays and federal holidays. That was when the library's+interface got complicated.++The module path was long, clumsy and inaccurate because it wasn't only+about bank holidays anymore.++### Why the mix of english function names and german type constructors?++I choose to use german names for type constructors for simplicity and consistency.+It's hard enough to find a common name and notation for German+holidays (e.g. "1. Mai", "1. Maifeiertag", "Tag der Arbeit", etc.).+But it's getting harder to find translations for e.g. "Augsburger+Friedensfest" ("Augsburg High Festival of Peace" – source:+bavarikon.de – doesn't sound right).++Also, many German users of this library will have an easier time when+they don't have to look up words like "Whit Monday", "Ascension Day",+or "Epiphany".++## Sample code for version 2+ Rank federal states by number of holidays: -`test.hs`:+`test2.hs`:  ```haskell import Prelude import Data.List import Data.Time+import Data.Holiday.Germany++holidays :: Year -> FederalState -> [(Day, Holiday)]+holidays year state = filter (isFederalPublicHoliday state . snd) $ holidaysBetween start end+  where+    start = fromGregorian year 1 1+    end = fromGregorian year 12 31++supportedFederalStates :: [FederalState]+supportedFederalStates = [minBound .. maxBound]++year :: Year+year = 2025++showPadded :: Int -> String+showPadded n | n < 10 = " " ++ show n+             | otherwise = show n++main :: IO ()+main = putStrLn+         $ unlines+         $ map (\(x, n) -> showPadded n ++ "  " ++ show x)+         $ sortOn ((0-) . snd)+         $ map (\x -> (x, length $ holidays year x))+         $ supportedFederalStates+```++```+$ stack runghc --package time test2.hs+14  Bayern+12  BadenWuerttemberg+12  Saarland+12  Thueringen+11  MecklenburgVorpommern+11  NordrheinWestfalen+11  RheinlandPfalz+11  Sachsen+11  SachsenAnhalt+10  Berlin+10  Brandenburg+10  Bremen+10  Hamburg+10  Hessen+10  Niedersachsen+10  SchleswigHolstein+```++More examples:++- [Holidays](https://hackage.haskell.org/package/bank-holiday-germany/docs/Data-Holiday-Germany.html)++## Sample code for version 1++Rank federal states by number of holidays:++`test1.hs`:++```haskell+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 import Data.Time.Calendar.BankHoliday.Germany (BankHoliday(..))@@ -91,16 +216,16 @@ ```  ```-$ stack script --resolver=lts-22.0 --package time --package bank-holiday-germany test.hs+$ stack script --resolver=lts-22.0 --package time --package bank-holiday-germany test1.hs 14  Bayern 12  BadenWuerttemberg 12  Saarland+12  Thueringen 11  MecklenburgVorpommern 11  NordrheinWestfalen 11  RheinlandPfalz 11  Sachsen 11  SachsenAnhalt-11  Thueringen 10  Berlin 10  Brandenburg 10  Bremen@@ -112,5 +237,5 @@  More examples: -- [Bank holidays](https://hackage.haskell.org/package/bank-holiday-germany/docs/Data-Time-Calendar-BankHoliday-Germany.html)-- [Extra holidays](https://hackage.haskell.org/package/bank-holiday-germany/docs/Data-Time-Calendar-BankHoliday-Germany-ExtraHolidays.html)+- [Bank holidays](https://hackage.haskell.org/package/bank-holiday-germany-1.3.0.0/docs/Data-Time-Calendar-BankHoliday-Germany.html)+- [Extra holidays](https://hackage.haskell.org/package/bank-holiday-germany-1.3.0.0/docs/Data-Time-Calendar-BankHoliday-Germany-ExtraHolidays.html)
bank-holiday-germany.cabal view
@@ -1,7 +1,7 @@ cabal-version:       3.6  name:                bank-holiday-germany-version:             1.3.1.0+version:             2.0.0.0 synopsis:            German bank holidays and public holidays description:         Calculation of bank holidays and public holidays in Germany. homepage:            https://github.com/schoettl/bank-holiday-germany#readme@@ -17,8 +17,7 @@  library   hs-source-dirs:      src-  exposed-modules:     Data.Time.Calendar.BankHoliday.Germany-                     , Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays+  exposed-modules:     Data.Holiday.Germany   build-depends:       base >= 4.7 && < 5                      , time >= 1.12.2 && < 1.15   default-language:    GHC2021@@ -35,8 +34,7 @@ test-suite tests   type:                exitcode-stdio-1.0   main-is:             Main.hs-  other-modules:       Data.Time.Calendar.BankHoliday.Germany-                     , Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays+  other-modules:       Data.Holiday.Germany   hs-source-dirs:      test                      , src   build-depends:       base
+ src/Data/Holiday/Germany.hs view
@@ -0,0 +1,327 @@++{-|+Description: Calculation of bank holidays and public holidays for Germany.++This module computes bank holidays and public holidays for Germany and+its federal states.++Use 'isBankHoliday' to check if a holiday is also a bank holiday.+Use 'isGermanPublicHoliday' and 'isFederalPublicHoliday' to check if a+holiday is also a public holiday.++You can test this package or just calculate a few holidays with GHCi:++@+$ stack ghci --package time --package bank-holiday-germany+ghci> import Data.Time+ghci> import Data.Holiday.Germany+ghci> fromDay (fromGregorian 2024 5 1)  -- Tag der Arbeit+[ErsterMai]+ghci> isBankHoliday Heiligabend+True+ghci> isGermanPublicHoliday Heiligabend+False+ghci> holidaysBetween (fromGregorian 2024 12 1) (fromGregorian 2024 12 26)+[(2024-12-24,Heiligabend),(2024-12-25,ErsterWeihnachtsfeiertag),(2024-12-26,ZweiterWeihnachtsfeiertag)]+@++Public holidays – except for 'Data.Holiday.Germany.TagDerDeutschenEinheit' –+are under federal obligations in Germany („Ländersache“).++Most bank holidays are also federal public holidays and vice versa.+But there are some additional 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.++The following example prints all public holidays in Bavaria (Landkreis+Miesbach, Oberbayern) in 2025:++@+import Prelude+import Data.Time+import Data.Holiday.Germany++start = fromGregorian 2025 1 1++end = fromGregorian 2025 12 31++main :: IO ()+main = putStrLn+  $ unlines+  $ map (\\(d,x) -> show d ++ " " ++ germanHolidayName x)+  $ filter (\\(_,x) -> isFederalPublicHoliday Bayern x && x /= Friedensfest)+  $ holidaysBetween start end+@++Resources:++Public holidays are implemented for all 16 federal states. For some+states, we couldn't find official sources; That's why this list+only includes some states.++ - Bankfeiertage: https://de.wikipedia.org/wiki/Bankfeiertag+ - Ü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+ - Rheinland-Pfalz: https://mdi.rlp.de/themen/buerger-und-staat/verfassung-und-verwaltung/sonn-und-feiertagsrecht+ - Brandenburg: https://bravors.brandenburg.de/gesetze/ftg_2003/6+ - Sachsen-Anhalt: https://www.landesrecht.sachsen-anhalt.de/bsst/document/jlr-FeiertGSTrahmen/part/X+ - Thüringen: https://innen.thueringen.de/staats-und-verwaltungsrecht/oeffentliches-recht/feiertagsrecht+ - Hamburg: https://www.hamburg.de/ferien-und-feiertage/+ - Mecklenburg-Vorpommern u. Berlin (Frauentag): https://www.deutsche-rentenversicherung.de/DRV/DE/Ueber-uns-und-Presse/Presse/Meldungen/2024/240306_frauentag_feiertag_frei.html+ - Saarland: https://www.saarland.de/mibs/DE/themen-aufgaben/aufgaben/buerger_und_staat/sonn_u_feiertagsrecht/feiertagsrecht_node.html+ - Bremen: https://www.transparenz.bremen.de/metainformationen/gesetz-ueber-die-sonn-gedenk-und-feiertage-vom-12-november-1954-145882?asl=bremen203_tpgesetz.c.55340.de&template=20_gp_ifg_meta_detail_d+-}++module Data.Holiday.Germany (+    Holiday(..),+    FederalState(..),+    isBankHoliday,+    isGermanPublicHoliday,+    isFederalPublicHoliday,+    calculateEasterSunday,+    holidaysBetween,+    fromDay,+    toDay,+    germanHolidayName,+    yearFromDay+) where++import Prelude+import Data.Time.Calendar++-- | Data type specifying all German holidays.+--+-- 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 Holiday+    = Neujahrstag        -- ^ Neujahrstag+    | Karfreitag         -- ^ Karfreitag+    | Ostermontag        -- ^ Ostermontag+    | ErsterMai          -- ^ 1. Mai - Tag der Arbeit+    | ChristiHimmelfahrt -- ^ Christi Himmelfahrt+    | Pfingstmontag      -- ^ Pfingstmontag+    | TagDerDeutschenEinheit -- ^ Tag der Deutschen Einheit+    | Heiligabend        -- ^ Heilig Abend+    | ErsterWeihnachtsfeiertag  -- ^ 1​. Weihnachtsfeiertag+    | ZweiterWeihnachtsfeiertag -- ^ 2​. Weihnachtsfeiertag+    | Silvestertag           -- ^ Silvestertag+    | HeiligeDreiKoenige     -- ^ Heilige Drei Könige (Bayern, Baden-Württemberg, Sachsen-Anhalt)+    | Fronleichnam           -- ^ Fronleichnam (Bayern, Baden-Württemberg, Nordrhein-Westfalen, Hessen, Rheinland-Pfalz, Thüringen*, Saarland)+    | Friedensfest           -- ^ Friedensfest (Bayern*)+    | MariaeHimmelfahrt      -- ^ Mariä Himmelfahrt (Bayern*, Saarland)+    | Allerheiligen          -- ^ Allerheiligen (Bayern, Baden-Württemberg, Nordrhein-Westfalen, Rheinland-Pfalz, Saarland)+    | Reformationstag        -- ^ Reformationstag (Niedersachsen, Sachsen, Schleswig-Holstein, Brandenburg, Sachsen-Anhalt, Thüringen, Hamburg, Mecklenburg-Vorpommern, Bremen)+    | InternationalerFrauentag -- ^ Internationaler Frauentag (Berlin, Mecklenburg-Vorpommern)+    | BussUndBettag          -- ^ Buß- und Bettag (Sachsen)+    | Weltkindertag          -- ^ Weltkindertag (Thüringen)+    deriving (Enum, Eq, Bounded, Show, Read)++-- | 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 (Enum, Eq, Bounded, Show, Read)++-- | Check if a given holiday is a bank holiday.+--+-- >>> isBankHoliday Heiligabend+-- True+isBankHoliday :: Holiday -> Bool+isBankHoliday Heiligabend = True+isBankHoliday Silvestertag = True+isBankHoliday holiday = isGermanPublicHoliday holiday++-- | Helper to extract the year from a date.+--+-- >>> yearFromDay $ fromGregorian 2020 1 1+-- 2020+yearFromDay :: Day -> Year+yearFromDay = (\(y, _, _) -> y) . toGregorian++-- | Calculate Easter Sunday using Spencer's algorithm.+calculateEasterSunday :: Year -> Day+calculateEasterSunday year =+    let+        a = year `rem` 19+        b = year `quot` 100+        c = year `rem` 100+        d = b `quot` 4+        e = b `rem` 4+        f = (b + 8) `quot` 25+        g = (b - f + 1) `quot` 3+        h = (19 * a + b - d - g + 15) `rem` 30+        i = c `quot` 4+        k = c `rem` 4+        l = (32 + 2 * e + 2 * i - h - k) `rem` 7+        m = (a + 11 * h + 22 * l) `quot` 451+        n = (h + l - 7 * m + 114) `quot` 31+        o = (h + l - 7 * m + 114) `rem` 31+    in+        fromGregorian year (fromIntegral n) (fromIntegral o + 1)++-- | Compute the date for a given year and bank holiday.+--+-- >>> toDay 2024 ErsterMai+-- 2024-05-01+toDay :: Year -> Holiday -> Day+toDay year Neujahrstag        = fromGregorian year 1 1+toDay year Karfreitag         = addDays (-2) (calculateEasterSunday year)+toDay year Ostermontag        = addDays 1 (calculateEasterSunday year)+toDay year ErsterMai          = fromGregorian year 5 1+toDay year ChristiHimmelfahrt = addDays 39 (calculateEasterSunday year)+toDay year Pfingstmontag      = addDays 50 (calculateEasterSunday year)+toDay year TagDerDeutschenEinheit = fromGregorian year 10 3+toDay year Heiligabend        = fromGregorian year 12 24+toDay year ErsterWeihnachtsfeiertag  = fromGregorian year 12 25+toDay year ZweiterWeihnachtsfeiertag = fromGregorian year 12 26+toDay year Silvestertag            = fromGregorian year 12 31+toDay year HeiligeDreiKoenige      = fromGregorian year 1 6+toDay year Fronleichnam            = addDays 60 $ calculateEasterSunday year+toDay year Friedensfest            = fromGregorian year 8 8+toDay year MariaeHimmelfahrt       = fromGregorian year 8 15+toDay year Allerheiligen           = fromGregorian year 11 1+toDay year InternationalerFrauentag = fromGregorian year 3 8+toDay year Reformationstag          = fromGregorian year 10 31+toDay year BussUndBettag           = calculateBussUndBettag year+toDay year Weltkindertag           = fromGregorian year 9 20++-- | Compute list of holidays for a given date.+--+-- Note: In some years, two bank holidays can fall on the same+-- day. E.g. 'ErsterMai' and 'ChristiHimmelfahrt' in 2008 are both on+-- 2008-05-01.+--+-- >>> fromDay (fromGregorian 2024 1 1)+-- [Neujahrstag]+--+-- >>> fromDay (fromGregorian 2024 5 5)+-- []+fromDay :: Day -> [Holiday]+fromDay day = filter (\d -> day == toDay (yearFromDay day) d) [minBound..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)+-- [ErsterWeihnachtsfeiertag,ZweiterWeihnachtsfeiertag]+holidaysBetween :: Day -> Day -> [(Day, Holiday)]+holidaysBetween start end = concat $ map (\d -> map (d,) $ fromDay d) [start..end]++-- | Translate the holiday name to German.+germanHolidayName :: Holiday -> String+germanHolidayName d = case d of+  Neujahrstag        -> "Neujahrstag"+  Karfreitag         -> "Karfreitag"+  Ostermontag        -> "Ostermontag"+  ErsterMai          -> "Tag der Arbeit"+  ChristiHimmelfahrt -> "Christi Himmelfahrt"+  Pfingstmontag      -> "Pfingstmontag"+  TagDerDeutschenEinheit -> "Tag der Deutschen Einheit"+  Heiligabend        -> "Heilig Abend"+  ErsterWeihnachtsfeiertag  -> "1. Weihnachtsfeiertag"+  ZweiterWeihnachtsfeiertag -> "2. Weihnachtsfeiertag"+  Silvestertag           -> "Silvestertag"+  HeiligeDreiKoenige     -> "Heilige Drei Könige"+  Fronleichnam           -> "Fronleichnam"+  Friedensfest           -> "Friedensfest"+  MariaeHimmelfahrt      -> "Mariä Himmelfahrt"+  Allerheiligen          -> "Allerheiligen"+  Reformationstag          -> "Reformationstag"+  InternationalerFrauentag -> "Internationaler Frauentag"+  BussUndBettag            -> "Buß- und Bettag"+  Weltkindertag            -> "Weltkindertag"++-- | True only for public holidays aka legal holidays that are+-- holidays in all federal states.+-- 'Heiligabend' and 'Silvestertag' are bank holidays but not public holidays.+isGermanPublicHoliday :: Holiday -> Bool+isGermanPublicHoliday Neujahrstag = True+isGermanPublicHoliday Karfreitag = True+isGermanPublicHoliday Ostermontag = True+isGermanPublicHoliday ErsterMai = True+isGermanPublicHoliday ChristiHimmelfahrt = True+isGermanPublicHoliday Pfingstmontag = True+isGermanPublicHoliday TagDerDeutschenEinheit = True+isGermanPublicHoliday ErsterWeihnachtsfeiertag = True+isGermanPublicHoliday ZweiterWeihnachtsfeiertag = True+isGermanPublicHoliday _ = False++-- | Check if 'Holiday' is a holiday in the given federal state.+--+-- Note: Internationaler Frauentag is a holiday in Berlin (since 2019)+-- and Mecklenburg-Vorpommern (since 2023).+-- However this function doesn't take the year into account and hence+-- is incorrect for earlier years.+--+-- >>> isFederalPublicHoliday Bayern Allerheiligen+-- True+--+-- >>> isFederalPublicHoliday Berlin Allerheiligen+-- False+isFederalPublicHoliday :: FederalState -> Holiday -> Bool+isFederalPublicHoliday BadenWuerttemberg HeiligeDreiKoenige = True+isFederalPublicHoliday BadenWuerttemberg Fronleichnam = True+isFederalPublicHoliday BadenWuerttemberg Allerheiligen = True+isFederalPublicHoliday Bayern HeiligeDreiKoenige = True+isFederalPublicHoliday Bayern Fronleichnam = True+isFederalPublicHoliday Bayern Friedensfest = True+isFederalPublicHoliday Bayern MariaeHimmelfahrt = True+isFederalPublicHoliday Bayern Allerheiligen = True+isFederalPublicHoliday Berlin InternationalerFrauentag = True+isFederalPublicHoliday NordrheinWestfalen Fronleichnam = True+isFederalPublicHoliday NordrheinWestfalen Allerheiligen = True+isFederalPublicHoliday Niedersachsen Reformationstag = True+isFederalPublicHoliday Hessen Fronleichnam = True+isFederalPublicHoliday RheinlandPfalz Allerheiligen = True+isFederalPublicHoliday RheinlandPfalz Fronleichnam = True+isFederalPublicHoliday Sachsen Reformationstag = True+isFederalPublicHoliday Sachsen BussUndBettag = True+isFederalPublicHoliday SchleswigHolstein Reformationstag = True+isFederalPublicHoliday Brandenburg Reformationstag = True+isFederalPublicHoliday SachsenAnhalt HeiligeDreiKoenige = True+isFederalPublicHoliday SachsenAnhalt Reformationstag = True+isFederalPublicHoliday Thueringen Fronleichnam = True+isFederalPublicHoliday Thueringen Reformationstag = True+isFederalPublicHoliday Thueringen Weltkindertag = True+isFederalPublicHoliday Hamburg Reformationstag = True+isFederalPublicHoliday MecklenburgVorpommern Reformationstag = True+isFederalPublicHoliday MecklenburgVorpommern InternationalerFrauentag = True+isFederalPublicHoliday Saarland Fronleichnam = True+isFederalPublicHoliday Saarland Allerheiligen = True+isFederalPublicHoliday Saarland MariaeHimmelfahrt = True+isFederalPublicHoliday Bremen Reformationstag = True+isFederalPublicHoliday _ holiday = isGermanPublicHoliday holiday++-- | Calculate Buß- und Bettag.+--+-- https://de.wikipedia.org/wiki/Bu%C3%9F-_und_Bettag+calculateBussUndBettag :: Year -> Day+calculateBussUndBettag year =+   let november23 = fromGregorian year 11 23+       weekDay = dayOfWeek november23+    in if weekDay <= Wednesday+         then addDays (toInteger $ fromEnum Wednesday - fromEnum weekDay - 7) november23+         else addDays (toInteger $ fromEnum Wednesday - fromEnum weekDay) november23
− src/Data/Time/Calendar/BankHoliday/Germany.hs
@@ -1,167 +0,0 @@--{-|-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 (Enum, Eq, Bounded, Show, Read)----- | Check if a given day is a 'BankHoliday'.------ >>> isBankHoliday (fromGregorian 2024 1 1)--- True-isBankHoliday :: Day -> Bool-isBankHoliday = isJust . fromDay---- | Helper to extract the year from a date.------ >>> yearFromDay $ fromGregorian 2020 1 1--- 2020-yearFromDay :: Day -> Year-yearFromDay = (\(y, _, _) -> y) . toGregorian---- | Calculate Easter Sunday using Spencer's algorithm.-calculateEasterSunday :: Year -> Day-calculateEasterSunday year =-    let-        a = year `rem` 19-        b = year `quot` 100-        c = year `rem` 100-        d = b `quot` 4-        e = b `rem` 4-        f = (b + 8) `quot` 25-        g = (b - f + 1) `quot` 3-        h = (19 * a + b - d - g + 15) `rem` 30-        i = c `quot` 4-        k = c `rem` 4-        l = (32 + 2 * e + 2 * i - h - k) `rem` 7-        m = (a + 11 * h + 22 * l) `quot` 451-        n = (h + l - 7 * m + 114) `quot` 31-        o = (h + l - 7 * m + 114) `rem` 31-    in-        fromGregorian year (fromIntegral n) (fromIntegral o + 1)---- | Compute the date for a given year and bank holiday.------ >>> toDay 2024 LabourDay--- 2024-05-01-toDay :: Year -> BankHoliday -> Day-toDay year NewYearsDay        = fromGregorian year 1 1-toDay year GoodFriday         = addDays (-2) (calculateEasterSunday year)-toDay year EasterMonday       = addDays 1 (calculateEasterSunday year)-toDay year LabourDay          = fromGregorian year 5 1-toDay year AscensionDay       = addDays 39 (calculateEasterSunday year)-toDay year WhitMonday         = addDays 50 (calculateEasterSunday year)-toDay year GermanUnityDay     = fromGregorian year 10 3-toDay year ChristmasEve       = fromGregorian year 12 24-toDay year ChristmasDay       = fromGregorian year 12 25-toDay year SecondChristmasDay = fromGregorian year 12 26-toDay year NewYearsEve        = fromGregorian year 12 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 = listToMaybe $ filter (\d -> day == toDay (yearFromDay day) d) [minBound..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 start end = catMaybes $ map (\d -> (d,) <$> fromDay d) [start..end]---- | Translate the holiday name to German.-germanHolidayName :: BankHoliday -> String-germanHolidayName d = case d of-  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"---- | 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 ChristmasEve = False-isPublicHoliday NewYearsEve = False-isPublicHoliday _ = True
− src/Data/Time/Calendar/BankHoliday/Germany/ExtraHolidays.hs
@@ -1,236 +0,0 @@--{-|--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.--The example prints all public holidays in Bavaria (Landkreis-Miesbach, Oberbayern) in 2025:--@-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 2025 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-@--@-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:--Extra holidays are implemented for all 16 federal states. For some-states, we couldn't find official sources; That's why this list-only includes some states.-- - Ü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- - Rheinland-Pfalz: https://mdi.rlp.de/themen/buerger-und-staat/verfassung-und-verwaltung/sonn-und-feiertagsrecht- - Brandenburg: https://bravors.brandenburg.de/gesetze/ftg_2003/6- - Sachsen-Anhalt: https://www.landesrecht.sachsen-anhalt.de/bsst/document/jlr-FeiertGSTrahmen/part/X- - Thüringen: https://landesrecht.thueringen.de/bsth/document/jlr-FeiertGTHrahmen- - Hamburg: https://www.hamburg.de/ferien-und-feiertage/- - Mecklenburg-Vorpommern u. Berlin (Frauentag): https://www.deutsche-rentenversicherung.de/DRV/DE/Ueber-uns-und-Presse/Presse/Meldungen/2024/240306_frauentag_feiertag_frei.html- - Saarland: https://www.saarland.de/mibs/DE/themen-aufgaben/aufgaben/buerger_und_staat/sonn_u_feiertagsrecht/feiertagsrecht_node.html- - Bremen: https://www.transparenz.bremen.de/metainformationen/gesetz-ueber-die-sonn-gedenk-und-feiertage-vom-12-november-1954-145882?asl=bremen203_tpgesetz.c.55340.de&template=20_gp_ifg_meta_detail_d---}--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 (Enum, Eq, Bounded, Show, Read)---- | Extra federal holidays, no overlap with--- 'Data.Time.Calendar.BankHoliday.Germany.BankHoliday'.--- Spezielle Feiertage der Bundesländer.------ \*regional holiday, only applies in parts of the federal state-data ExtraHoliday-  = HeiligeDreiKoenige     -- ^ Heilige Drei Könige (Bayern, Baden-Württemberg, Sachsen-Anhalt)-  | Fronleichnam           -- ^ Fronleichnam (Bayern, Baden-Württemberg, Nordrhein-Westfalen, Hessen, Rheinland-Pfalz, Thüringen*, Saarland)-  | Friedensfest           -- ^ Friedensfest (Bayern*)-  | MariaeHimmelfahrt      -- ^ Mariä Himmelfahrt (Bayern*, Saarland)-  | Allerheiligen          -- ^ Allerheiligen (Bayern, Baden-Württemberg, Nordrhein-Westfalen, Rheinland-Pfalz, Saarland)-  | Reformationstag        -- ^ Reformationstag (Niedersachsen, Sachsen, Schleswig-Holstein, Brandenburg, Sachsen-Anhalt, Thüringen, Hamburg, Mecklenburg-Vorpommern, Bremen)-  | Weltkindertag          -- ^ Weltkindertag-  | InternationalerFrauentag -- ^ Internationaler Frauentag (Berlin, Mecklenburg-Vorpommern)-  | BussUndBettag          -- ^ Buß- und Bettag (Sachsen)-  deriving (Enum, Eq, Bounded, Show, Read)---- | Compute the date for a given year and extra holiday.------ >>> toDay 2024 HeiligeDreiKoenige--- 2024-01-06-toDay :: Year -> ExtraHoliday -> Day-toDay year HeiligeDreiKoenige      = fromGregorian year 1 6-toDay year Fronleichnam            = addDays 60 $ calculateEasterSunday year-toDay year Friedensfest            = fromGregorian year 8 8-toDay year MariaeHimmelfahrt       = fromGregorian year 8 15-toDay year Allerheiligen           = fromGregorian year 11 1-toDay year InternationalerFrauentag = fromGregorian year 3 8-toDay year Reformationstag          = fromGregorian year 10 31-toDay year Weltkindertag           = fromGregorian year 9 20-toDay year BussUndBettag           = calculateBussUndBettag year---- | 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 = listToMaybe $ filter (\d -> day == toDay (yearFromDay day) d) [minBound..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 state start end = filter (isHolidayInState state . snd) $ catMaybes $ map (\d -> (d,) <$> fromDay d) [start..end]---- | Translate the holiday name to German.-germanHolidayName :: ExtraHoliday -> String-germanHolidayName d = case d of-  HeiligeDreiKoenige     -> "Heilige Drei Könige"-  Fronleichnam           -> "Fronleichnam"-  Friedensfest           -> "Friedensfest"-  MariaeHimmelfahrt      -> "Mariä Himmelfahrt"-  Allerheiligen          -> "Allerheiligen"-  Reformationstag        -> "Reformationstag"-  Weltkindertag          -> "Weltkindertag"-  InternationalerFrauentag -> "Internationaler Frauentag"-  BussUndBettag            -> "Buß- und Bettag"---- | Check if 'ExtraHoliday' is a holiday in the given federal state.------ Note: Internationaler Frauentag is a holiday in Berlin (since 2019)--- and Mecklenburg-Vorpommern (since 2023).--- However this function doesn't take the year into account and hence--- is incorrect for earlier years.------ >>> isHolidayInState Bayern Allerheiligen--- True------ >>> isHolidayInState Berlin Allerheiligen--- False-isHolidayInState :: FederalState -> ExtraHoliday -> Bool-isHolidayInState BadenWuerttemberg HeiligeDreiKoenige = True-isHolidayInState BadenWuerttemberg Fronleichnam = True-isHolidayInState BadenWuerttemberg Allerheiligen = True-isHolidayInState Bayern HeiligeDreiKoenige = True-isHolidayInState Bayern Fronleichnam = True-isHolidayInState Bayern Friedensfest = True-isHolidayInState Bayern MariaeHimmelfahrt = True-isHolidayInState Bayern Allerheiligen = True-isHolidayInState Berlin InternationalerFrauentag = True-isHolidayInState NordrheinWestfalen Fronleichnam = True-isHolidayInState NordrheinWestfalen Allerheiligen = True-isHolidayInState Niedersachsen Reformationstag = True-isHolidayInState Hessen Fronleichnam = True-isHolidayInState RheinlandPfalz Allerheiligen = True-isHolidayInState RheinlandPfalz Fronleichnam = True-isHolidayInState Sachsen Reformationstag = True-isHolidayInState Sachsen BussUndBettag = True-isHolidayInState SchleswigHolstein Reformationstag = True-isHolidayInState Brandenburg Reformationstag = True-isHolidayInState SachsenAnhalt HeiligeDreiKoenige = True-isHolidayInState SachsenAnhalt Reformationstag = True-isHolidayInState Thueringen Fronleichnam = True-isHolidayInState Thueringen Reformationstag = True-isHolidayInState Thueringen Weltkindertag = True-isHolidayInState Hamburg Reformationstag = True-isHolidayInState MecklenburgVorpommern Reformationstag = True-isHolidayInState MecklenburgVorpommern InternationalerFrauentag = True-isHolidayInState Saarland Fronleichnam = True-isHolidayInState Saarland Allerheiligen = True-isHolidayInState Saarland MariaeHimmelfahrt = True-isHolidayInState Bremen Reformationstag = True-isHolidayInState _ _ = False---- | Calculate Buß- und Bettag.------ https://de.wikipedia.org/wiki/Bu%C3%9F-_und_Bettag-calculateBussUndBettag :: Year -> Day-calculateBussUndBettag year =-   let november23 = fromGregorian year 11 23-       weekDay = dayOfWeek november23-    in if weekDay <= Wednesday-         then addDays (toInteger $ fromEnum Wednesday - fromEnum weekDay - 7) november23-         else addDays (toInteger $ fromEnum Wednesday - fromEnum weekDay) november23
test/Main.hs view
@@ -1,9 +1,7 @@  module Main (main) where -import Data.Time.Calendar.BankHoliday.Germany-import Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays (ExtraHoliday(..), FederalState(..))-import Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays qualified as EH+import Data.Holiday.Germany import Data.Time.Calendar.WeekDate import Data.Time @@ -25,6 +23,15 @@ year :: Day -> Year year = (\(y, _, _) -> y) . toGregorian +holidaysBetween' :: FederalState -> Day -> Day -> [(Day, Holiday)]+holidaysBetween' state x y = filter (isFederalPublicHoliday state . snd) $ holidaysBetween x y++generalHolidays :: [Holiday]+generalHolidays = filter isGermanPublicHoliday [minBound .. maxBound]++federalHolidaysOnly :: FederalState -> Day -> Day -> [Holiday]+federalHolidaysOnly state x y = filter (not . flip elem generalHolidays) $ map snd $ holidaysBetween' state x y+ main :: IO () main = do   doctest ["src/"]@@ -33,15 +40,15 @@     describe "holidaysBetween" $ do       it "works for Christmas" $         holidaysBetween (day 2024 12 1) (day 2024 12 30)-          `shouldBe` [(day 2024 12 24, ChristmasEve), (day 2024 12 25, ChristmasDay), (day 2024 12 26, SecondChristmasDay)]+          `shouldBe` [(day 2024 12 24, Heiligabend), (day 2024 12 25, ErsterWeihnachtsfeiertag), (day 2024 12 26, ZweiterWeihnachtsfeiertag)]       it "is empty list when there are no holidays" $         holidaysBetween (day 2024 12 1) (day 2024 12 3)           `shouldBe` []       it "counts 11 bank holidays and 9 public holidays per year (2010 to 2020)" $ hedgehog $ do         y <- forAll $ Gen.integral (Range.linear 2010 2020)-        let bankHolidays = holidaysBetween ((jan1 y)) ((dec31 y))+        let bankHolidays = filter (isBankHoliday . snd) $ holidaysBetween (jan1 y) ((dec31 y))         length bankHolidays === 11-        length (filter (isPublicHoliday . snd) bankHolidays) === 9+        length (filter (isGermanPublicHoliday . snd) bankHolidays) === 9     describe "toDay" $ do       it "always returns a day in the given year" $ hedgehog $ do         y <- forAll $ Gen.integral (Range.linear 0 5000)@@ -49,7 +56,7 @@         year (toDay y d) === y       it "Easter Monday is always a Monday" $ hedgehog $ do         y <- forAll $ Gen.integral (Range.linear 0 5000)-        let (_, _, d) = toWeekDate $ toDay y EasterMonday+        let (_, _, d) = toWeekDate $ toDay y Ostermontag         d === 1     -- 1 = Monday     describe "calculateEasterSunday" $ do       it "is always between Mar 22 and Apr 25" $ hedgehog $ do@@ -64,9 +71,9 @@         y <- forAll $ Gen.integral (Range.linear 0 5000)         let (_, _, d) = toWeekDate $ calculateEasterSunday y         d === 7  -- 7 = Sunday-    describe "isPublicHoliday" $ do-      it "is False only for Chrismas Eve and New Year's Eve" $-        length (filter (not . isPublicHoliday) [minBound..maxBound])+    describe "isGermanPublicHoliday" $ do+      it "the only bank holidays that are no public holidays are Chrismas Eve and New Year's Eve" $+        length (filter (\x -> isBankHoliday x && not (isGermanPublicHoliday x)) [minBound..maxBound])           `shouldBe` 2     describe "yearFromDay" $ do       it "works for any year" $ hedgehog $ do@@ -76,76 +83,75 @@         yearFromDay (day y m d) === y     describe "germanHolidayName" $       it "names are longer than 5 characters for all holidays (which mean there are no non-exhaustive patterns)" $-        all ((>5) . length . germanHolidayName) [minBound .. maxBound :: BankHoliday] `shouldBe` True+        all ((>5) . length . germanHolidayName) [minBound .. maxBound :: Holiday] `shouldBe` True -   describe "Data.Time.Calendar.BankHoliday.Germany.ExtraHolidays" $ do-     describe "FederalState" $-       it "has right number of states" $-         length [minBound .. maxBound :: FederalState] `shouldBe` 16-     describe "toDay" $-       it "works for Fronleichnam (depending on Easter Sunday)" $ do-         -- https://www.arbeitstage.org/feiertage/fronleichnam/-         map (\y -> show $ EH.toDay y Fronleichnam) [2024..2027]-           `shouldBe` ["2024-05-30", "2025-06-19", "2026-06-04", "2027-05-27"]-     describe "holidaysBetween" $ do-       it "yields Bavaria's extra holidays" $-         map snd (EH.holidaysBetween Bayern (day 2024 11 1) (day 2024 12 31))-           `shouldBe` [Allerheiligen]-       it "there is only one extra holiday in Berlin" $ do-         filter (EH.isHolidayInState Berlin) [minBound..maxBound]-           `shouldBe` [InternationalerFrauentag]-       it "there are 3 extra holidays in Baden-Württemberg" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         length (EH.holidaysBetween BadenWuerttemberg ((jan1 y)) ((dec31 y))) === 3-       it "there are 2 extra holidays in Nordrhein-Westfalen" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         length (EH.holidaysBetween NordrheinWestfalen ((jan1 y)) ((dec31 y))) === 2-       it "there is only 1 extra holiday in Niedersachsen" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         length (EH.holidaysBetween Niedersachsen (jan1 y) (dec31 y)) === 1-       it "there is only Fronleichnam in Hessen" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween Hessen (jan1 y) (dec31 y)) === [Fronleichnam]-       it "has holidays for Rheinland-Pfalz" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween RheinlandPfalz (jan1 y) (dec31 y)) === [Fronleichnam, Allerheiligen]-       it "has holidays for Sachsen" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween Sachsen (jan1 y) (dec31 y)) === [Reformationstag, BussUndBettag]-       it "has holidays for Schleswig-Holstein" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween SchleswigHolstein (jan1 y) (dec31 y)) === [Reformationstag]-       it "has holidays for Brandenburg" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween Brandenburg (jan1 y) (dec31 y)) === [Reformationstag]-       it "has holidays for Sachsen-Anhalt" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween SachsenAnhalt (jan1 y) (dec31 y)) === [HeiligeDreiKoenige, Reformationstag]-       it "has holidays for Thüringen" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween Thueringen (jan1 y) (dec31 y)) === [Fronleichnam, Weltkindertag, Reformationstag]-       it "has holidays for Hamburg" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2024 5000)-         (map snd $ EH.holidaysBetween Hamburg (jan1 y) (dec31 y)) === [Reformationstag]-       it "has holidays for Mecklenburg-Vorpommern" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2023 5000)-         (map snd $ EH.holidaysBetween MecklenburgVorpommern (jan1 y) (dec31 y)) === [InternationalerFrauentag, Reformationstag]-       it "has holidays for Saarland" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2023 5000)-         (map snd $ EH.holidaysBetween Saarland (jan1 y) (dec31 y)) === [Fronleichnam, MariaeHimmelfahrt, Allerheiligen]-       it "has holidays for Bremen" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 2023 5000)-         (map snd $ EH.holidaysBetween Bremen (jan1 y) (dec31 y)) === [Reformationstag]-       it "computes Buss- und Bettag correctly for some years" $ do-         EH.toDay 2024 BussUndBettag `shouldBe` day 2024 11 20-         EH.toDay 2025 BussUndBettag `shouldBe` day 2025 11 19-         EH.toDay 2026 BussUndBettag `shouldBe` day 2026 11 18-         EH.toDay 2033 BussUndBettag `shouldBe` day 2033 11 16-       it "Buss- und Bettag is always between 16. and 22. November (inclusive)" $ hedgehog $ do-         y <- forAll $ Gen.integral (Range.linear 0 5000)-         let (_, m, d) = toGregorian $ EH.toDay y BussUndBettag-         (d >= 16 && d <= 22) === True-         m === 11-     describe "germanHolidayName" $-       it "names are longer than 5 characters for all holidays (which mean there are no non-exhaustive patterns)" $-         all ((>5) . length . EH.germanHolidayName) [minBound .. maxBound :: ExtraHoliday] `shouldBe` True+    describe "FederalState" $+      it "has right number of states" $+        length [minBound .. maxBound :: FederalState] `shouldBe` 16+    describe "toDay" $+      it "works for Fronleichnam (depending on Easter Sunday)" $ do+        -- https://www.arbeitstage.org/feiertage/fronleichnam/+        map (\y -> show $ toDay y Fronleichnam) [2024..2027]+          `shouldBe` ["2024-05-30", "2025-06-19", "2026-06-04", "2027-05-27"]+    describe "holidaysBetween" $ do+      it "yields Bavaria's extra holidays" $+        federalHolidaysOnly Bayern (day 2024 11 1) (day 2024 12 31)+          `shouldBe` [Allerheiligen]+      it "there is only one extra holiday in Berlin" $ do+        filter (\x -> isFederalPublicHoliday Berlin x && not (isGermanPublicHoliday x)) [minBound..maxBound]+          `shouldBe` [InternationalerFrauentag]+      it "there are 3 extra holidays in Baden-Württemberg" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        length (federalHolidaysOnly BadenWuerttemberg ((jan1 y)) ((dec31 y))) === 3+      it "there are 2 extra holidays in Nordrhein-Westfalen" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        length (federalHolidaysOnly NordrheinWestfalen ((jan1 y)) ((dec31 y))) === 2+      it "there is only 1 extra holiday in Niedersachsen" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        length (federalHolidaysOnly Niedersachsen (jan1 y) (dec31 y)) === 1+      it "there is only Fronleichnam in Hessen" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly Hessen (jan1 y) (dec31 y)) === [Fronleichnam]+      it "has holidays for Rheinland-Pfalz" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly RheinlandPfalz (jan1 y) (dec31 y)) === [Fronleichnam, Allerheiligen]+      it "has holidays for Sachsen" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly Sachsen (jan1 y) (dec31 y)) === [Reformationstag, BussUndBettag]+      it "has holidays for Schleswig-Holstein" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly SchleswigHolstein (jan1 y) (dec31 y)) === [Reformationstag]+      it "has holidays for Brandenburg" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly Brandenburg (jan1 y) (dec31 y)) === [Reformationstag]+      it "has holidays for Sachsen-Anhalt" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly SachsenAnhalt (jan1 y) (dec31 y)) === [HeiligeDreiKoenige, Reformationstag]+      it "has holidays for Thüringen" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly Thueringen (jan1 y) (dec31 y)) === [Fronleichnam, Weltkindertag, Reformationstag]+      it "has holidays for Hamburg" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2024 5000)+        (federalHolidaysOnly Hamburg (jan1 y) (dec31 y)) === [Reformationstag]+      it "has holidays for Mecklenburg-Vorpommern" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2023 5000)+        (federalHolidaysOnly MecklenburgVorpommern (jan1 y) (dec31 y)) === [InternationalerFrauentag, Reformationstag]+      it "has holidays for Saarland" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2023 5000)+        (federalHolidaysOnly Saarland (jan1 y) (dec31 y)) === [Fronleichnam, MariaeHimmelfahrt, Allerheiligen]+      it "has holidays for Bremen" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 2023 5000)+        (federalHolidaysOnly Bremen (jan1 y) (dec31 y)) === [Reformationstag]+      it "computes Buss- und Bettag correctly for some years" $ do+        toDay 2024 BussUndBettag `shouldBe` day 2024 11 20+        toDay 2025 BussUndBettag `shouldBe` day 2025 11 19+        toDay 2026 BussUndBettag `shouldBe` day 2026 11 18+        toDay 2033 BussUndBettag `shouldBe` day 2033 11 16+      it "Buss- und Bettag is always between 16. and 22. November (inclusive)" $ hedgehog $ do+        y <- forAll $ Gen.integral (Range.linear 0 5000)+        let (_, m, d) = toGregorian $ toDay y BussUndBettag+        (d >= 16 && d <= 22) === True+        m === 11+    describe "germanHolidayName" $+      it "names are longer than 5 characters for all holidays (which mean there are no non-exhaustive patterns)" $+        all ((>5) . length . germanHolidayName) [minBound .. maxBound :: Holiday] `shouldBe` True