@@ -0,0 +1,43 @@
1+-- {-# OPTIONS_GHC -fwarn-name-shadowing #-}
3+module Data.Time.Holidays
4+ ( addTimeExcludingHolidays
8+import qualified Data.Set as S
10+import Data.Time.Calendar.OrdinalDate
11+import Data.Time.LocalTime.TimeZone.Series
14+addTimeExcludingHolidays
20+addTimeExcludingHolidays hDays tz dTime =
21+ addLocalTime' dTime . utcToLocalTime' tz
23+ shouldSkip day = weekday `elem` [6, 7] || day `S.member` hDays
24+ where (_, weekday) = mondayStartWeek day
26+ diffTimeToPicoseconds . secondsToDiffTime $ 60 * 60 * 24
27+ addLocalTime' :: DiffTime -> LocalTime -> UTCTime
28+ addLocalTime' dTime' (LocalTime lDay' lTime') =
30+ actualDays = filter (not . shouldSkip) [lDay'..]
31+ -- Jumps to nearest day if `now` is a holiday
32+ lTime = if shouldSkip lDay'
35+ -- At this point the head of `actualDays` and `lTime`
36+ -- is the day from which we should be counting
37+ (dDays, dPicoSeconds) =
38+ (diffTimeToPicoseconds $ dTime' + (timeOfDayToTime lTime))
39+ `divMod` dayPicoseconds
40+ resultDay = head . drop (fromIntegral dDays) $ actualDays
42+ timeToTimeOfDay . picosecondsToDiffTime $ dPicoSeconds
43+ in localTimeToUTC' tz $ LocalTime resultDay resultTime