-- | Provides a dynamic effect for "Data.Time".
--
-- @since 0.1
module Effectful.Time.Dynamic
  ( -- * Effect
    Time (..),
    getSystemTime,
    getSystemZonedTime,
    getTimeZone,
    utcToLocalZonedTime,
    loadLocalTZ,
    getMonotonicTime,

    -- ** Handlers
    runTime,

    -- * Timing
    withTiming,
    withTiming_,

    -- ** TimeSpec
    TimeSpec (..),

    -- *** Creation
    TimeSpec.fromSeconds,
    TimeSpec.fromNanoSeconds,

    -- *** Elimination
    TimeSpec.toSeconds,
    TimeSpec.toNanoSeconds,

    -- *** Operations
    TimeSpec.diffTimeSpec,
    TimeSpec.normalizeTimeSpec,

    -- * Formatting
    Utils.formatLocalTime,
    Utils.formatZonedTime,

    -- * Parsing
    Utils.parseLocalTime,
    Utils.parseZonedTime,

    -- * Misc
    getSystemTimeString,
    getSystemZonedTimeString,

    -- * Re-exports
    LocalTime (..),
    ZonedTime (..),
  )
where

import Data.Time.Clock (UTCTime)
import Data.Time.LocalTime
  ( LocalTime (LocalTime, localDay, localTimeOfDay),
    TimeZone,
    ZonedTime (ZonedTime, zonedTimeToLocalTime, zonedTimeZone),
  )
import Data.Time.LocalTime qualified as Local
import Data.Time.Zones (TZ)
import Effectful
  ( Dispatch (Dynamic),
    DispatchOf,
    Eff,
    Effect,
    IOE,
    type (:>),
  )
import Effectful.Dispatch.Dynamic (HasCallStack, reinterpret_, send)
import Effectful.Dynamic.Utils (ShowEffect (showEffectCons))
import Effectful.Time.Static qualified as Static
import Effectful.Time.TimeSpec (TimeSpec (nsec, sec))
import Effectful.Time.TimeSpec qualified as TimeSpec
import Effectful.Time.Utils qualified as Utils

-- | Dynamic effect for "Data.Time".
--
-- @since 0.1
data Time :: Effect where
  GetSystemZonedTime :: Time m ZonedTime
  GetTimeZone :: UTCTime -> Time m TimeZone
  UtcToLocalZonedTime :: UTCTime -> Time m ZonedTime
  LoadLocalTZ :: Time m TZ
  GetMonotonicTime :: Time m Double

-- | @since 0.1
type instance DispatchOf Time = Dynamic

-- | @since 0.1
instance ShowEffect Time where
  showEffectCons :: forall (m :: * -> *) a. Time m a -> String
showEffectCons = \case
    Time m a
GetSystemZonedTime -> String
"GetSystemZonedTime"
    GetTimeZone UTCTime
_ -> String
"GetTimeZone"
    UtcToLocalZonedTime UTCTime
_ -> String
"UtcToLocalZonedTime"
    Time m a
LoadLocalTZ -> String
"LoadLocalTZ"
    Time m a
GetMonotonicTime -> String
"GetMonotonicTime"

-- | Runs 'Time' in 'IO'.
--
-- @since 0.1
runTime :: (HasCallStack, IOE :> es) => Eff (Time : es) a -> Eff es a
runTime :: forall (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, IOE :> es) =>
Eff (Time : es) a -> Eff es a
runTime = (Eff (Time : es) a -> Eff es a)
-> EffectHandler_ Time (Time : es) -> Eff (Time : es) a -> Eff es a
forall (e :: (* -> *) -> * -> *)
       (handlerEs :: [(* -> *) -> * -> *]) a (es :: [(* -> *) -> * -> *])
       b.
