HQu-0.0.0.0: src/Q/Stats/TimeSeries.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
module Q.Stats.TimeSeries where
import qualified Data.ByteString.Lazy as B
import Data.Csv ((.:))
import qualified Data.Csv as Csv
import qualified Data.Map as M
import Data.Maybe (fromJust)
import qualified Data.Text as T
import Data.Time (Day, LocalTime (LocalTime), midnight)
import Data.Time.Format ()
import Data.Time.Format.ISO8601 (FormatExtension (BasicFormat),
calendarFormat, formatParseM,
formatShow, localTimeFormat,
timeOfDayFormat)
import Data.Vector (Vector, toList)
import GHC.Generics (Generic)
-- A single data point with a time and value.
data DataPoint a b = DataPoint {
dpT :: a -- ^Time
, dpV :: b -- ^Value
} deriving (Generic, Show, Eq, Ord)
{-|
Read a a csv row with 2 columns: `date,value` where `date` is
in shortened iso format. (with our without time)
-}
instance Csv.FromNamedRecord (DataPoint LocalTime Double) where
parseNamedRecord m = DataPoint
<$> fmap (fromJust . parseDateTime) (m .: "date")
<*> (m .: "value")
{-|
Read a a csv row with 2 columns: `date,value` where `date` is
in year fractions.
-}
instance Csv.FromNamedRecord (DataPoint Double Double) where
parseNamedRecord m = DataPoint
<$> (m .: "date")
<*> (m .: "value")
parseDateTime :: String -> Maybe LocalTime
parseDateTime iso_datetime =
if length iso_datetime == 8 then
parseDay iso_datetime
else
formatParseM localTimeFormat' iso_datetime
localTimeFormat' = localTimeFormat (calendarFormat BasicFormat) (timeOfDayFormat BasicFormat)
dayFormat' = calendarFormat BasicFormat
parseTime :: String -> Maybe LocalTime
parseTime = formatParseM localTimeFormat'
parseDay :: String -> Maybe LocalTime
parseDay iso_date = do
day <- formatParseM dayFormat' iso_date
return $ LocalTime day midnight
dayToString :: Day -> T.Text
dayToString = T.pack . formatShow dayFormat'
dateToString :: LocalTime -> String
dateToString = formatShow (localTimeFormat (calendarFormat BasicFormat) (timeOfDayFormat BasicFormat))
read :: forall a. (Csv.FromNamedRecord a) => FilePath -> IO [a]
read f = do
s <- B.readFile f
let records = Csv.decodeByName s
case records of (Left s) -> fail s
(Right (header, rows)) -> return $ toList rows
valuesOnly :: [DataPoint a b] -> [b]
valuesOnly = fmap dpV
toPair (DataPoint d v) = (d, v)