{-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE RankNTypes #-} -- | Define the 'TimeIn' type that lets us specify in the type how a -- 'LocalTime' should be converted to a 'UTCTime'. module TimeIn where import Control.Monad (MonadPlus (mzero), msum) import qualified Data.Text as T import Data.Time.Clock import Data.Time.Format import Data.Time.LocalTime import Data.Time.Zones import Data.Time.Zones.TH import Frames.ColumnTypeable (Parseable(..), Parsed(..)) import GHC.TypeLits import Language.Haskell.TH -- | A 'UTCTime' tagged with a symbol denoting the 'TZ' time zone from -- whence it came. newtype TimeIn (zone :: Symbol) = TimeIn UTCTime deriving Show failZero :: MonadPlus m => Maybe r -> m r failZero = maybe mzero pure -- | Try to parse a 'LocalTime' value using common formats. parseLocalTime :: MonadPlus m => T.Text -> m LocalTime parseLocalTime t = msum (map (($ T.unpack t) . mkParser) formats) where formats = ["%F %T", "%F"] mkParser = (failZero .) . parseTimeM True defaultTimeLocale -- | @zonedTime "America/Chicago"@ will create a 'Parseable' instance -- for the type @TimeIn "America/Chicago"@. You can then use this type -- when loading data. timeIn :: String -> DecsQ timeIn tzStr = do let fromLocal = [e| localTimeToUTCTZ $(includeTZFromDB tzStr) |] ex = [e| fmap (Definitely . TimeIn . $fromLocal) . parseLocalTime |] sequenceA [ instanceD (pure []) [t|Parseable (TimeIn $(pure $ LitT (StrTyLit tzStr)))|] [ funD (mkName "parse") [clause [] (normalB ex) []] ] ]