bank-holiday-germany (empty) → 1.0.0.0
raw patch · 7 files changed
+309/−0 lines, 7 filesdep +bank-holiday-germanydep +basedep +doctestsetup-changed
Dependencies added: bank-holiday-germany, base, doctest, hedgehog, hspec, hspec-hedgehog, time
Files
- CHANGELOG.md +11/−0
- LICENSE +21/−0
- README.md +15/−0
- Setup.hs +2/−0
- bank-holiday-germany.cabal +60/−0
- src/Data/Time/Calendar/BankHoliday/Germany.hs +139/−0
- test/Main.hs +61/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog for `bank-holiday-germany`++All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the+[Haskell Package Versioning Policy](https://pvp.haskell.org/).++## Unreleased++## 0.1.0.0 - YYYY-MM-DD
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2024 Jakob Schöttl++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,15 @@+# bank-holiday-germany++This module provides calculation of bank holidays in Germany.++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.++Legal holidays are generally off for all employees. Bank holidays that+are no legal holidays as well are generally only off for bank employees.++Note: There are even more public holidays in each federal state which+are not covered by this module.++https://de.wikipedia.org/wiki/Bankfeiertag
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bank-holiday-germany.cabal view
@@ -0,0 +1,60 @@+cabal-version: 3.6++name: bank-holiday-germany+version: 1.0.0.0+synopsis: German bank holidays and public holidays+description: Calculation of bank holidays and most public holidays in Germany.+homepage: https://github.com/schoettl/bank-holiday-germany#readme+license: MIT+license-file: LICENSE+author: Jakob Schöttl+maintainer: jakob.schoettl@intensovet.de+copyright: 2024 Jakob Schöttl+category: Time+build-type: Simple+extra-source-files: README.md+ CHANGELOG.md++library+ hs-source-dirs: src+ exposed-modules: Data.Time.Calendar.BankHoliday.Germany+ build-depends: base >= 4.7 && < 5+ , time >= 1.12.2 && < 1.13+ default-language: GHC2021+ ghc-options: -Wall+ -Wcompat+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wmissing-export-lists+ -Wmissing-home-modules+ -Wpartial-fields+ -Wredundant-constraints++test-suite tests+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules: Data.Time.Calendar.BankHoliday.Germany+ hs-source-dirs: test+ , src+ build-depends: base+ , bank-holiday-germany+ , time >= 1.12.2 && < 1.13+ , doctest >= 0.22.2 && < 0.23+ , hedgehog >= 1.4 && < 1.5+ , hspec >= 2.11.7 && < 2.12+ , hspec-hedgehog >= 0.1.0 && < 0.2+ default-language: GHC2021+ ghc-options: -Wall+ -Wcompat+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wmissing-export-lists+ -Wmissing-home-modules+ -Wpartial-fields+ -Wredundant-constraints++source-repository head+ type: git+ location: https://github.com/schoettl/bank-holiday-germany
+ src/Data/Time/Calendar/BankHoliday/Germany.hs view
@@ -0,0 +1,139 @@++{-|+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 not covered by this module.++https://de.wikipedia.org/wiki/Bankfeiertag++-}++module Data.Time.Calendar.BankHoliday.Germany (+ BankHoliday(..),+ isBankHoliday,+ isPublicHoliday,+ calculateEasterSunday,+ holidaysBetween,+ fromDay,+ toDay,+ germanHolidayName+) where++import Data.Time.Calendar+import Data.Maybe++-- | Data type specifying German bank 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 BankHoliday+ = NewYearsDay+ | GoodFriday -- ^ Karfreitag+ | EasterMonday+ | LabourDay+ | AscensionDay -- ^ Christi Himmelfahrt+ | WhitMonday -- ^ Pfingstmontag+ | GermanUnityDay+ | ChristmasEve+ | ChristmasDay+ | SecondChristmasDay+ | NewYearsEve+ 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.+--+-- >>> 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.+--+-- >>> 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
+ test/Main.hs view
@@ -0,0 +1,61 @@++module Main (main) where++import Data.Time.Calendar.BankHoliday.Germany+import Data.Time.Calendar.WeekDate+import Data.Time++import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range+import Test.Hspec (describe, hspec, it, shouldBe)+import Test.Hspec.Hedgehog (forAll, hedgehog, (===))+import Test.DocTest++day :: Year -> Int -> Int -> Day+day = fromGregorian++year :: Day -> Year+year = (\(y, _, _) -> y) . toGregorian++main :: IO ()+main = do+ doctest ["src/"]+ hspec $ do+ 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)]+ 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 (day y 1 1) (day y 12 31)+ length bankHolidays === 11+ length (filter (isPublicHoliday . 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)+ d <- forAll Gen.enumBounded+ 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+ d === 1 -- 1 = Monday+ describe "calculateEasterSunday" $ do+ it "is always between Mar 22 and Apr 25" $ hedgehog $ do+ y <- forAll $ Gen.integral (Range.linear 0 10000)+ (calculateEasterSunday y >= day y 3 22) === True+ (calculateEasterSunday y <= day y 4 25) === True+ it "is correct for some years" $ do+ calculateEasterSunday 2024 `shouldBe` day 2024 3 31+ calculateEasterSunday 2025 `shouldBe` day 2025 4 20+ calculateEasterSunday 2026 `shouldBe` day 2026 4 5+ it "is always a Sunday" $ hedgehog $ do+ 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])+ `shouldBe` 2