module Data.TimeSeries.Periodic (
Period(..),
Weekdays(..),
periodStep,
periodStepBack,
PeriodicSequence,
periodicSequence,
nth,
psToList,
psOver,
psToUTimeList,
periodStepUTime,
periodStepBackUTime,
psOverUTime,
) where
import Control.Arrow (first, second)
import Control.Lens
import Data.Fixed (divMod')
import Data.Time
import Data.Set (Set)
import qualified Data.Set as S
import Data.UTime
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as V
data Period
= PicoSeconds Integer
| Seconds Int
| Minutes Int
| Hours Int
| Days Int
| Weeks Int
| Workdays
| Weekdays (Set Weekdays)
| Months Int
| Years Int
deriving (Eq, Show, Read)
data Weekdays
= Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Enum)
data PeriodicSequence
= PSecs !DiffTime !Integer !DiffTime
| PDays !Integer !Integer !DiffTime
| PCal !Integer !Int !Integer !Int !Int !DiffTime
| PWeek !(Vector Int) !Integer !Int !DiffTime
deriving (Eq, Show)
periodicSequence :: Period
-> UTCTime
-> PeriodicSequence
periodicSequence per start = case per of
PicoSeconds p -> PSecs (picosecondsToDiffTime p) dayi dayt
Seconds s -> PSecs (fromIntegral s) dayi dayt
Minutes mp -> PSecs (60 * fromIntegral mp) dayi dayt
Hours h -> PSecs (3600 * fromIntegral h) dayi dayt
Days d -> PDays (fromIntegral d) dayi dayt
Weeks w -> PDays (fromIntegral w * 7) dayi dayt
Months mp -> PCal 0 mp y m md dayt
Years yp -> PCal (fromIntegral yp) 0 y m md dayt
Workdays -> PWeek v (dMon + 7*w) i dayt
where
v = V.fromList [0, 1, 2, 3, 4]
(w, i) = firstAfter v wday
Weekdays s -> PWeek v (dMon + 7*w) i dayt
where
v = V.fromList . map fromEnum . S.toList $ s
(w, i) = firstAfter v wday
where
(UTCTime day@(ModifiedJulianDay dayi) dayt) = start
(y,m,md) = toGregorian day
wday = (dayi + 2) `mod` 7
dMon = dayi wday
firstAfter :: Vector Int -> Integer -> (Integer, Int)
firstAfter v a = case V.findIndex (>= fromIntegral a) v of
Just i -> (0, i)
Nothing -> (1, 0)
nth :: PeriodicSequence -> Int -> UTCTime
nth ps k0 = case ps of
PSecs p day dt -> UTCTime (ModifiedJulianDay $ day + day') dt'
where (day', dt') = (dt + p * fromIntegral k0) `divMod'` 86400
PDays p day dt -> UTCTime (ModifiedJulianDay $ day + k * p) dt
PCal yp mp y m d dt -> UTCTime (fromGregorian (y + k * yp + y') m' d) dt
where
(y', m') = first fromIntegral . second (+1) $ (m + k0 * mp 1) `divMod` 12
PWeek v day0 mday dt -> UTCTime (ModifiedJulianDay day) dt
where
day = day0 + fromIntegral (7 * w + v V.! i)
(w, i) = (mday + k0) `divMod` V.length v
where
k = fromIntegral k0
psToList :: PeriodicSequence -> [UTCTime]
psToList ps = go 0
where
go !k = nth ps k : go (k+1)
periodStep :: Period -> UTCTime -> UTCTime
periodStep p t = nth (periodicSequence p t) 1
periodStepBack :: Period -> UTCTime -> UTCTime
periodStepBack p t = nth (periodicSequence p t) (1)
psOver :: Period -> (UTCTime, UTCTime) -> [UTCTime]
psOver p (start, end) = takeWhile (<= end) $ psToList $ periodicSequence p start
psToUTimeList :: PeriodicSequence -> [UTime]
psToUTimeList = map toUTime . psToList
periodStepUTime :: Period -> UTime -> UTime
periodStepUTime p = under utime (periodStep p)
periodStepBackUTime :: Period -> UTime -> UTime
periodStepBackUTime p = under utime (periodStepBack p)
psOverUTime :: Period -> (UTime, UTime) -> [UTime]
psOverUTime p (start, end)
= takeWhile (<= end) . psToUTimeList . periodicSequence p $ fromUTime start