time-recurrence 0.4.2 → 0.5.2
raw patch · 15 files changed
+1099/−565 lines, 15 files
Files
- README +235/−0
- src/Data/Time/Calendar/Month.hs +50/−0
- src/Data/Time/Calendar/WeekDay.hs +16/−0
- src/Data/Time/CalendarTime.hs +7/−0
- src/Data/Time/CalendarTime/CalendarTime.hs +124/−0
- src/Data/Time/Moment.hs +10/−0
- src/Data/Time/Moment/Interval.hs +12/−0
- src/Data/Time/Moment/Moment.hs +224/−0
- src/Data/Time/Moment/Private.hs +14/−0
- src/Data/Time/Moment/StartOfWeek.hs +13/−0
- src/Data/Time/Moment/UTC.hs +43/−0
- src/Data/Time/Recurrence.hs +7/−553
- src/Data/Time/Recurrence/Schedule.hs +316/−0
- tests/Tests.lhs +10/−10
- time-recurrence.cabal +18/−2
README view
@@ -4,3 +4,238 @@ library, however the library does not make an effort to track the RFC at all times. A future Data.Time.Recurrence.RFC5545 library would be a useful add-on for those in need of strict RFC compliance.++Examples:++> parse822Time :: String -> UTCTime+> parse822Time = ...++> nov1996 = parse822Time "Tue, 05 Nov 1996 09:00:00 -0400"+> mar1997 = parse822Time "Mon, 10 Mar 1997 09:00:00 -0400"+> sep1997 = parse822Time "Tue, 02 Sep 1997 09:00:00 -0400"+> oct1997 = parse822Time "Fri, 10 Oct 1997 00:00:00 -0400"+> dec1997 = parse822Time "Wed, 24 Dec 1997 00:00:00 -0400"+> jan1998 = parse822Time "Thu, 01 Jan 1998 09:00:00 -0400"+> jan2000 = parse822Time "Mon, 31 Jan 2000 09:00:00 -0400"++Daily for 10 occurrences:++> take 10 $ recur daily `begin` sep1997++Daily until Dec. 24, 1997:++> takeWhile (<= dec1997) $ recur daily `begin` sep1997++Every other day - forever:++> recur daily `by` 2 `begin` sep1997++Every 10 days, 5 occurrences:++> take 5 $ recur daily `by` 10 `begin` sep1997++Every day in January, for 3 years:++> takeWhile (<= jan2000) $ recur yearly `starting` jan1998 $+> enumMonths [January] >=>+> filterWeekDays [Monday .. Sunday]++> takeWhile (<= jan2000) $ recur daily `starting` jan1998 $+> enumMonths [January]++Weekly for 10 occurrences:++> take 10 $ recur weekly `begin` sep1997++Weekly until Dec. 24, 1997:++> takeWhile (<= dec1997) $ recur weekly `withStartOfWeek` Sunday `begin` sep1997++Every other week - forever:++> recur weekly `by` 2 `begin` sep1997++Weekly on Tuesday and Thursday for five weeks:++> takeUntil (<= oct1997) $ recur weekly `withStartOfWeek` Sunday `starting` sep1997 $+> enumWeekDaysInWeek [Tuesday, Thursday]++or++> take 10 $ recur weekly `withStartOfWeek` Sunday `starting` sep1997 $+> enumWeekDaysInWeek [Tuesday, Thursday]++Every other week (Monday, Wednesday, Firday) until Dec. 24, 1997:++> takeWhile (<= dec1997) $ recur weekly `withStartOfWeek` Sunday `by` 2 `starting` sep1997$+> enumWeekDaysInWeek [Monday, Wednesday, Friday] >=>++> Every other week on Tuesday and Thursday, for 8 occurrences:++> take 8 $ recur weekly `withStartOfWeek` Sunday `by` 2 `starting` sep1997 $+> enumWeekDaysInWeek [Tuesday, Thursday]++Monthly on the first Friday for 10 occurrences:++> take 10 $ recur monthly `starting` sep1997 $+> enumWeekDaysInMonth [Friday] >=>+> nthWeekDayInMonth [1]++Monthly on the first Friday until Dec. 24, 1997++> takeWhile (<= dec1997) $ recur monthly `starting` sep1997 $+> enumWeekDaysInMonth [Friday] >=>+> nthWeekDayInMonth [1]++Every other month on the first and last Sunday of the month for 10 occurrences:++> take 10 $ recur monthly `by` 2 `starting` sep1997 $+> enumWeekDaysInMonth [Sunday] >=>+> nthWeekDayInMonth [1,-1]++Monthly on the second-to-last Monday of the month for 6 months:++> take 6 $ recur monthly `starting` sep1997 $+> enumWeekDaysInMonth [Monday] >=>+> nthWeekDayInMonth [-2]++Monthly on the third-to-last day of the month, forever:++> recur monthly `starting` sep1997 $ enumDays [-3]++Monthly on the 2nd and 15th of the month for 10 occurrences:++> take 10 $ recur monthly `starting` sep1997 $ enumDays [2,15]++Monthly on the first and last day of the month for 10 occurrences:++> take 10 $ recur monthly `starting` sep1997 $ enumDays [1,-1]++Every 18 months on the 10th thru 15th of the month for 10 occurrences:++> take 10 $ recur monthly `by` 18 `starting` sep1997 $ enumDays [10 .. 15]++Every Tuesday, every other month:++> recur monthly `by` 2 `starting` sep1997 $ enumWeekDaysInMonth [Tuesday]++Yearly in June and July for 10 occurrences:++> take 10 $ recur yearly `starting` sep1997 $ enumMonths [June, July]++Every other year on January thru March for 10 occurrences:++> take 10 $ recur yearly `by` 2 `starting` mar1997 $ +> enumMonths [January .. March] >=>+> enumWeekDaysInMonth [Monday .. Sunday]++Every third year on the 1st, 100th, and 200th day for 10 occurrences:++> take 10 $ recur yearly `by` 3 `starting` sep1997 $+> enumYearDays [1,100,200]++Every 20th Monday of the year, forever:++> recur monthly `starting` sep1997 $ +> enumWeekDaysInMonth [Monday] >=>+> nthWeekDay [20]++Monday of week number 20, forever:++> recur yearly `starting` mar1997 $+> enumWeekNumbers [20] >=>+> filterWeekDays [Monday]++or++> recur weekly `starting` mar1997 $+> filterWeekNumbers [20] >=>+> filterWeekDays [Monday]++Every Thursday in March, forever:++> recur yearly `starting` mar1997 $+> enumMonths [March] >=>+> enumWeekDaysInMonth [Thrusday]++Every Thursday, but only during June thru August, forever:++> recur yearly `starting` mar1997 $+> enumMonths [June .. August] >=>+> enumWeekDaysInMonth [Thursday]++Firday the 13th, Forever:++> recur monthly `starting` sep1997 $ enumDays [13] >=> filterWeekDays [Friday]++The first Saturday that follows the first Sunday of the month, forever:++> recur monthly sep1997 $+> enumDays [7 .. 13] >=>+> filterWeekDays [Saturday]++U.S. Presidential Election Day:+Every 4 years, the first Tuesday after a Monday in November, forever:++> recur yearly `by` 4 `starting` nov1996 $+> enumMonths [November] >=>+> enumDays [2 .. 8] >=>+> filterWeekDays [Tuesday]++The third instance into the month of one of Tuesday, Wednesday, or Thursday, for the next 3 months.++> take 3 $ recur monthly `starting` sep1997 $+> enumWeekDaysInMonth [Tuesday .. Thursday] >=>+> nthWeekDayInMonth [3]++The second-to-last weekday of the month:++> recur monthly `starting` sep1997 $+> enumWeekDaysOfMonth [Monday .. Friday] >=>+> nthWeekDayOfMonth [-2]++Every 3 hours from 9:00 AM to 5:00 PM on a specific day:++> takeWhile (<= addSeconds sep1997 (8 * oneHour)) $ +> recur hourly `by` 3 `begin` sep1997++Every 15 minutes for 6 occurrences:++> take 6 $ recur minutely `by` 15 `begin` sep1997++Every hour and a hald for 4 occurrences:++> take 4 $ recur minutely `by` 90 `begin` sep1997++Every 20 minutes from 9:00 AM to 4:40 PM every day:++> recur daily `starting` sep1997 $+> enumHours [9 .. 16] >=>+> enumMinutes [0,20,40]++or++> recur minutely `by` 20 `starting` sep1997 $ enumHours [9 .. 16]++The following two examples will generate different results due to changes in the start of the week.++> take 4 $ recur weekly `by` 2 `withStartOfWeek` Monday $+> enumWeekDaysInWeek [Tuesday,Sunday]++vs++> take 4 $ recur weekly `by` 2 `withStartOfWeek` Sunday $+> enumWeekDaysInWeek [Tuesday,Sunday]++An example where an invalid date (Feb. 30) is ignored:++> take 5 $ recur monthly `starting` jan2000 $ enumDays [15,30]++The 15th and the 30th of the month, forever:++> recur monthly `starting` sep1997 $ enumDays [15,30]++The 15th and the 30th of the month, but only during the work week:+> recur monthly `starting` sep1997 $+> enumDays [15,30] >=>+> filterWeekDays [Monday .. Friday]
+ src/Data/Time/Calendar/Month.hs view
@@ -0,0 +1,50 @@+module Data.Time.Calendar.Month+ (+ -- * Month+ Month (..)+ ) + where++data Month+ = January+ | February+ | March+ | April+ | May+ | June+ | July+ | August+ | September+ | October+ | November+ | December+ deriving (Show, Eq, Ord, Bounded)++instance Enum Month where+ fromEnum January = 1+ fromEnum February = 2+ fromEnum March = 3+ fromEnum April = 4+ fromEnum May = 5+ fromEnum June = 6+ fromEnum July = 7+ fromEnum August = 8+ fromEnum September = 9+ fromEnum October = 10+ fromEnum November = 11+ fromEnum December = 12++ toEnum 1 = January+ toEnum 2 = February+ toEnum 3 = March+ toEnum 4 = April+ toEnum 5 = May+ toEnum 6 = June+ toEnum 7 = July+ toEnum 8 = August+ toEnum 9 = September+ toEnum 10 = October+ toEnum 11 = November+ toEnum 12 = December++ toEnum unmatched = error ("Month.toEnum: Cannot match " ++ show unmatched)
+ src/Data/Time/Calendar/WeekDay.hs view
@@ -0,0 +1,16 @@+module Data.Time.Calendar.WeekDay+ (+ -- * WeekDay+ WeekDay(..)+ )+ where++data WeekDay + = Monday + | Tuesday + | Wednesday + | Thursday + | Friday + | Saturday + | Sunday+ deriving (Show, Eq, Ord, Enum, Bounded)
+ src/Data/Time/CalendarTime.hs view
@@ -0,0 +1,7 @@+module Data.Time.CalendarTime+ (+ module Data.Time.CalendarTime.CalendarTime+ )+ where++import Data.Time.CalendarTime.CalendarTime
+ src/Data/Time/CalendarTime/CalendarTime.hs view
@@ -0,0 +1,124 @@+module Data.Time.CalendarTime.CalendarTime+ (+ -- * Calendar Time+ CalendarTime (..)+ , toDay+ , withDay+ , toTimeOfDay+ , daysInYear+ , lastDayOfMonth+ , weekNumber++ -- * Calendar Time Convertible+ , CalendarTimeConvertible (..)+ ) + where++import Data.Time+import Data.Time.Calendar.OrdinalDate+import Data.Time.Calendar.Month+import Data.Time.Calendar.WeekDay+import Data.Time.Moment.StartOfWeek+import System.IO.Unsafe++-- | A representation of calendar time separated into year, month, day, and so on.+data CalendarTime = CalendarTime + {+ calendarSecond :: Int+ , calendarMinute :: Int+ , calendarHour :: Int+ , calendarDay :: Int+ , calendarMonth :: Month+ , calendarYear :: Integer+ , calendarWeekDay :: WeekDay+ , calendarYearDay :: Int+ , calendarTimeZone :: TimeZone+} deriving (Eq,Ord,Show)++-- | The class of types which can be converted to a 'CalendarTime'+class CalendarTimeConvertible t where+ -- | Convert to a 'CalendarTime'+ toCalendarTime :: t -> CalendarTime+ -- | Convert from a 'CalendarTime'+ fromCalendarTime :: CalendarTime -> Maybe t++instance CalendarTimeConvertible CalendarTime where+ toCalendarTime = id+ fromCalendarTime = Just . id++-- | Convert to a 'Day'+toDay :: CalendarTime -> Maybe Day+toDay t = fromGregorianValid (calendarYear t) (fromEnum $ calendarMonth t) (calendarDay t)++-- | Convert to a 'TimeOfDay'+toTimeOfDay :: CalendarTime -> Maybe TimeOfDay+toTimeOfDay t = makeTimeOfDayValid (calendarHour t) (calendarMinute t) (toEnum $ calendarSecond t)++-- | Change y-m-d in 'CalendarTime'+withDay :: CalendarTime -> Day -> CalendarTime+withDay ct day = ct+ { calendarYear = y+ , calendarMonth = toEnum m+ , calendarDay = d}+ where+ (y, m, d) = toGregorian day++dayInfo :: + Day + -> ( Integer -- Year+ , Int -- Month+ , Int -- Day+ , WeekDay -- Week Day+ , Int -- Year Day+ )+dayInfo day = let+ (y, m, d) = toGregorian day+ weekDay = toEnum $ snd (mondayStartWeek day) - 1+ yearDay = snd $ toOrdinalDate day+ in (y, m, d, weekDay, yearDay)+ ++instance CalendarTimeConvertible UTCTime where+ toCalendarTime (UTCTime utcDay utcTime) = CalendarTime (fromEnum ss) mm hh d (toEnum m) y weekDay yearDay utc+ where+ (TimeOfDay hh mm ss) = timeToTimeOfDay utcTime+ (y, m, d, weekDay, yearDay) = dayInfo utcDay++ fromCalendarTime t = do+ day <- toDay t+ time <- toTimeOfDay t+ return $ UTCTime day (timeOfDayToTime time)++instance CalendarTimeConvertible LocalTime where+ toCalendarTime (LocalTime day t) = CalendarTime (fromEnum $ todSec t) (todMin t) (todHour t) d (toEnum m) y weekDay yearDay tz+ where+ (y, m, d, weekDay, yearDay) = dayInfo day+ tz = unsafePerformIO getCurrentTimeZone++ fromCalendarTime t = do+ day <- toDay t+ time <- toTimeOfDay t+ return $ LocalTime day time++instance CalendarTimeConvertible ZonedTime where+ toCalendarTime (ZonedTime (LocalTime day t) tz) = CalendarTime (fromEnum $ todSec t) (todMin t) (todHour t) d (toEnum m) y weekDay yearDay tz+ where+ (y, m, d, weekDay, yearDay) = dayInfo day++ fromCalendarTime t = do+ day <- toDay t+ time <- toTimeOfDay t+ return $ ZonedTime (LocalTime day time) (calendarTimeZone t)++daysInYear :: (CalendarTimeConvertible a) => a -> Int+daysInYear t = let ct = toCalendarTime t+ in if isLeapYear $ calendarYear ct then 366 else 365++lastDayOfMonth :: (CalendarTimeConvertible a) => a -> Int+lastDayOfMonth t = let ct = toCalendarTime t+ in gregorianMonthLength (calendarYear ct) (fromEnum $ calendarMonth ct)++weekNumber :: (CalendarTimeConvertible a) => StartOfWeek -> a -> Maybe Int+weekNumber _ t = do+ day <- toDay $ toCalendarTime t+ return $ fst $ mondayStartWeek day
+ src/Data/Time/Moment.hs view
@@ -0,0 +1,10 @@+module Data.Time.Moment+ (+ module ReExport+ )+ where++import Data.Time.Moment.Interval as ReExport+import Data.Time.Moment.Moment as ReExport+import Data.Time.Moment.StartOfWeek as ReExport+import Data.Time.Moment.UTC as ReExport
+ src/Data/Time/Moment/Interval.hs view
@@ -0,0 +1,12 @@+module Data.Time.Moment.Interval+ (+ -- * Interval+ Interval (fromInterval)+ , toInterval+ )+ where++import Data.Time.Moment.Private++toInterval :: Integer -> Interval+toInterval = Interval
+ src/Data/Time/Moment/Moment.hs view
@@ -0,0 +1,224 @@+module Data.Time.Moment.Moment+ (+ -- * Moment+ Moment (..)+ , iterateMoments+ , withYearDay+ , withWeekNumber+ , withSecond+ , withMinute+ , withHour+ , withDay+ , withMonth+ , withYear+ , advanceToWeekDay++ -- * Initial Moment+ , InitialMoment (..)++ -- * Default Initial Moment+ , secondly+ , minutely+ , hourly+ , daily+ , weekly+ , monthly+ , yearly++ -- * Adjust Interval+ , by+ -- * Adjust Start of Week+ , withStartOfWeek++ -- * Period+ , Period (..)+ )+ where++import Data.Time.Calendar.Month+import Data.Time.Calendar.OrdinalDate+import Data.Time.Calendar.WeekDay+import Data.Time.CalendarTime hiding (withDay)+import qualified Data.Time.CalendarTime as CT+import Data.Time.Moment.Interval+import Data.Time.Moment.Private+import Data.Time.Moment.StartOfWeek++oneSecond :: Integer+oneSecond = 1++oneMinute :: Integer+oneMinute = 60 * oneSecond++oneHour :: Integer+oneHour = 60 * oneMinute++oneDay :: Integer+oneDay = 24 * oneHour++oneWeek :: Integer+oneWeek = 7 * oneDay++-- | The @Moment@ class is for representing a instance in time.+--+-- Instances of @Moment@ can be derived for any user-defined+-- datatype for which can satisfy the minimal complete definition.+--+-- Minimal complete definition: 'epoch', 'addSeconds', 'addMonths', 'addYears'++class Moment a where+ -- | Provide a default moment.+ epoch :: a+ addSeconds :: a -> Integer -> a+ addMonths :: a -> Integer -> a+ addYears :: a -> Integer -> a++ addMinutes :: a -> Integer -> a+ addMinutes a = addSeconds a . (* oneMinute)+ addHours :: a -> Integer -> a+ addHours a = addSeconds a . (* oneHour)+ addDays :: a -> Integer -> a+ addDays a = addSeconds a . (* oneDay)+ addWeeks :: a -> Integer -> a+ addWeeks a = addSeconds a . (* oneWeek)++ -- | Produce a new @Moment@ in the future ocurring at (/interval/ * /freq/)+ next :: Interval -> Period -> a -> a+ next (Interval interval) freq =+ case freq of+ Seconds -> add oneSecond+ Minutes -> add oneMinute+ Hours -> add oneHour+ Days -> add oneDay+ Weeks -> add oneWeek+ Months -> flip addMonths interval+ Years -> flip addYears interval+ where+ add x = flip addSeconds (interval * x)++ -- | Produce a new @Moment@ in the past ocurring at (-/interval/ * /freq/)+ prev :: Interval -> Period -> a -> a+ prev (Interval interval) = next $ Interval (-interval)++-- | Produce an infinite list from an initial @Moment@ and a step function.+iterateMoments :: Moment a => (a -> a) -> a -> [a]+iterateMoments = iterate++-- | Possibly produce a 'Moment' with the given week number+withWeekNumber :: + (CalendarTimeConvertible a, Moment a) => + StartOfWeek + -> a + -> Int + -> Maybe a+withWeekNumber _ t wk = do+ let ct = toCalendarTime t+ day <- fromMondayStartWeekValid (calendarYear ct) wk (fromEnum $ calendarWeekDay ct)+ fromCalendarTime $ CT.withDay ct day++-- | Possibly produce a 'Moment' with the given day of the year+withYearDay ::+ (CalendarTimeConvertible a, Moment a) =>+ a+ -> Int+ -> Maybe a+withYearDay t yd = do+ let ct = toCalendarTime t+ day <- fromOrdinalDateValid (calendarYear ct) yd+ fromCalendarTime $ CT.withDay ct day++-- | Possibly produce a 'Moment' with the given second+withSecond :: (CalendarTimeConvertible a, Moment a) => a -> Int -> Maybe a+withSecond t s = fromCalendarTime (toCalendarTime t){calendarSecond = s}++-- | Possibly produce a 'Moment' with the given minute+withMinute :: (CalendarTimeConvertible a, Moment a) => a -> Int -> Maybe a+withMinute t m = fromCalendarTime (toCalendarTime t){calendarMinute = m}++-- | Possibly produce a 'Moment' with the given hour+withHour :: (CalendarTimeConvertible a, Moment a) => a -> Int -> Maybe a+withHour t h = fromCalendarTime (toCalendarTime t){calendarHour = h}++-- | Possibly produce a 'Moment' with the given month day+withDay :: (CalendarTimeConvertible a, Moment a) => a -> Int -> Maybe a+withDay t d = fromCalendarTime (toCalendarTime t){calendarDay = d}++-- | Possibly produce a 'Moment' with the given month+withMonth :: (CalendarTimeConvertible a, Moment a) => a -> Month -> Maybe a+withMonth t m = fromCalendarTime (toCalendarTime t){calendarMonth = m}++-- | Possibly produce a 'Moment' with the given year+withYear :: (CalendarTimeConvertible a, Moment a) => a -> Integer -> Maybe a+withYear t y = fromCalendarTime (toCalendarTime t){calendarYear = y}++advanceToWeekDay :: + (CalendarTimeConvertible a, Moment a) => + a -> + WeekDay -> + a+advanceToWeekDay t d = let+ ct = toCalendarTime t+ d0 = calendarWeekDay ct+ d' = fromEnum d+ d0' = fromEnum d0+ delta = toInteger $ d' - d0' `mod` 7+ in addDays t $ if delta == 0 then 7 else delta++-- | The @InitialMoment@ datatype++data InitialMoment a = InitialMoment+ { period :: Period+ , interval :: Interval+ , startOfWeek :: StartOfWeek+ , moment :: a+ }+ deriving (Show)++mkIM :: Moment a => Period -> InitialMoment a+mkIM f = InitialMoment f (toInterval 1) (StartOfWeek Monday) epoch++-- | Default initial moments++secondly :: Moment a => InitialMoment a+secondly = mkIM Seconds++minutely :: Moment a => InitialMoment a+minutely = mkIM Minutes++hourly :: Moment a => InitialMoment a+hourly = mkIM Hours++daily :: Moment a => InitialMoment a+daily = mkIM Days++weekly :: Moment a => InitialMoment a+weekly = mkIM Weeks++monthly :: Moment a => InitialMoment a+monthly = mkIM Months++yearly :: Moment a => InitialMoment a+yearly = mkIM Years++-- | Typically called infix on an existing 'InitialMoment', like:+-- +-- > monthly `by` 2+by :: InitialMoment a -> Integer -> InitialMoment a+by im i = im{interval=toInterval i}++-- | Typically called infix on an existing 'InitialMoment', like:+--+-- > weekly `withStartOfWeek` Tuesday+withStartOfWeek :: InitialMoment a -> WeekDay -> InitialMoment a+withStartOfWeek im sow = im{startOfWeek=toStartOfWeek sow}++-- | @Period@ data type+data Period+ = Seconds+ | Minutes+ | Hours+ | Days+ | Weeks+ | Months+ | Years+ deriving (Enum, Bounded, Eq, Ord, Show)
+ src/Data/Time/Moment/Private.hs view
@@ -0,0 +1,14 @@+module Data.Time.Moment.Private+ (+ -- * Interval+ Interval (..)+ + -- * StartOfWeek+ , StartOfWeek (..)+ )+ where++import Data.Time.Calendar.WeekDay++newtype Interval = Interval { fromInterval :: Integer } deriving (Show)+newtype StartOfWeek = StartOfWeek { fromStartOfWeek :: WeekDay } deriving (Show)
+ src/Data/Time/Moment/StartOfWeek.hs view
@@ -0,0 +1,13 @@+module Data.Time.Moment.StartOfWeek+ (+ -- * StartOfWeek+ StartOfWeek (fromStartOfWeek)+ , toStartOfWeek+ )+ where++import Data.Time.Calendar.WeekDay+import Data.Time.Moment.Private++toStartOfWeek :: WeekDay -> StartOfWeek+toStartOfWeek = StartOfWeek
+ src/Data/Time/Moment/UTC.hs view
@@ -0,0 +1,43 @@+module Data.Time.Moment.UTC+ (+ secondlyUTC+ , minutelyUTC+ , hourlyUTC+ , dailyUTC+ , weeklyUTC+ , monthlyUTC+ , yearlyUTC+ )+ where++import Data.Time+import Data.Time.Moment.Moment++instance Moment UTCTime where+ epoch = UTCTime (toEnum 0) 0+ addSeconds utc i = addUTCTime (fromIntegral i) utc+ addMonths (UTCTime d t) i = UTCTime (addGregorianMonthsRollOver i d) t+ addYears (UTCTime d t) i = UTCTime (addGregorianYearsRollOver i d) t++-- | @InitialMoment@ defaults for @UTCTime@++secondlyUTC :: InitialMoment UTCTime+secondlyUTC = secondly++minutelyUTC :: InitialMoment UTCTime+minutelyUTC = minutely++hourlyUTC :: InitialMoment UTCTime+hourlyUTC = hourly++dailyUTC :: InitialMoment UTCTime+dailyUTC = daily++weeklyUTC :: InitialMoment UTCTime+weeklyUTC = weekly++monthlyUTC :: InitialMoment UTCTime+monthlyUTC = monthly++yearlyUTC :: InitialMoment UTCTime+yearlyUTC = yearly
src/Data/Time/Recurrence.hs view
@@ -1,558 +1,12 @@ module Data.Time.Recurrence (- -- * The @WeekDay@ type- WeekDay (..)-- -- * The @Month@ type- , Month (..)-- -- * The @Moment@ type class- , Moment (..)-- -- * The @DateTime@ type class- , DateTime (..)-- -- * The @InitialMoment@ type- , InitialMoment (..)-- , toInterval- , toStartOfWeek-- , secondly- , minutely- , hourly- , daily- , weekly- , monthly- , yearly-- , secondlyUTC- , minutelyUTC- , hourlyUTC- , dailyUTC- , weeklyUTC- , monthlyUTC- , yearlyUTC-- -- * Recurrence combinators- , enumYear- , enumMonth- , enumWeek-- , restrict- , bySeconds- , byMinutes- , byHours- , byWeekDays- , byMonthDays- , byMonths- , byYearDays-- , expand- , onWeekNumbers- , onMonths- , onMonthDays- , onYearDays-- , onEachWeek- , onEachMonth- , onEachYear-- , repeatSchedule- , repeatSchedule'+ CalendarTime (..)+ , module ReExport ) where -import Control.Applicative-import Control.Monad.Reader-import Data.List.Ordered (nubSort)-import Data.Maybe (fromJust, mapMaybe)-import Data.Time-import Data.Time.Calendar.MonthDay (monthLength)-import Data.Time.Calendar.OrdinalDate (toOrdinalDate, fromOrdinalDateValid, fromMondayStartWeekValid, mondayStartWeek)-import Data.Traversable---- | Symbolic week days.------ Note: The first Day of the Week is Monday --- TODO: Move this to a more general library-data WeekDay- = Monday- | Tuesday- | Wednesday- | Thursday- | Friday- | Saturday- | Sunday- deriving (Show, Eq, Ord, Enum, Bounded)---- | Symbolic months.------ TODO: Move this to a more general library-data Month- = January- | February- | March- | April- | May- | June- | July- | August- | September- | October- | November- | December- deriving (Show, Eq, Ord, Bounded)--instance Enum Month where- fromEnum January = 1- fromEnum February = 2- fromEnum March = 3- fromEnum April = 4- fromEnum May = 5- fromEnum June = 6- fromEnum July = 7- fromEnum August = 8- fromEnum September = 9- fromEnum October = 10- fromEnum November = 11- fromEnum December = 12-- toEnum 1 = January- toEnum 2 = February- toEnum 3 = March- toEnum 4 = April- toEnum 5 = May- toEnum 6 = June- toEnum 7 = July- toEnum 8 = August- toEnum 9 = September- toEnum 10 = October- toEnum 11 = November- toEnum 12 = December-- toEnum unmatched = error ("Month.toEnum: Cannot match " ++ show unmatched)---- | @DateTime@ data type--- This is a componentized version of a time value--- simmilar to a 'struct tm'-data DateTime = DateTime- { dtSecond :: Int- , dtMinute :: Int- , dtHour :: Int- , dtDay :: Int- , dtMonth :: Month- , dtYear :: Integer- , dtWeekDay :: WeekDay- , dtYearDay :: Int- , dtTimeZone :: TimeZone- }- deriving (Show)---- | @Frequency@ data type-data Frequency- = Seconds- | Minutes- | Hours- | Days- | Weeks- | Months- | Years- deriving (Show)--newtype Interval = Interval Integer deriving (Show)-newtype StartOfWeek = StartOfWeek { fromStartOfWeek :: WeekDay } deriving (Show)--toInterval :: Integer -> Interval-toInterval = Interval--toStartOfWeek :: WeekDay -> StartOfWeek-toStartOfWeek = StartOfWeek---- useful time constants-oneSecond :: Integer-oneSecond = 1--oneMinute :: Integer-oneMinute = 60 * oneSecond--oneHour :: Integer-oneHour = 60 * oneMinute--oneDay :: Integer-oneDay = 24 * oneHour--oneWeek :: Integer-oneWeek = 7 * oneDay---- | The @Moment@ class is for representing a instance in time.------ Instances of @Moment@ can be derived for any user-defined--- datatype for which can satisfy the minimal complete definition.------ Minimal complete definition: 'epoch', 'toDateTime', 'fromDateTime',--- 'scaleTime', 'scaleMonth', 'scaleYear', 'alterWeekNumber',--- 'alterYearDay'--class Moment a where-- -- | Provide a default moment.- epoch :: a-- -- | Convert a @Moment@ into a @DateTime@- toDateTime :: a -> DateTime-- -- | Convert a @DateTime@ into a @Moment@- fromDateTime :: DateTime -> Maybe a-- -- | Produce a new @Moment@ offset by a given number of seconds.- scaleTime :: a -> Integer -> a-- -- | Produce a new @Moment@ offset by a given number of months.- scaleMonth :: a -> Integer -> a-- -- | Produce a new @Moment@ offset by a given number of years.- scaleYear :: a -> Integer -> a-- -- | Possibly produce a new @Moment@ shifted to a different week of the year.- alterWeekNumber :: StartOfWeek -> a -> Int -> Maybe a-- -- | Possibly produce a new @Moment@ shifted to a different day of the year.- alterYearDay :: a -> Int -> Maybe a-- -- | The 'alter*' methods can potentially produce invalid dates.- -- - -- For each user-defined @Moment@ instance the definitions of- -- 'toDateTime', 'fromDateTime', 'alterWeekNumber' and 'alterYearDay' - -- will determine if an altered @Moment@ that lands on an invalid date- -- in the given calendar will be reduced to @Nothing@-- -- | Possibly produce a new @Moment@ shifted to a different second of the day.- alterSecond :: a -> Int -> Maybe a- alterSecond x s = fromDateTime (toDateTime x){dtSecond = s}-- -- | Possibly produce a new @Moment@ shifted to a different minute of the day.- alterMinute :: a -> Int -> Maybe a- alterMinute x m = fromDateTime (toDateTime x){dtMinute = m}-- -- | Possibly produce a new @Moment@ shifted to a different hour of the day.- alterHour :: a -> Int -> Maybe a- alterHour x h = fromDateTime (toDateTime x){dtHour = h}-- -- | Possibly produce a new @Moment@ shifted to a different day of the month.- alterDay :: a -> Int -> Maybe a- alterDay x d = fromDateTime (toDateTime x){dtDay = d}-- -- | Possibly produce a new @Moment@ shifted to a different month of the year.- alterMonth :: a -> Month -> Maybe a- alterMonth x m = fromDateTime (toDateTime x){dtMonth = m}-- -- | Possibly produce a new @Moment@ shifted to a different year.- alterYear :: a -> Integer -> Maybe a- alterYear x y = fromDateTime (toDateTime x){dtYear = y}-- -- | Produce a new @Moment@ in the future ocurring at (/interval/ * /freq/)- next :: Interval -> Frequency -> a -> a- next (Interval interval) freq =- case freq of- Seconds -> scale oneSecond- Minutes -> scale oneMinute- Hours -> scale oneHour- Days -> scale oneDay- Weeks -> scale oneWeek- Months -> flip scaleMonth interval- Years -> flip scaleYear interval- where- scale x = flip scaleTime (interval * x)-- -- | Produce a new @Moment@ in the past ocurring at (-/interval/ * /freq/)- prev :: Interval -> Frequency -> a -> a- prev (Interval interval) = next $ Interval (-interval)---- | The @InitialMoment@ datatype--data InitialMoment a = InitialMoment- { frequency :: Frequency- , interval :: Interval- , startOfWeek :: StartOfWeek- , moment :: a- }- deriving (Show)--mkIM :: Moment a => Frequency -> InitialMoment a-mkIM f = InitialMoment f (toInterval 1) (StartOfWeek Monday) epoch---- | Default initial moments--secondly :: Moment a => InitialMoment a-secondly = mkIM Seconds--minutely :: Moment a => InitialMoment a-minutely = mkIM Minutes--hourly :: Moment a => InitialMoment a-hourly = mkIM Hours--daily :: Moment a => InitialMoment a-daily = mkIM Days--weekly :: Moment a => InitialMoment a-weekly = mkIM Weeks--monthly :: Moment a => InitialMoment a-monthly = mkIM Months--yearly :: Moment a => InitialMoment a-yearly = mkIM Years---- | The @Schedule@ datatype--newtype Schedule a = Schedule {fromSchedule :: [a]} deriving (Show, Eq, Ord)---- | Produce an infinite list from an initial @Moment@ and a step function.-iterateMoments :: Moment a => (a -> a) -> a -> [a]-iterateMoments = iterate--type RecurringSchedule a = Reader (InitialMoment a) (Schedule a)--enumMoments :: Moment a => - (Interval -> Frequency -> a -> a) - -> RecurringSchedule a-enumMoments step = do- i <- ask- return $ Schedule $ iterateMoments (step' i) (moment i)- where- step' i = step (interval i) (frequency i)---- | 'enumFutureMoments' is a @Schedule@ of all future moments derived--- from the @InitialMoment@-enumFutureMoments :: Moment a => RecurringSchedule a-enumFutureMoments = enumMoments next---- | 'enumPastMoments' goes in the opposite direction of 'enumFutureMoments'-enumPastMoments :: Moment a => RecurringSchedule a-enumPastMoments = enumMoments prev---- | 'enumPeriod' produces a period from /beg/ to /end/-enumPeriod :: (Moment a, Ord a) => a -> a -> RecurringSchedule a-enumPeriod beg end = do- i <- ask- return $ Schedule $ takeWhile (<= end) $ iterateMoments (step i) beg- where- step i = next (interval i) (frequency i)---- | 'enumPeriodFrom' generalizes 'enumPeriod' by allowing an explicit--- starting moment-enumPeriodFrom :: (Moment a, Ord a) => - InitialMoment a- -> a- -> a- -> RecurringSchedule a-enumPeriodFrom i' beg end = local (const i') (enumPeriod beg end)---- | 'enumYear' produces all days in the year starting with /m/-enumYear :: (Moment a, Ord a) => a -> RecurringSchedule a-enumYear m = do- i <- ask- let mi = moment i- enumPeriodFrom (daily `asTypeOf` i){moment = mi} (startDate' mi) endDate- where- eoy = if isLeapYear $ dtYear $ toDateTime m then 366 else 365- endDate = fromJust $ alterYearDay m eoy- startDate' = max (fromJust $ alterYearDay m 1)---- | 'enumMonth' produces all days in the current month starting with /m/-enumMonth :: (Moment a, Ord a) => a -> RecurringSchedule a-enumMonth m = do- i <- ask- let mi = moment i- enumPeriodFrom (daily `asTypeOf` i){moment = mi} (startDate' mi) endDate- where- dt = toDateTime m- eom = monthLength (isLeapYear $ dtYear dt) (fromEnum $ dtMonth dt)- endDate = fromJust $ alterDay m eom- startDate' = max (fromJust $ alterDay m 1)---- | 'enumWeek' produces all days in the current week starting with /m/-enumWeek :: (Moment a, Ord a) => a -> RecurringSchedule a-enumWeek m = do- i <- ask- let mi = moment i- let sow = startOfWeek i- let dt = toDateTime m- let delta = fromEnum (dtWeekDay dt) - fromEnum (fromStartOfWeek sow)- let delta' = toInteger delta- let endDate = scaleTime m $ (7 - delta') * oneDay- enumPeriodFrom (daily `asTypeOf` i){moment = mi} m endDate---- | Normalize an bounded index--- Pass an upper-bound 'ub' and an index 'idx'--- Converts 'idx' < 0 into valid 'idx' > 0 or--- Nothing-normIndex :: Int -> Int -> Maybe Int-normIndex _ 0 = Nothing-normIndex ub idx =- if abs idx > ub- then Nothing- else Just $ (idx + ub') `mod` ub'- where- ub' = ub + 1--mapNormIndex :: Int -> [Int] -> [Int]-mapNormIndex n = mapMaybe (normIndex n)---- | 'restrict', applied to a predicate and a @Schedule@, returns a @Schedule@--- of those moments that statisfy the predicate.-restrict :: Moment a => (a -> Bool) -> Schedule a -> RecurringSchedule a-restrict f s = return $ Schedule $ filter f $ fromSchedule s--by :: (Moment a, Ord b) => (DateTime -> b) -> [b] -> a -> Bool-by f bs a = f (toDateTime a) `elem` nubSort bs--by' :: Moment a => (DateTime -> Int) -> Int -> [Int] -> a -> Bool-by' f n bs = by f $ mapNormIndex n bs--bySeconds :: Moment a => [Int] -> a -> Bool-bySeconds = by dtSecond--byMinutes :: Moment a => [Int] -> a -> Bool-byMinutes = by dtMinute--byHours :: Moment a => [Int] -> a -> Bool-byHours = by dtHour--byWeekDays :: Moment a => [WeekDay] -> a -> Bool-byWeekDays = by dtWeekDay--byMonthDays :: Moment a => [Int] -> a -> Bool-byMonthDays = by' dtDay 31--byMonths :: Moment a => [Month] -> a -> Bool-byMonths = by dtMonth--byYearDays :: Moment a => [Int] -> a -> Bool-byYearDays = by' dtYearDay 366---- monadic concatMap-concatMapM :: Applicative m => (a -> m [b]) -> [a] -> m [b]-concatMapM f xs = concat <$> traverse f xs---- | 'expand', takes an expansion function and a @Schedule@, and maps the--- expansion function over the moments.--- Each moment is then replaced with its expansions.-expand :: Moment a => (a -> Reader (InitialMoment a) [a]) -> Schedule a -> RecurringSchedule a-expand f s = do- xs <- concatMapM f (fromSchedule s)- return $ Schedule xs--on :: Moment a => - (a -> b -> Maybe a) - -> [b] - -> a - -> Reader (InitialMoment a) [a]-on f bs a = return $ mapMaybe (f a) bs--on' :: Moment a =>- (InitialMoment a -> a -> b -> Maybe a)- -> [b]- -> a- -> Reader (InitialMoment a) [a]-on' f bs a = ask >>= \i -> on (f i) bs a--onEach :: (Moment a, Ord a) => - (a -> RecurringSchedule a) - -> a - -> Reader (InitialMoment a) [a]-onEach f m = fmap fromSchedule (f m)--onEachYear :: (Moment a, Ord a) => a -> Reader (InitialMoment a) [a]-onEachYear = onEach enumYear--onEachMonth :: (Moment a, Ord a) => a -> Reader (InitialMoment a) [a]-onEachMonth = onEach enumMonth--onEachWeek :: (Moment a, Ord a) => a -> Reader (InitialMoment a) [a]-onEachWeek = onEach enumWeek--onMonths :: Moment a => [Month] -> a -> Reader (InitialMoment a) [a]-onMonths = on alterMonth--onMonthDays :: Moment a => [Int] -> a -> Reader (InitialMoment a) [a]-onMonthDays ds = on alterDay (mapNormIndex 31 ds)--onYearDays :: Moment a => [Int] -> a -> Reader (InitialMoment a) [a]-onYearDays ds = on alterYearDay (mapNormIndex 366 ds)--onWeekNumbers :: Moment a => [Int] -> a -> Reader (InitialMoment a) [a]-onWeekNumbers ds = on' (alterWeekNumber . startOfWeek) (mapNormIndex 53 ds)---- | 'repeatSchedule' runs a schedule with a given /init/ and rule /r/-repeatSchedule :: Moment a => - InitialMoment a - -> (Schedule a -> RecurringSchedule a)- -> [a]-repeatSchedule init r = fromSchedule $ runReader (enumFutureMoments >>= r) init---- | 'repeatSchedule'' is like 'repeatSchedule' but it takes no rules-repeatSchedule' :: Moment a =>- InitialMoment a- -> [a]-repeatSchedule' init = repeatSchedule init return---- | Instance of the @Moment@ class defined for the @UTCTime@ datatype.--instance Moment UTCTime where- epoch = UTCTime (toEnum 0) 0- toDateTime (UTCTime utcDay utcTime) =- DateTime (fromEnum seconds) minutes hours- day (toEnum month) year- weekDay yearDay utc- where- (TimeOfDay hours minutes seconds) = timeToTimeOfDay utcTime- (year, month, day) = toGregorian utcDay- yearDay = snd $ toOrdinalDate utcDay- weekDay = toEnum $ snd (mondayStartWeek utcDay) - 1-- fromDateTime dt = do- let _ = dtTimeZone dt -- just called here to shut GHC up for now- day <- fromGregorianValid (dtYear dt) (fromEnum $ dtMonth dt) (dtDay dt)- time <- makeTimeOfDayValid (dtHour dt) (dtMinute dt) (toEnum $ dtSecond dt)- return $ UTCTime day (timeOfDayToTime time)-- scaleTime utc i = addUTCTime (fromIntegral i) utc- scaleMonth (UTCTime d t) i = UTCTime (addGregorianMonthsRollOver i d) t- scaleYear (UTCTime d t) i = UTCTime (addGregorianYearsRollOver i d) t-- -- TODO: First argument is StartOfWeek and is ignored right now. fix.- alterWeekNumber _ utc@(UTCTime _ time) week = do- let dt = toDateTime utc- day <- fromMondayStartWeekValid (dtYear dt) week (fromEnum $ dtWeekDay dt)- return $ UTCTime day time-- alterYearDay utc@(UTCTime _ time) yearDay = do- let dt = toDateTime utc- day <- fromOrdinalDateValid (dtYear dt) yearDay- return $ UTCTime day time---- | @InitialMoment@ defaults for @UTCTime@--secondlyUTC :: InitialMoment UTCTime-secondlyUTC = secondly--minutelyUTC :: InitialMoment UTCTime-minutelyUTC = minutely--hourlyUTC :: InitialMoment UTCTime-hourlyUTC = hourly--dailyUTC :: InitialMoment UTCTime-dailyUTC = daily--weeklyUTC :: InitialMoment UTCTime-weeklyUTC = weekly--monthlyUTC :: InitialMoment UTCTime-monthlyUTC = monthly--yearlyUTC :: InitialMoment UTCTime-yearlyUTC = yearly+import Data.Time.CalendarTime+import Data.Time.Calendar.Month as ReExport+import Data.Time.Calendar.WeekDay as ReExport+import Data.Time.Moment as ReExport+import Data.Time.Recurrence.Schedule as ReExport
+ src/Data/Time/Recurrence/Schedule.hs view
@@ -0,0 +1,316 @@+-- 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 -- abstract, instances: Eq, Ord, Show+ , recur+ , starting+ , begin++ , enumMonths+ , enumDays+ , enumWeekDaysInWeek+ , enumWeekDaysInMonth+ , enumYearDays+ , enumHours+ , enumMinutes+ , enumSeconds++ , nthMonth+ , nthDay+ , nthWeekDay+ , nthWeekDayOfWeek+ , nthWeekDayOfMonth+ , nthYearDay+ , nthHour+ , nthMinute+ , nthSecond++ , filterMonths+ , filterDays+ , filterWeekDays+ , filterYearDays+ , filterHours+ , filterMinutes+ , filterSeconds+ )+ where++import Control.Monad.Reader+import Data.List as L+import Data.List.Ordered as O+import Data.Maybe (mapMaybe, fromJust)+import Data.Time.Calendar.Month+import Data.Time.Calendar.WeekDay+import Data.Time.CalendarTime hiding (withDay)+import Data.Time.Moment++type Schedule a = Reader (InitialMoment a) [a]++runSchedule :: Schedule a -> InitialMoment a -> [a]+runSchedule = runReader++repeatSchedule :: + Moment a => + InitialMoment a + -> ([a] -> Schedule a) + -> [a]+repeatSchedule im sch = runSchedule (iterateInitialMoment >>= sch) im+ where+ iterateInitialMoment :: Moment a => Schedule a+ iterateInitialMoment = do+ im <- ask+ return $ iterate (next (interval im) (period im)) (moment im)++recur :: a -> a+recur = id++-- | 'starting' is an infinite list of 'Moment's, where no 'Moment' +-- occurrs before the 'InitialMoment'. The list is further refined+-- by the passed in function.+starting :: (Ord a, Moment a) => + InitialMoment a + -> a + -> ([a] -> Schedule a) + -> [a]+starting im m0 = dropWhile (< m0) . O.nub . repeatSchedule im{moment=m0}++begin :: (Ord a, Moment a) => InitialMoment a -> a -> [a]+begin im m0 = starting im m0 return++-- | Normalize an bounded ordinal index+-- Pass an upper-bound 'ub' and an index 'idx'+-- Converts 'idx' < 0 into valid 'idx' > 0 or+-- Nothing+normalizeOrdinalIndex :: Int -> Int -> Maybe Int+normalizeOrdinalIndex _ 0 = Nothing+normalizeOrdinalIndex ub idx =+ if abs idx > ub+ then Nothing+ else Just $ (idx + ub') `mod` ub'+ where+ ub' = ub + 1++enumYearDays ::+ (CalendarTimeConvertible a, Moment a) =>+ [Int]+ -> [a]+ -> Schedule a+enumYearDays days as = return $ concatMap (enumYearDays' days) as+ where+ enumYearDays' days a = mapMaybe (withYearDay a) (days' a days)+ days' a = mapMaybe $ normalizeOrdinalIndex (daysInYear a)++enumMonths :: + (CalendarTimeConvertible a, Moment a) => + [Month] + -> [a] + -> Schedule a+enumMonths months as = return $ concatMap (enumMonths' months) as+ where + enumMonths' months a = mapMaybe (withMonth a) months++enumDays ::+ (CalendarTimeConvertible a, Moment a) =>+ [Int]+ -> [a]+ -> Schedule a+enumDays days as = return $ concatMap (enumDays' days) as+ where + enumDays' days a = mapMaybe (withDay a) (days' a days)+ days' a = mapMaybe $ normalizeOrdinalIndex (lastDayOfMonth a)++enumWeekDaysInWeek ::+ (CalendarTimeConvertible a, Moment a) =>+ [WeekDay]+ -> [a]+ -> Schedule a+enumWeekDaysInWeek wdays as = return $ concatMap (enumWeekDays' wdays) as+ where+ enumWeekDays' :: (CalendarTimeConvertible a, Moment a) => [WeekDay] -> a -> [a]+ enumWeekDays' wdays a0 = let+ w0 = calendarWeekDay $ toCalendarTime a0+ wdays' = dropWhile (/= w0) $ O.nubSort wdays+ in map (advanceToWeekDay a0) wdays'++enumWeekDaysInMonth ::+ (CalendarTimeConvertible a, Moment a) =>+ [WeekDay]+ -> [a]+ -> Schedule a+enumWeekDaysInMonth wdays as = return $ concatMap (enumWeekDays' wdays) as+ where+ enumWeekDays' wdays a = let+ mdays = mapMaybe (withDay a) [1 .. lastDayOfMonth a]+ in filter (flip elem wdays . calendarWeekDay . toCalendarTime) mdays+ +enumHours ::+ (CalendarTimeConvertible a, Moment a) =>+ [Int]+ -> [a]+ -> Schedule a+enumHours hours as = return $ concatMap (enumHours' hours) as+ where+ enumHours' hours a = mapMaybe (withHour a) (hours' a hours)+ hours' a = mapMaybe $ normalizeOrdinalIndex 23++enumMinutes ::+ (CalendarTimeConvertible a, Moment a) =>+ [Int]+ -> [a]+ -> Schedule a+enumMinutes ms as = return $ concatMap (enumMinutes' ms) as+ where+ enumMinutes' ms a = mapMaybe (withMinute a) (ms' a ms)+ ms' a = mapMaybe $ normalizeOrdinalIndex 59++enumSeconds ::+ (CalendarTimeConvertible a, Moment a) => + [Int]+ -> [a]+ -> Schedule a+enumSeconds secs as = return $ concatMap (enumSeconds' secs) as+ where+ enumSeconds' secs a = mapMaybe (withSecond a) (secs' a secs)+ secs' a = mapMaybe $ normalizeOrdinalIndex 60++groupWith :: (Ord b) => (a -> b) -> [a] -> [[a]]+groupWith f = groupBy (\a b -> f a == f b)++nth :: [Int] -> [a] -> [a]+nth ns as = map ((as !!) . pred) $ mapMaybe (normalizeOrdinalIndex (length as)) ns++nth' ::+ Ord b => + (a -> b)+ -> [Int]+ -> [a]+ -> Schedule a+nth' f ns as = return $ concatMap (nth ns) $ groupWith f as++nthYearDay ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthYearDay = nth' $ calendarYear . toCalendarTime++nthMonth ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthMonth = nth' $ calendarYear . toCalendarTime++nthDay ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthDay = nth' $ calendarMonth . toCalendarTime++nthWeekDayOfWeek ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthWeekDayOfWeek ns as = do+ sow <- asks startOfWeek+ return $ + concatMap (nth ns) $+ concatMap (groupWith (weekNumber sow)) $+ groupWith (calendarMonth . toCalendarTime) as ++nthWeekDayOfMonth ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthWeekDayOfMonth = nth' $ calendarMonth . toCalendarTime++nthWeekDay ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthWeekDay = nth' $ calendarYear . toCalendarTime++nthHour ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthHour = nth' $ calendarDay . toCalendarTime++nthMinute ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthMinute = nth' $ calendarHour . toCalendarTime++nthSecond ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+nthSecond = nth' $ calendarMinute . toCalendarTime++filterCalendarTime ::+ (CalendarTimeConvertible a, Eq b) =>+ (CalendarTime -> b)+ -> [b]+ -> [a]+ -> Schedule a+filterCalendarTime f xs as = return $ filter (flip elem xs . f . toCalendarTime) as++filterMonths ::+ CalendarTimeConvertible a =>+ [Month]+ -> [a]+ -> Schedule a+filterMonths = filterCalendarTime calendarMonth++filterYearDays ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+filterYearDays = filterCalendarTime calendarYearDay++filterDays ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+filterDays = filterCalendarTime calendarDay++filterWeekDays ::+ CalendarTimeConvertible a => + [WeekDay]+ -> [a]+ -> Schedule a+filterWeekDays = filterCalendarTime calendarWeekDay++filterHours ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+filterHours = filterCalendarTime calendarHour++filterMinutes ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+filterMinutes = filterCalendarTime calendarMinute++filterSeconds ::+ CalendarTimeConvertible a =>+ [Int]+ -> [a]+ -> Schedule a+filterSeconds = filterCalendarTime calendarSecond
tests/Tests.lhs view
@@ -31,7 +31,7 @@ > date1 = parse822Time "Tue, 02 Sep 1997 09:00:00 -0400" > date2 = parse822Time "Wed, 24 Dec 1997 00:00:00 -0400" > date3 = parse822Time "Thu, 01 Jan 1998 09:00:00 -0400"-> date4 = parse822Time "Mon, 01 Jan 2000 09:00:00 -0400"+> date4 = parse822Time "Mon, 31 Jan 2000 09:00:00 -0400" > main :: IO () > main = defaultMain tests@@ -43,19 +43,19 @@ > tests = > [ testGroup "RFC5445 Examples" $ zipWith (testCase . show) [1::Int ..] > [ assertEqual ("Test Daily from "++ show date1 ++". 10 Occurrences") -> (take 10 $ repeatSchedule' dailyUTC{moment = date1})-> (take 10 $ repeatSchedule monthlyUTC{moment = date1} $ expand (onMonthDays [2 .. 11]))+> (take 10 $ recur daily `begin` date1)+> (take 10 $ recur monthly `starting` date1 $ enumDays [2 .. 11]) > , assertEqual ("Test Daily from "++ show date1 ++". Until "++ show date2)-> (until date2 $ repeatSchedule' dailyUTC{moment = date1})-> (until date2 $ repeatSchedule monthlyUTC{moment = date1} $ expand onEachMonth)+> (until date2 $ recur daily `begin` date1)+> (until date2 $ recur monthly `starting` date1 $ enumWeekDaysInMonth [Monday .. Sunday]) > , assertBool ("Test every other day from "++ show date1 ++". Cap at 10000")-> (checkDayDist 2 $ take 10000 $ repeatSchedule' dailyUTC{moment = date1, interval = toInterval 2})+> (checkDayDist 2 $ take 10000 $ recur daily `by` 2 `begin` date1) > , assertEqual ("Test every 10 days from "++ show date1 ++". 5 Occurrences")-> (take 5 $ repeatSchedule' dailyUTC{moment = date1, interval = toInterval 10})-> (take 5 $ repeatSchedule yearlyUTC{moment = date1} $ expand (onMonths [September,October]) >=> expand (onMonthDays [2,12,22]))+> (take 5 $ recur daily `by` 10 `begin` date1)+> (take 5 $ recur yearly `starting` date1 $ enumMonths [September, October] >=> enumDays [2,12,22]) > , assertEqual "Test every day in Jan. for 3 years"-> (until date4 $ repeatSchedule yearlyUTC{moment = date3} $ expand (onMonths [January]) >=> expand onEachMonth)-> (until date4 $ repeatSchedule dailyUTC{moment = date3} $ restrict (byMonths [January]))+> (until date4 $ recur yearly `starting` date3 $ enumMonths [January] >=> enumWeekDaysInMonth [Monday .. Sunday])+> (until date4 $ recur daily `starting` date3 $ filterMonths [January]) > ] > ]
time-recurrence.cabal view
@@ -7,7 +7,7 @@ -- The package version. See the Haskell package versioning policy -- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for -- standards guiding when and how versions should be incremented.-Version: 0.4.2+Version: 0.5.2 -- A short (one-line) description of the package. Synopsis: Generate recurring dates.@@ -64,7 +64,20 @@ ghc-options: -Wall -fno-warn-name-shadowing -- Modules exported by the library.- Exposed-modules: Data.Time.Recurrence+ Exposed-modules:+ Data.Time.Calendar.Month,+ Data.Time.Calendar.WeekDay,+ Data.Time.CalendarTime,+ Data.Time.Moment,+ Data.Time.Moment.Interval,+ Data.Time.Moment.StartOfWeek,+ Data.Time.Recurrence,+ Data.Time.Recurrence.Schedule+ Other-modules:+ Data.Time.CalendarTime.CalendarTime,+ Data.Time.Moment.Moment,+ Data.Time.Moment.Private,+ Data.Time.Moment.UTC -- Packages needed in order to build this package. Build-depends: base >= 4 && < 5,@@ -85,6 +98,9 @@ main-is: Tests.lhs ghc-options: -Wall -fno-warn-name-shadowing build-depends: base >= 4 && < 5,+ time >= 1.2 && < 1.2.0.6,+ data-ordlist >= 0.4 && < 0.4.5,+ mtl >= 2.0.1.0 && < 2.1, test-framework >= 0.4.0 && < 0.5, test-framework-hunit >= 0.2.6 && < 0.3, HUnit >= 1.2.2.3 && < 1.3,