packages feed

ttc-0.1.0.0: examples/Duration.hs

------------------------------------------------------------------------------
-- |
-- Module      : Duration
-- Description : duration data type
-- Copyright   : Copyright (c) 2019 Travis Cardwell
-- License     : MIT
--
-- This module defines a duration data type, with 'TTC.Render' and 'TTC.Parse'
-- instances, as an example of TTC usage.
--
-- The constructor for 'Duration' is not exported.  The 'TTC.Parse' instance
-- serves as a "smart constructor," ensuring that all values are valid.
------------------------------------------------------------------------------

{-# LANGUAGE TemplateHaskell #-}

module Duration
  ( -- * Type
    Duration
    -- * API
  , fromDouble
  , fromFloat
  , fromInt
  , fromMicro
  , fromNominalDiffTime
  , fromReal
  , toDouble
  , toFloat
  , toInt
  , toIntegral
  , toMicro
  , toNominalDiffTime
  , valid
  ) where

-- https://hackage.haskell.org/package/base
import Data.List (dropWhileEnd)
import Text.Read (readMaybe)

-- https://hackage.haskell.org/package/time
import Data.Time.Clock (NominalDiffTime)

-- ttc
import qualified Data.TTC as TTC

------------------------------------------------------------------------------
-- $Type

-- | A 'Duration' is a measure of seconds.  It is fractional and must be zero
-- or greater.
--
-- The underlying type is 'NominalDiffTime', which may be negative.  The
-- constructor is not exported, and all functions that create a value ensure
-- that the constraint holds.
newtype Duration = Duration NominalDiffTime
  deriving Show

instance TTC.Parse Duration where
  parse = TTC.asS $ \s ->
    maybe (Left $ "invalid Duration: " <> s) fromDouble $ readMaybe s

instance TTC.Render Duration where
  render (Duration t) = TTC.fromS . dropWhileEnd (== 's') $ show t

------------------------------------------------------------------------------
-- $API

-- | Construct a 'Duration' from a 'Double' (seconds).
fromDouble :: Double -> Either String Duration
fromDouble = fromReal

-- | Construct a 'Duration' from a 'Float' (seconds).
fromFloat :: Float -> Either String Duration
fromFloat = fromReal

-- | Construct a 'Duration' from an 'Int' (seconds).
fromInt :: Int -> Either String Duration
fromInt = fromReal

-- | Construct a 'Duration' from an 'Int' (microseconds).
fromMicro :: Int -> Either String Duration
fromMicro us = fromDouble $ fromIntegral us / 1000000.0

-- | Construct a 'Duration' from a 'NominalDiffTime'.
fromNominalDiffTime :: NominalDiffTime -> Either String Duration
fromNominalDiffTime t
    | t >= 0.0 = Right $ Duration t
    | otherwise = Left $ "Duration less than zero: " ++ show t

-- | Construct a 'Duration' from a 'Real' (seconds).
fromReal :: (Real a, Show a) => a -> Either String Duration
fromReal x
    | x >= 0 = Right . Duration $ realToFrac x
    | otherwise = Left $ "Duration less than zero: " ++ show x

-- | Convert a 'Duration' to a 'Double' (seconds).
toDouble :: Duration -> Double
toDouble = toFractional

-- | Convert a 'Duration' to a 'Float' (seconds).
toFloat :: Duration -> Float
toFloat = toFractional

-- | Convert a 'Duration' to a 'Fractional' (seconds).
toFractional :: Fractional a => Duration -> a
toFractional (Duration t) = realToFrac t

-- | Convert a 'Duration' to an 'Int' (seconds).
toInt :: Duration -> Int
toInt = toIntegral

-- | Convert a 'Duration' to an 'Integral' (seconds).
toIntegral :: Integral a => Duration -> a
toIntegral = round . toFloat

-- | Convert a 'Duration' to an 'Int' (microseconds).
toMicro :: Duration -> Int
toMicro (Duration t) = round (realToFrac t * 1000000.0 :: Double)

-- | Convert a 'Duration' to a 'NominalDiffTime'.
toNominalDiffTime :: Duration -> NominalDiffTime
toNominalDiffTime (Duration t) = t

-- | The 'valid' function is created using 'TTC.mkValid' via Template Haskell.
-- Note that 'Duration' does not have a 'Language.Haskell.TH.Syntax.Lift'
-- instance.
$(TTC.mkValid "valid" ''Duration)