saturn-0.1.0.0: source/library/Saturn/Unstable/Match.hs
module Saturn.Unstable.Match where
import qualified Control.Monad as Monad
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import qualified Data.Time as Time
import qualified Saturn.Unstable.Extra.Int as Int
import qualified Saturn.Unstable.Extra.Time as Time
import qualified Saturn.Unstable.Type.Day as Day
import qualified Saturn.Unstable.Type.Field as Field
import qualified Saturn.Unstable.Type.Hour as Hour
import qualified Saturn.Unstable.Type.Minute as Minute
import qualified Saturn.Unstable.Type.Month as Month
import qualified Saturn.Unstable.Type.Schedule as Schedule
import qualified Saturn.Unstable.Type.Weekday as Weekday
import qualified Saturn.Unstable.Type.Wildcard as Wildcard
-- | Returns 'True' if the given 'Time.UTCTime' matches the given
-- 'Schedule.Schedule', otherwise returns 'False'.
isMatch :: Time.UTCTime -> Schedule.Schedule -> Bool
isMatch utcTime schedule = Maybe.fromMaybe False $ do
let time = Time.pastMidnight $ Time.utctDayTime utcTime
minute <- Int.toWord8 $ Time.todMin time
Monad.guard . Minute.isMatch minute $ Schedule.minute schedule
hour <- Int.toWord8 $ Time.todHour time
Monad.guard . Hour.isMatch hour $ Schedule.hour schedule
let date = Time.utctDay utcTime
let (_, monthOfYear, dayOfMonth) = Time.toGregorian date
month <- Int.toWord8 monthOfYear
Monad.guard . Month.isMatch month $ Schedule.month schedule
day <- Int.toWord8 dayOfMonth
let dayMatches = Day.isMatch day $ Schedule.day schedule
let weekday = Time.dayOfWeekToWord8 $ Time.dayOfWeek date
let weekdayMatches = Weekday.isMatch weekday $ Schedule.weekday schedule
Monad.guard $
if dayIsWildcard schedule || weekdayIsWildcard schedule
then dayMatches && weekdayMatches
else dayMatches || weekdayMatches
pure True
dayIsWildcard :: Schedule.Schedule -> Bool
dayIsWildcard = Field.isWildcard . Day.toField . Schedule.day
weekdayIsWildcard :: Schedule.Schedule -> Bool
weekdayIsWildcard = Field.isWildcard . Weekday.toField . Schedule.weekday
-- | Looks for the first time after the given 'Time.UTCTime' that matches the
-- given 'Schedule.Schedule'. Returns 'Nothing' if the 'Schedule.Schedule' only
-- matches dates that cannot happen, like February 30th.
nextMatch :: Time.UTCTime -> Schedule.Schedule -> Maybe Time.UTCTime
nextMatch utcTime schedule =
if dayIsWildcard schedule || weekdayIsWildcard schedule
then incompleteNextMatch utcTime schedule
else do
let wildcard = Field.fromEither . Left $ Wildcard.fromUnit ()
day <- Day.fromField wildcard
weekday <- Weekday.fromField wildcard
Maybe.listToMaybe . List.sort $
Maybe.catMaybes
[ incompleteNextMatch utcTime schedule {Schedule.day = day},
incompleteNextMatch utcTime schedule {Schedule.weekday = weekday}
]
incompleteNextMatch :: Time.UTCTime -> Schedule.Schedule -> Maybe Time.UTCTime
incompleteNextMatch utcTime schedule = Maybe.listToMaybe $ do
let oldDate = Time.utctDay utcTime
let (oldYear, _, _) = Time.toGregorian oldDate
year <- [oldYear .. oldYear + 8]
month <- fmap Int.fromWord8 . Set.toAscList . Month.expand $ Schedule.month schedule
day <- fmap Int.fromWord8 . Set.toAscList . Day.expand $ Schedule.day schedule
date <- Maybe.maybeToList $ Time.fromGregorianValid year month day
Monad.guard $ date >= oldDate
Monad.guard
. Set.member (Time.dayOfWeekToWord8 $ Time.dayOfWeek date)
. Weekday.expand
$ Schedule.weekday schedule
hour <- fmap Int.fromWord8 . Set.toAscList . Hour.expand $ Schedule.hour schedule
minute <- fmap Int.fromWord8 . Set.toAscList . Minute.expand $ Schedule.minute schedule
time <-
fmap Time.sinceMidnight
. Maybe.maybeToList
$ Time.makeTimeOfDayValid hour minute 0
Monad.when (date == oldDate) . Monad.guard $ time > Time.utctDayTime utcTime
pure Time.UTCTime {Time.utctDay = date, Time.utctDayTime = time}