(HasCallStack, DispatchOf e ~ 'Dynamic) =>
(Eff handlerEs a -> Eff es b)
-> EffectHandler_ e handlerEs -> Eff (e : es) a -> Eff es b
reinterpret_ Eff (Time : es) a -> Eff es a
forall (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, IOE :> es) =>
Eff (Time : es) a -> Eff es a
Static.runTime (EffectHandler_ Time (Time : es) -> Eff (Time : es) a -> Eff es a)
-> EffectHandler_ Time (Time : es) -> Eff (Time : es) a -> Eff es a
forall a b. (a -> b) -> a -> b
$ \case
  Time (Eff localEs) a
GetSystemZonedTime -> Eff (Time : es) a
Eff (Time : es) ZonedTime
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es ZonedTime
Static.getSystemZonedTime
  GetTimeZone UTCTime
utc -> UTCTime -> Eff (Time : es) TimeZone
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
UTCTime -> Eff es TimeZone
Static.getTimeZone UTCTime
utc
  UtcToLocalZonedTime UTCTime
utc -> UTCTime -> Eff (Time : es) ZonedTime
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
UTCTime -> Eff es ZonedTime
Static.utcToLocalZonedTime UTCTime
utc
  Time (Eff localEs) a
LoadLocalTZ -> Eff (Time : es) a
Eff (Time : es) TZ
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es TZ
Static.loadLocalTZ
  Time (Eff localEs) a
GetMonotonicTime -> Eff (Time : es) a
Eff (Time : es) Double
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es Double
Static.getMonotonicTime

-- | Returns the local system time.
--
-- @since 0.1
getSystemTime :: (HasCallStack, Time :> es) => Eff es LocalTime
getSystemTime :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es LocalTime
getSystemTime = ZonedTime -> LocalTime
Local.zonedTimeToLocalTime (ZonedTime -> LocalTime) -> Eff es ZonedTime -> Eff es LocalTime
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Eff es ZonedTime
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es ZonedTime
getSystemZonedTime

-- | Returns the zoned system time.
--
-- @since 0.1
getSystemZonedTime :: (HasCallStack, Time :> es) => Eff es ZonedTime
getSystemZonedTime :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es ZonedTime
getSystemZonedTime = Time (Eff es) ZonedTime -> Eff es ZonedTime
forall (e :: (* -> *) -> * -> *) (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, DispatchOf e ~ 'Dynamic, e :> es) =>
e (Eff es) a -> Eff es a
send Time (Eff es) ZonedTime
forall (m :: * -> *). Time m ZonedTime
GetSystemZonedTime

-- | Lifted 'Local.getTimeZone'.
--
-- @since 0.1
getTimeZone :: (HasCallStack, Time :> es) => UTCTime -> Eff es TimeZone
getTimeZone :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
UTCTime -> Eff es TimeZone
getTimeZone = Time (Eff es) TimeZone -> Eff es TimeZone
forall (e :: (* -> *) -> * -> *) (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, DispatchOf e ~ 'Dynamic, e :> es) =>
e (Eff es) a -> Eff es a
send (Time (Eff es) TimeZone -> Eff es TimeZone)
-> (UTCTime -> Time (Eff es) TimeZone)
-> UTCTime
-> Eff es TimeZone
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> Time (Eff es) TimeZone
forall (m :: * -> *). UTCTime -> Time m TimeZone
GetTimeZone

-- | Lifted 'Local.utcToLocalZonedTime'.
--
-- @since 0.1
utcToLocalZonedTime :: (HasCallStack, Time :> es) => UTCTime -> Eff es ZonedTime
utcToLocalZonedTime :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
UTCTime -> Eff es ZonedTime
utcToLocalZonedTime = Time (Eff es) ZonedTime -> Eff es ZonedTime
forall (e :: (* -> *) -> * -> *) (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, DispatchOf e ~ 'Dynamic, e :> es) =>
e (Eff es) a -> Eff es a
send (Time (Eff es) ZonedTime -> Eff es ZonedTime)
-> (UTCTime -> Time (Eff es) ZonedTime)
-> UTCTime
-> Eff es ZonedTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> Time (Eff es) ZonedTime
forall (m :: * -> *). UTCTime -> Time m ZonedTime
UtcToLocalZonedTime

-- | Lifted 'TZ.loadLocalTZ'.
--
-- @since 0.1
loadLocalTZ :: (HasCallStack, Time :> es) => Eff es TZ
loadLocalTZ :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es TZ
loadLocalTZ = Time (Eff es) TZ -> Eff es TZ
forall (e :: (* -> *) -> * -> *) (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, DispatchOf e ~ 'Dynamic, e :> es) =>
e (Eff es) a -> Eff es a
send Time (Eff es) TZ
forall (m :: * -> *). Time m TZ
LoadLocalTZ

-- | Returns the zoned system time
--
-- @since 0.1
getMonotonicTime :: (HasCallStack, Time :> es) => Eff es Double
getMonotonicTime :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es Double
getMonotonicTime = Time (Eff es) Double -> Eff es Double
forall (e :: (* -> *) -> * -> *) (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, DispatchOf e ~ 'Dynamic, e :> es) =>
e (Eff es) a -> Eff es a
send Time (Eff es) Double
forall (m :: * -> *). Time m Double
GetMonotonicTime

-- | Runs an action, returning the elapsed time.
--
-- @since 0.1
withTiming :: (HasCallStack, Time :> es) => Eff es a -> Eff es (TimeSpec, a)
withTiming :: forall (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, Time :> es) =>
Eff es a -> Eff es (TimeSpec, a)
withTiming Eff es a
m = do
  start <- Eff es Double
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es Double
getMonotonicTime
  res <- m
  end <- getMonotonicTime
  pure (TimeSpec.fromSeconds (end - start), res)

-- | 'withTiming' but ignores the result value.
--
-- @since 0.1
withTiming_ :: (HasCallStack, Time :> es) => Eff es a -> Eff es TimeSpec
withTiming_ :: forall (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, Time :> es) =>
Eff es a -> Eff es TimeSpec
withTiming_ = ((TimeSpec, a) -> TimeSpec)
-> Eff es (TimeSpec, a) -> Eff es TimeSpec
forall a b. (a -> b) -> Eff es a -> Eff es b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (TimeSpec, a) -> TimeSpec
forall a b. (a, b) -> a
fst (Eff es (TimeSpec, a) -> Eff es TimeSpec)
-> (Eff es a -> Eff es (TimeSpec, a))
-> Eff es a
-> Eff es TimeSpec
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Eff es a -> Eff es (TimeSpec, a)
forall (es :: [(* -> *) -> * -> *]) a.
(HasCallStack, Time :> es) =>
Eff es a -> Eff es (TimeSpec, a)
withTiming

-- | Retrieves the formatted 'Data.Time.LocalTime'.
--
-- @since 0.1
getSystemTimeString :: (HasCallStack, Time :> es) => Eff es String
getSystemTimeString :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es String
getSystemTimeString = (LocalTime -> String) -> Eff es LocalTime -> Eff es String
forall a b. (a -> b) -> Eff es a -> Eff es b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap LocalTime -> String
Utils.formatLocalTime Eff es LocalTime
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es LocalTime
getSystemTime

-- | Retrieves the formatted 'Data.Time.ZonedTime'.
--
-- @since 0.1
getSystemZonedTimeString :: (HasCallStack, Time :> es) => Eff es String
getSystemZonedTimeString :: forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es String
getSystemZonedTimeString = (ZonedTime -> String) -> Eff es ZonedTime -> Eff es String
forall a b. (a -> b) -> Eff es a -> Eff es b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ZonedTime -> String
Utils.formatZonedTime Eff es ZonedTime
forall (es :: [(* -> *) -> * -> *]).
(HasCallStack, Time :> es) =>
Eff es ZonedTime
getSystemZonedTime