{- This file is part of time-interval.
 -
 - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
 -
 - ♡ Copying is an act of love. Please copy, reuse and share.
 -
 - The author(s) have dedicated all copyright and related and neighboring
 - rights to this software to the public domain worldwide. This software is
 - distributed without any warranty.
 -
 - You should have received a copy of the CC0 Public Domain Dedication along
 - with this software. If not, see
 - <http://creativecommons.org/publicdomain/zero/1.0/>.
 -}

-- | Suppose you have a program which periodically reloads state and saves some
-- logs, and you'd like the intervals for these periodic actions to be
-- expressed using time units, and abstract away the internal representation
-- until the site of actual use. Your code may look like this:
--
-- > data AppState tr ts = AppState
-- >     { userName       :: String
-- >     , newMessages    :: [String]
-- >     , reloadInterval :: tr
-- >     , saveInterval   :: ts
-- >     }
-- >
-- > type App tr ts = StateT (AppState tr ts) IO
-- >
-- > setReloadInterval :: TimeUnit t => t -> App ()
-- > setReloadInterval new = modify $ \ s -> s { reloadInterval = new }
-- >
-- > syncStateWithFiles :: (TimeUnit tr, TimeUnit ts) => App tr ts ()
-- > syncStateWithFiles = do
-- >     reloadMicrosec <- liftM toMicroseconds $ gets reloadInterval
-- >     saveMicrosec <- liftM toMicroseconds $ gets saveInterval
-- >     {- ... use the values ... -}
--
-- And usage looks like this:
--
-- > setReloadInterval (5 :: Second)
--
-- So every time you add, change or remove a time field, all your type
-- signatures need to be updated and all your @App@ actions, even unrelated to
-- those times, are stuck with time type parameters in their type signatures.
--
-- An easy and common approach to avoid all the mess is to store the time as an
-- integer, i.e. applying 'toMicroseconds' when setting the value rather than
-- when using it. That makes things much easier, but then your type (and your
-- API) expose the time directly as a number of microseconds, and people end up
-- writing things like @1000 * 1000 * 60 * 5@ to say "5 minuts". It's also an
-- internal technical detail there's no reason to expose, and should be
-- possible to change without breaking anything - e.g. what if your scheduling
-- tool one day moves to a higher precision than microseconds? Your high-level
-- API would ideally not let that change float all the way up.
--
-- Here's how things can work when using this library.
--
-- > data AppState = AppState
-- >     { userName       :: String
-- >     , newMessages    :: [String]
-- >     , reloadInterval :: TimeInterval
-- >     , saveInterval   :: TimeInterval
-- >     }
-- >
-- > type App = StateT AppState IO
-- >
-- > setReloadInterval :: TimeUnit t => t -> App ()
-- > setReloadInterval new = modify $ \ s -> s { reloadInterval = time new }
-- >
-- > syncStateWithFiles :: App ()
-- > syncStateWithFiles = do
-- >     reloadMicrosec <- liftM microseconds $ gets reloadInterval
-- >     saveMicrosec <- liftM microseconds $ gets saveInterval
-- >     {- ... use the values ... -}
--
-- And usage looks the same:
--
-- > setReloadInterval (5 :: Second)
--
-- Also, even if you let the user use the 'time' function in their code, e.g.
-- like this:
--
-- > setReloadInterval $ time (5 :: Second)
--
-- ... you stil get the advantages from both worlds.
module Data.Time.Interval
    ( TimeInterval ()
    , time
    , microseconds
    )
where

import Data.Time.Units (TimeUnit (..))

-- | A time duration.
newtype TimeInterval = TimeInterval Integer deriving (Eq, Ord, Show)

-- | Convert a time value expressed in a some time unit into a 'TimeInterval'.
time :: TimeUnit t => t -> TimeInterval
time = TimeInterval . toMicroseconds

-- | Express a 'TimeInterval' in microseconds.
microseconds :: TimeInterval -> Integer
microseconds (TimeInterval i) = i