{-# LANGUAGE GADTs, FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, StandaloneDeriving, UndecidableInstances #-} -- This module is intended to be imported @qualified!, to avoid name -- clashes with "Prelude" functions. eg. -- -- > import qualified Data.Time.Recurrence.Schedule as S module Data.Time.Recurrence.Schedule ( -- * Schedule Schedule (..) -- * Freq , Freq -- * Function interface to Recur , recur -- * Adjust Interval , by -- * Adjust Start of Week , withStartOfWeek -- * Default Freq , secondly , minutely , hourly , daily , weekly , monthly , yearly -- * evaluate a Schedule into a function , eval -- * run an evaluated Schedule from a moment , starting ) where import Data.List.Ordered as O import Data.Time.Calendar.Month () import Data.Time.Calendar.WeekDay import Data.Time.CalendarTime import Data.Time.Moment hiding (interval, startOfWeek, Period(..)) import qualified Data.Time.Moment as M (Period(..)) import Data.Time.Recurrence.AndThen import Data.Time.Recurrence.ScheduleDetails hiding (eval) import qualified Data.Time.Recurrence.ScheduleDetails as D (eval) data Freq = Secondly { interval :: Interval, startOfWeek :: StartOfWeek } | Minutely { interval :: Interval, startOfWeek :: StartOfWeek } | Hourly { interval :: Interval, startOfWeek :: StartOfWeek } | Daily { interval :: Interval, startOfWeek :: StartOfWeek } | Weekly { interval :: Interval, startOfWeek :: StartOfWeek } | Monthly { interval :: Interval, startOfWeek :: StartOfWeek } | Yearly { interval :: Interval, startOfWeek :: StartOfWeek } deriving (Show) defaultFreq :: (Interval -> StartOfWeek -> Freq) -> Freq defaultFreq = flip uncurry (toInterval 1, toStartOfWeek Sunday) secondly :: Freq secondly = defaultFreq Secondly minutely :: Freq minutely = defaultFreq Minutely hourly :: Freq hourly = defaultFreq Hourly daily :: Freq daily = defaultFreq Daily weekly :: Freq weekly = defaultFreq Weekly monthly :: Freq monthly = defaultFreq Monthly yearly :: Freq yearly = defaultFreq Yearly -- | Typically called infix on an existing 'Freq', like: -- -- > monthly `by` 2 by :: Freq -> Integer -> Freq by fr i = fr{interval=toInterval i} -- | Typically called infix on an existing 'Freq', like: -- -- > weekly `withStartOfWeek` Tuesday withStartOfWeek :: Freq -> WeekDay -> Freq withStartOfWeek fr sow = fr{startOfWeek=toStartOfWeek sow} data Schedule a where Recur :: Freq -> Schedule Freq And :: Schedule Freq -> ScheduleDetails b -> Schedule (ScheduleDetails b) deriving instance Show (Schedule a) recur :: Freq -> Schedule Freq recur = Recur instance AndThen (Schedule Freq) (ScheduleDetails b) (Schedule (ScheduleDetails b)) where (>==>) x y = And x y eval :: (CalendarTimeConvertible a, Ord a, Moment a) => Schedule b -> (a -> [a]) eval (And recur details) = flip (startWith $ mkIM recur) $ D.eval details eval recur@(Recur _) = start $ mkIM recur starting :: (CalendarTimeConvertible a, Ord a, Moment a) => a -> Schedule b -> [a] starting m0 sch = (eval sch) m0 mkIM :: Moment a => Schedule Freq -> InitialMoment a mkIM (Recur freq) = mkIM' (case freq of (Secondly _ _) -> M.Seconds (Minutely _ _) -> M.Minutes (Hourly _ _) -> M.Hours (Daily _ _) -> M.Days (Weekly _ _) -> M.Weeks (Monthly _ _) -> M.Months (Yearly _ _) -> M.Years) (interval freq) (startOfWeek freq) where mkIM' :: Moment a => M.Period -> Interval -> StartOfWeek -> InitialMoment a mkIM' per int sow = InitialMoment per int sow epoch -- | 'startWith' is an infinite list of 'Moment's, where no 'Moment' -- occurrs before the 'InitialMoment'. The list is further refined -- by the passed in function. startWith :: (Ord a, Moment a) => InitialMoment a -> a -> ([a] -> FutureMoments a) -> [a] startWith im m0 = dropWhile (< m0) . O.nub . iterateFutureMoments im{moment=m0} start :: (Ord a, Moment a) => InitialMoment a -> a -> [a] start im m0 = startWith im m0 return