packages feed

time-patterns (empty) → 0.1.0.2

raw patch · 5 files changed

+572/−0 lines, 5 filesdep +basedep +intervalsdep +lenssetup-changed

Dependencies added: base, intervals, lens, thyme, vector-space

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Jann Mueller
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Jann Mueller nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ src/Data/Time/Patterns.hs view
@@ -0,0 +1,326 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Time.Patterns+-- Copyright   :  (C) 2013 Jann Mueller+-- License     :  BSD3 (see the file LICENSE)+-- Maintainer  :  j.mueller.11@ucl.ac.uk+-- Stability   :  experimental+-- Patterns for re-occurring events. Use the @DatePattern@ type to build up+-- a pattern, and the functions @elementOf@, @instancesFrom@ and +-- @intervalsFrom@ to evaluate it.+-- Simple example:+--+-- > import Control.Lens+-- > import Data.Thyme.Calendar+-- > import Data.Time.Patterns+-- > import qualified Prelude as P+-- > Module Main where+-- > +-- > main = do+-- >   -- get the 6th of April for the next ten years+-- >   let april6 = (take 1 $ skip 5 day) `inEach` april+-- >   let today = (YearMonthDay 2013 12 01)^.from gregorian+-- >   print $ P.take 10 $ instancesFrom today april6+-- >+----------------------------------------------------------------------------+module Data.Time.Patterns(+    -- * Date Patterns+    day,+    mondayWeek,+    sundayWeek,+    month,+    year,+    -- ** Months+    january,+    february,+    march,+    april,+    may,+    june,+    july,+    august,+    september,+    october, +    november,+    december,+    -- ** Days+    monday,+    tuesday,+    wednesday,+    thursday,+    friday,+    saturday,+    sunday,+    -- * Operations on date patterns+    never,+    every,+    shiftBy,+    inEach,+    take,+    skip,+    except,+    intersect,+    union,+    -- * Queries+    elementOf,+    instancesFrom,+    intervalsFrom+    ) where++import Numeric.Interval+import Control.Lens hiding (elementOf, elements, contains)+import Data.Thyme.Calendar (Day, Days, Months, YearMonthDay(..), gregorian, modifiedJulianDay, _ymdYear, _ymdMonth, _ymdDay)+import Data.Thyme.Calendar.WeekDate (_mwDay, _swDay)+import qualified Data.Thyme.Calendar.WeekDate as W+import Data.Time.Patterns.Internal hiding (elementOf, every, never, take, skip, except, intersect, occurrencesFrom, union)+import qualified Data.Time.Patterns.Internal as I+import Prelude hiding (cycle, elem, filter, take)+import qualified Prelude as P++-- | An event that occurs every month.+month :: DatePattern+month = IntervalSequence $ \t -> +        let m = firstOfMonth t in+        let m' = addMonths 1 m in+        Just (I m m', month) where++-- | Every January.+january :: DatePattern+january = monthOfYear 1++-- | Every February.+february :: DatePattern+february = monthOfYear 2++-- | Every March.+march :: DatePattern+march = monthOfYear 3++-- | Every April.+april :: DatePattern+april = monthOfYear 4++-- | Every May.+may :: DatePattern+may = monthOfYear 5++-- | Every June.+june :: DatePattern+june = monthOfYear 6++-- | Every July.+july :: DatePattern+july = monthOfYear 7++-- | Every August.+august :: DatePattern+august = monthOfYear 8++-- | Every September.+september :: DatePattern+september = monthOfYear 9++-- | Every October.+october :: DatePattern+october = monthOfYear 10++-- | Every November.+november :: DatePattern+november = monthOfYear 11++-- | Every December.+december :: DatePattern+december = monthOfYear 12++-- | An event that occurs every day.+day :: DatePattern+day = IntervalSequence{..} where+    nextInterval t = Just (I t (succ t), day)++-- | Every Monday.+monday :: DatePattern+monday = filter (isDayOfWeek 1) day++-- | Every Tuesday.+tuesday :: DatePattern+tuesday = filter (isDayOfWeek 2) day++-- | Every Wednesday.+wednesday :: DatePattern+wednesday = filter (isDayOfWeek 3) day++-- | Every Thursday.+thursday :: DatePattern+thursday = filter (isDayOfWeek 4) day++-- | Every Friday.+friday :: DatePattern+friday = filter (isDayOfWeek 5) day++-- | Every Saturday.+saturday :: DatePattern+saturday = filter (isDayOfWeek 6) day++-- | Every Sunday.+sunday :: DatePattern+sunday = filter (isDayOfWeek 7) day++-- | Weeks, starting on Monday+mondayWeek :: DatePattern+mondayWeek = IntervalSequence $ \d -> let m = lastMonday d in +    Just (I m $ addDays 7 m, mondayWeek)++-- | Weeks, starting on Sunday.+sundayWeek :: DatePattern+sundayWeek = IntervalSequence $ \d -> let m = lastSunday d in +    Just (I m $ addDays 7 m, sundayWeek)++-- | Years, starting from Jan. 1+year :: DatePattern+year = IntervalSequence $ \d -> let m = jan1 d in+    Just (I m $ addYears 1 m, year)++-- | The first pattern repeated for each interval of the+--   second pattern. E.g.:+--   +--   > (take 3 $ every 4 monday) `inEach` year+--+--  will give the fourth, eighth and twelveth Monday in each year+inEach :: DatePattern -> DatePattern -> DatePattern+inEach i o = IntervalSequence (inEach' o i i)++-- | like inEach, except that the ``inner`` DatePattern is replaced by a sequence of DatePatterns+--   so that for every new outer interval, the next element from the sequence will be used.+inEach' :: DatePattern -> DatePattern -> DatePattern -> Day -> Maybe (Interval Day, DatePattern)+inEach' outer inner orig d = do+        (o1, outer') <- nextInterval outer d+        let inner' = stopAt' (sup o1) inner+        case (firstOccurrenceIn (max d $ inf o1) o1 inner') of+                Nothing           -> inEach' outer' orig orig $ sup o1+                Just (i1,inner'') -> return (i1, IntervalSequence $ inEach' outer inner'' orig)++-- | Shift all the results by a number of day+shiftBy :: Days -> DatePattern -> DatePattern+shiftBy n sq = mapS (addDays n) sq++-- | Add a number of day to a day+addDays :: Days -> Day -> Day+addDays n d = (d^.modifiedJulianDay + n)^.from modifiedJulianDay++-- | Take every nth occurrence+every :: Int -> DatePattern -> DatePattern+every = I.every++-- | Stop after n occurrences+take :: Int -> DatePattern -> DatePattern+take = I.take++-- | Skip the first n occurrences+skip :: Int -> DatePattern -> DatePattern+skip = I.skip++-- | Skip over all occurrences of a day.+--   If the pattern describes a  period longer+--   than a day, the entire period will be+--   skipped.+except :: Day -> DatePattern -> DatePattern+except = I.except++-- | Check if a date is covered by a DatePattern+elementOf :: Day -> DatePattern -> Bool+elementOf = I.elementOf++-- | Get occurrences of an event starting with a given day+instancesFrom :: Day -> DatePattern -> [Day]+instancesFrom = I.elementsFrom++-- | An event that never occurs+never :: DatePattern+never = I.never++-- | Return only occurrences that are present in both patterns+--+-- > let myBirthday = (take 1 day) `inEach` august+-- > let s = intersect myBirthday sunday+--+-- Will return August 1 in years when it falls on a Sunday+intersect :: DatePattern -> DatePattern -> DatePattern+intersect = I.intersect++-- | Occurrences of both patterns.+--+-- > union april june+-- +-- Will return the months April and June in each year+--+-- > let fifteenth = (take 1 $ skip 14 day) `inEach` month+-- > let third = (take 1 $ skip 2 day) `inEach` month+-- > union fifteenth third+--+-- Will return the 3rd and the 15th of each month+union :: DatePattern -> DatePattern -> DatePattern+union = I.union++-- | Get the date intervals described by the pattern, starting+--   from the specified date. +--   +--   The intervals range from the first+--   day included by the pattern to the first day after it, so+--   a single day @d@ would be described as @(d ... succ d)@ and+--   the interval for a month will go from the 1st of the month+--   to the 1st of the next month.+intervalsFrom :: Day -> DatePattern -> [Interval Day]+intervalsFrom = I.occurrencesFrom++-- | Check if a day interval covers exactly a given weekday+--   with Monday = 1, Tuesday = 2, etc.+isDayOfWeek :: Int -> Interval Day -> Bool+isDayOfWeek d i = case (elements i) of+    [dt] -> dt^. W.mondayWeek . _mwDay == d+    _   -> False++-- | Get the last Monday before or on the date+lastMonday :: Day -> Day+lastMonday d = case (d^.W.mondayWeek._mwDay) of+    1 -> d+    _ -> lastMonday $ pred d+++-- | Get the last Monday before or on the date+lastSunday :: Day -> Day+lastSunday d = case (d^.W.sundayWeek._swDay) of+    1 -> d+    _ -> lastSunday $ pred d++-- | Get the beginning of a year+jan1 :: Day -> Day+jan1 d = let d' = d^.gregorian in +    (YearMonthDay (d'^._ymdYear) 1 1)^.from gregorian++addYears :: Int -> Day -> Day+addYears n d = let d' = d^.gregorian in +    (YearMonthDay (d'^._ymdYear + n) (d'^._ymdMonth) (d'^._ymdDay))^.from gregorian++addMonths :: Months -> Day -> Day+addMonths m d = let d' = d^.gregorian in +    let (years,months) = (d'^._ymdMonth + m) `divMod` 12 in+    (YearMonthDay (d'^._ymdYear + years) months (d'^._ymdDay))^.from gregorian++firstOfMonth :: Day -> Day+firstOfMonth d = let d' = d^.gregorian in +    (YearMonthDay (d'^._ymdYear) (d'^._ymdMonth) 1)^.from gregorian++get1stOfMonth :: Int -> Day -> Day+get1stOfMonth i d = +    let d' = d^.gregorian in +    let y = abs $ (i - d'^._ymdMonth) `div` 12 in+    (YearMonthDay (d'^._ymdYear + y) i 1)^.from gregorian++getMonth :: Int -> Day -> Interval Day+getMonth i d = (d' ... addMonths 1 d')+    where+        d' = get1stOfMonth i d++monthOfYear :: Int -> DatePattern+monthOfYear i = IntervalSequence $ \d -> Just (getMonth i d, monthOfYear i)
+ src/Data/Time/Patterns/Internal.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Time.Patterns.Internal+-- Copyright   :  (C) 2013 Jann Mueller+-- License     :  BSD3 (see the file LICENSE)+-- Maintainer  :  j.mueller.11@ucl.ac.uk+-- Stability   :  experimental+-- Internal stuff for time patterns+----------------------------------------------------------------------------+module Data.Time.Patterns.Internal where++import Numeric.Interval+import Data.Thyme.Calendar (Day)+import Data.Thyme.Clock (UTCTime)+import Prelude hiding (cycle, elem, filter, take)++-- | If the argument to nextOccurrence is part of an interval, then the result should be the interval containing it.+-- The interval should be closed at the first parameter and open at the second, so that repeated calls of+-- nextOccurrence yield a sequence of occurrences.+newtype IntervalSequence t = IntervalSequence { nextInterval :: t -> Maybe (Interval t, IntervalSequence t)}++type DatePattern = IntervalSequence Day+type TimePattern = IntervalSequence UTCTime -- TimePattern should cycle throu+++-- | A sequence with no occurrences+never :: IntervalSequence t+never = IntervalSequence $ const $ Nothing++-- | Take every nth occurrence+every :: Int -> IntervalSequence t -> IntervalSequence t+every n sq@IntervalSequence{..} +  | n < 1 = never+  | otherwise = IntervalSequence $ nextOcc 1+      where+        nextOcc n'  d +            | n' == n = nextInterval d >>= \s -> return (fst s, every n sq)+            | otherwise = nextInterval d >>= nextOcc (n' + 1) . sup . fst++-- | Accept results which satisfy a condition+filter :: (Interval t -> Bool) -> IntervalSequence t -> IntervalSequence t+filter f IntervalSequence{..} = IntervalSequence nOcc' where+    nOcc' t = nextInterval t >>= checkCondition+    checkCondition (p,q) = case (f p) of+        True -> Just (p, filter f q)+        False -> nOcc' $ sup p+++-- | Repeat a point infinitely+cycle :: Interval t -> IntervalSequence t+cycle i = IntervalSequence $ const $ Just (i, cycle i)++-- | Check if a point is covered by an interval sequence+elementOf :: Ord t => t -> IntervalSequence t -> Bool+elementOf t IntervalSequence{..} = maybe False (\(p,_) -> (elem t p) && (<) t (sup p)) (nextInterval t)++-- | The sequence of occurrences from an initial point.+occurrencesFrom :: t -> IntervalSequence t -> [Interval t]+occurrencesFrom start IntervalSequence{..} = case (nextInterval start) of+    Nothing -> []+    Just (res, sq') -> res : occurrencesFrom (sup res) sq'++-- | Elements covered by an interval sequence from an initial point.+elementsFrom :: Enum t => t -> IntervalSequence t -> [t]+elementsFrom start sq = concat $ fmap elements $ occurrencesFrom start sq++elements :: Enum a => Interval a -> [a]+elements i = enumFromTo (inf i) (pred $ sup i)++-- | End a sequence after n occurrences+take :: Int -> IntervalSequence t -> IntervalSequence t+take n IntervalSequence{..} +    | n < 1     = never+    | otherwise = IntervalSequence $ \d -> +        nextInterval d >>= \r -> Just (fst r, take (pred n) $ snd r)++-- | Skip the first n occurrences of a sequence+skip :: Int -> IntervalSequence t -> IntervalSequence t+skip n sq+  | n < 0 = never+  | otherwise = IntervalSequence $ nextOcc (nextInterval sq) n+      where+        nextOcc ni n' d +            | n' < 1 = ni d +            | otherwise = ni d >>= \(p, q) -> nextOcc (nextInterval q) (n' - 1) (sup p)++-- | Take occurrences until an interval is reached+stopAt :: Ord t => Interval t -> IntervalSequence t -> IntervalSequence t+stopAt p IntervalSequence{..} = IntervalSequence ni' where+    ni' d = nextInterval d >>= \(p', q) -> case (p' `contains` p) of+        True -> Nothing+        False -> return (p', stopAt p q)++stopAt' :: Ord t => t -> IntervalSequence t -> IntervalSequence t+stopAt' p IntervalSequence{..} = IntervalSequence ni' where+    ni' d = nextInterval d >>= \(p', q) -> case (sup p' >= p) of+        True -> Nothing+        False -> return (p', stopAt' p q)++-- | Stop as soon as a result greater than or equal to the parameter+--   is produced+before :: Ord t => Interval t -> IntervalSequence t -> IntervalSequence t+before p IntervalSequence{..} = IntervalSequence ni' where+    ni' d = nextInterval d >>= \(p', q) -> case (p >=! p') of+        False -> Nothing+        True  -> return (p', stopAt p q)++skipUntil :: Ord t => Interval t -> IntervalSequence t -> IntervalSequence t+skipUntil fr sq = IntervalSequence $ nextOcc $ nextInterval sq where+    nextOcc ni d = ni d >>= \(p', q) -> case (fr <=! p') of+        False -> nextOcc (nextInterval q) (sup p')+        True  -> return (p', q)++-- | Skip over a point in the sequence. All occurrences of this+--   datum are removed.+except :: (Enum t, Ord t) => t -> IntervalSequence t -> IntervalSequence t+except p = except' (p ... succ p)++-- | Skip over all intervals which contain the parameter+except' ::Ord t => Interval t -> IntervalSequence t -> IntervalSequence t+except' p IntervalSequence{..} = IntervalSequence ni' where+    ni' d = nextInterval d >>= \(p', q) -> case (p' `contains` p) of+        False -> return (p', except' p q) +        True -> ni' $ sup p++-- | Apply a function to the results of an interval sequence+mapS :: (t -> t) -> IntervalSequence t -> IntervalSequence t+mapS f IntervalSequence{..} = IntervalSequence nOcc' where+    nOcc' d = nextInterval d >>= \r -> return (fmap f $ fst r, snd r)++firstOccurrenceIn :: (Enum t, Ord t) => t -> Interval t -> IntervalSequence t -> Maybe (Interval t, IntervalSequence t)+firstOccurrenceIn s i IntervalSequence{..} = firstOcc s where+    firstOcc start = do+        (p, q) <- nextInterval start+        case (i `contains` p) of+            True -> return (p, q)+            False -> case (sup p < sup i) of+                True ->  firstOcc $ sup p+                False -> Nothing++-- | Return intervals that are exactly the same+intersect :: (Ord t, Enum t) => IntervalSequence t -> IntervalSequence t -> IntervalSequence t+intersect a b = IntervalSequence (nOcc' a b) where+    nOcc' a' b' d = do+        (ia, sa) <- nextInterval a' d+        (ib, sb) <- nextInterval b'  $ inf ia+        case ((sup ia == sup ib) && (inf ia == inf ib)) of+            True -> return (ib, intersect sa sb)+            False -> nOcc' b' sa $ sup ia -- mix up a' and b' to search in both directions evenly++-- | Merge two sequences into one by switching between them+diag :: IntervalSequence t -> IntervalSequence t -> IntervalSequence t+diag a b = IntervalSequence (nOcc' a b) where+    nOcc' a' b' d = do+        (na, sa) <- nextInterval a' d+        return (na, diag b' sa)++-- | Occurrences from both intervals.+union :: Ord t => IntervalSequence t -> IntervalSequence t -> IntervalSequence t+union a b = IntervalSequence $ \d ->+    case (nextInterval a d, nextInterval b d) of+        (Nothing, Nothing) -> Nothing+        (Nothing, b')       -> b'+        (a',       Nothing) -> a'+        (Just (ia, sa), Just (ib, sb)) -> +            case (sup ia <= sup ib) of+                True -> return (ia, union sa (ib `andThen` sb))+                False -> return (ib, union (ia `andThen` sa) sb)++-- | Prepend an interval to an interval sequence+andThen :: Interval t -> IntervalSequence t -> IntervalSequence t+andThen i sq = IntervalSequence $ \_ -> Just (i, sq)+
+ time-patterns.cabal view
@@ -0,0 +1,40 @@+-- Initial time-patterns.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                time-patterns+version:             0.1.0.2+synopsis:            Patterns of re-occurring events    +license:             BSD3+license-file:        LICENSE+author:              Jann Müller+maintainer:          j.mueller.11@ucl.ac.uk+copyright:           Copyright (C) 2013 Jann Müller+category:            Data, Time+build-type:          Simple+cabal-version:       >= 1.10+homepage:            https://bitbucket.org/jfmueller/time-patterns+bug-reports:         https://bitbucket.org/jfmueller/time-patterns/issues+synopsis:            Patterns for re-occurring events.+description:         +  This package contains a set of primitives and combinators for event patterns. Example:+  .+  > >> import qualified Prelude as P+  > >> let sundays = every 2 sunday+  > >> let today = (YearMonthDay 2013 12 01)^.from gregorian+  > >> P.take 2 $ instancesFrom today sundays+  > [2013-12-08, 2013-12-22]++source-repository head+  type: git+  location: git@bitbucket.org:jfmueller/time-patterns.git++library+  exposed-modules:     Data.Time.Patterns+  other-modules:       Data.Time.Patterns.Internal+  build-depends:       base >=4.6 && <4.7,+                       intervals == 0.4,+                       lens >= 3.9,+                       thyme == 0.3.*,+                       vector-space == 0.8.6+  hs-source-dirs:      src+  default-language:    Haskell2010