{-# LANGUAGE ExistentialQuantification #-}

{- |
Module      : Time.Time
License     : BSD-style
Copyright   : (c) 2014 Vincent Hanquez <vincent@snarc.org>
Stability   : experimental
Portability : unknown

Types representing points in time, and associated type classes, and durations
of time, and associated functions.
-}

module Time.Time
  ( -- * Classes for conversion

    Time (..)
  , Timeable (..)
    -- * Conversion

  , timeConvert
    -- * Date and time

  , timeGetDate
  , timeGetDateTimeOfDay
  , timeGetTimeOfDay
    -- * Arithmetic

  , Duration (..)
  , Period (..)
  , timeAdd
  , timeDiff
  , timeDiffP
  , dateAddPeriod
  ) where

import           Foreign.C.Types ( CTime (..) )
import           Time.Calendar ( dateTimeToUnixEpoch )
import           Time.Diff
                   ( Duration (..), Period (..), dateAddPeriod
                   , elapsedTimeAddSecondsP
                   )
import           Time.Internal ( dateTimeFromUnixEpoch, dateTimeFromUnixEpochP )
import           Time.Types
                   ( Date, DateTime (..), Elapsed (..), ElapsedP (..)
                   , NanoSeconds (..), Seconds (..), TimeInterval (..)
                   , TimeOfDay (..)
                   )

-- | A type class promising functionality for:

--

-- * converting a value of the type in question to a t'Elapsed' value or

--   a t'ElapsedP' value; and

--

-- * yielding separately a nanoseconds component of the value of the type in

--   question (should yield @0@ when the type is less precise than seconds).

--

class Timeable t where
  -- | Convert the given value to the number of elapsed seconds and nanoseconds

  -- since the start of the Unix epoch (1970-01-01 00:00:00 UTC).

  timeGetElapsedP :: t -> ElapsedP

  -- | Convert the given value to the number of elapsed seconds since the start

  -- of the Unix epoch (1970-01-01 00:00:00 UTC).

  --

  -- Defaults to 'timeGetElapsedP'.

  timeGetElapsed :: t -> Elapsed
  timeGetElapsed t
t = Elapsed
e
   where
    ElapsedP Elapsed
e NanoSeconds
_ = t -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t
t

  -- | Optionally, for the given value, yield the number of nanoseconds

  -- component.

  --

  -- If the type in question does not provide sub-second precision, should yield

  -- @0@.

  --

  -- Defaults to 'timeGetElapsedP'. For efficiency, if the type in question does

  -- not provide sub-second precision, it is a good idea to override this

  -- method.

  timeGetNanoSeconds :: t -> NanoSeconds
  timeGetNanoSeconds t
t = NanoSeconds
ns where ElapsedP Elapsed
_ NanoSeconds
ns = t -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t
t

-- | A type class promising functionality for converting t'ElapsedP' values

-- and t'Elapsed' values to values of the type in question.

class Timeable t => Time t where
  -- | Convert from a number of elapsed seconds and nanoseconds since the start

  -- of the Unix epoch (1970-01-01 00:00:00 UTC).

  timeFromElapsedP :: ElapsedP -> t

  -- | Convert from a number of elapsed seconds since the start of the Unix

  -- epoch (1970-01-01 00:00:00 UTC).

  --

  -- Defaults to 'timeFromElapsedP'.

  timeFromElapsed :: Elapsed -> t
  timeFromElapsed Elapsed
e = ElapsedP -> t
forall t. Time t => ElapsedP -> t
timeFromElapsedP (Elapsed -> NanoSeconds -> ElapsedP
ElapsedP Elapsed
e NanoSeconds
0)

instance Timeable CTime where
  timeGetElapsedP :: CTime -> ElapsedP
timeGetElapsedP CTime
c         = Elapsed -> NanoSeconds -> ElapsedP
ElapsedP (CTime -> Elapsed
forall t. Timeable t => t -> Elapsed
timeGetElapsed CTime
c) NanoSeconds
0

  timeGetElapsed :: CTime -> Elapsed
timeGetElapsed  (CTime Int64
c) = Seconds -> Elapsed
Elapsed (Int64 -> Seconds
Seconds (Int64 -> Seconds) -> Int64 -> Seconds
forall a b. (a -> b) -> a -> b
$ Int64 -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
c)

  timeGetNanoSeconds :: CTime -> NanoSeconds
timeGetNanoSeconds CTime
_ = NanoSeconds
0

instance Time CTime where
  timeFromElapsedP :: ElapsedP -> CTime
timeFromElapsedP (ElapsedP Elapsed
e NanoSeconds
_)       = Elapsed -> CTime
forall t. Time t => Elapsed -> t
timeFromElapsed Elapsed
e

  timeFromElapsed :: Elapsed -> CTime
timeFromElapsed (Elapsed (Seconds Int64
c)) = Int64 -> CTime
CTime (Int64 -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
c)

instance Timeable Elapsed where
  timeGetElapsedP :: Elapsed -> ElapsedP
timeGetElapsedP  Elapsed
e = Elapsed -> NanoSeconds -> ElapsedP
ElapsedP Elapsed
e NanoSeconds
0

  timeGetElapsed :: Elapsed -> Elapsed
timeGetElapsed   Elapsed
e = Elapsed
e

  timeGetNanoSeconds :: Elapsed -> NanoSeconds
timeGetNanoSeconds Elapsed
_ = NanoSeconds
0

instance Time Elapsed where
  timeFromElapsedP :: ElapsedP -> Elapsed
timeFromElapsedP (ElapsedP Elapsed
e NanoSeconds
_) = Elapsed
e

  timeFromElapsed :: Elapsed -> Elapsed
timeFromElapsed  Elapsed
e = Elapsed
e

instance Timeable ElapsedP where
  timeGetElapsedP :: ElapsedP -> ElapsedP
timeGetElapsedP    ElapsedP
e               = ElapsedP
e
  timeGetNanoSeconds :: ElapsedP -> NanoSeconds
timeGetNanoSeconds (ElapsedP Elapsed
_ NanoSeconds
ns) = NanoSeconds
ns

instance Time ElapsedP where
  timeFromElapsedP :: ElapsedP -> ElapsedP
timeFromElapsedP   ElapsedP
e               = ElapsedP
e

instance Timeable Date where
  timeGetElapsedP :: Date -> ElapsedP
timeGetElapsedP Date
d  = DateTime -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP (Date -> TimeOfDay -> DateTime
DateTime Date
d (Hours -> Minutes -> Seconds -> NanoSeconds -> TimeOfDay
TimeOfDay Hours
0 Minutes
0 Seconds
0 NanoSeconds
0))

instance Time Date where
  timeFromElapsedP :: ElapsedP -> Date
timeFromElapsedP (ElapsedP Elapsed
elapsed NanoSeconds
_) = Date
d
   where
    (DateTime Date
d TimeOfDay
_) = Elapsed -> DateTime
dateTimeFromUnixEpoch Elapsed
elapsed

instance Timeable DateTime where
  timeGetElapsedP :: DateTime -> ElapsedP
timeGetElapsedP DateTime
d = Elapsed -> NanoSeconds -> ElapsedP
ElapsedP (DateTime -> Elapsed
dateTimeToUnixEpoch DateTime
d) (DateTime -> NanoSeconds
forall t. Timeable t => t -> NanoSeconds
timeGetNanoSeconds DateTime
d)
  timeGetElapsed :: DateTime -> Elapsed
timeGetElapsed = DateTime -> Elapsed
dateTimeToUnixEpoch
  timeGetNanoSeconds :: DateTime -> NanoSeconds
timeGetNanoSeconds (DateTime Date
_ (TimeOfDay Hours
_ Minutes
_ Seconds
_ NanoSeconds
ns)) = NanoSeconds
ns

instance Time DateTime where
  timeFromElapsedP :: ElapsedP -> DateTime
timeFromElapsedP = ElapsedP -> DateTime
dateTimeFromUnixEpochP

-- | Convert from one time representation to another. This will not compile

-- unless the compiler can infer the types.

--

-- Specialized functions are available for built-in types:

--

-- * 'timeGetDate'

--

-- * 'timeGetDateTimeOfDay'

--

-- * 'timeGetElapsed'

--

-- * 'timeGetElapsedP'

timeConvert :: (Timeable t1, Time t2) => t1 -> t2
timeConvert :: forall t1 t2. (Timeable t1, Time t2) => t1 -> t2
timeConvert t1
t1 = ElapsedP -> t2
forall t. Time t => ElapsedP -> t
timeFromElapsedP (t1 -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t1
t1)
{-# INLINE[2] timeConvert #-}
{-# RULES "timeConvert/ID" timeConvert = id #-}
{-# RULES "timeConvert/ElapsedP" timeConvert = timeGetElapsedP #-}
{-# RULES "timeConvert/Elapsed" timeConvert = timeGetElapsed #-}

-- | For the given value of a point in time, yield the corresponding date (a

-- specialization of 'timeConvert').

timeGetDate :: Timeable t => t -> Date
timeGetDate :: forall t. Timeable t => t -> Date
timeGetDate t
t = Date
d where (DateTime Date
d TimeOfDay
_) = t -> DateTime
forall t. Timeable t => t -> DateTime
timeGetDateTimeOfDay t
t
{-# INLINE[2] timeGetDate #-}
{-# RULES "timeGetDate/ID" timeGetDate = id #-}
{-# RULES "timeGetDate/DateTime" timeGetDate = dtDate #-}

-- | For the given value for a point in time, yield the corresponding time

-- (a specialization of 'timeConvert').

timeGetTimeOfDay :: Timeable t => t -> TimeOfDay
timeGetTimeOfDay :: forall t. Timeable t => t -> TimeOfDay
timeGetTimeOfDay t
t = TimeOfDay
tod where (DateTime Date
_ TimeOfDay
tod) = t -> DateTime
forall t. Timeable t => t -> DateTime
timeGetDateTimeOfDay t
t
{-# INLINE[2] timeGetTimeOfDay #-}
{-# RULES "timeGetTimeOfDay/Date" timeGetTimeOfDay = const (TimeOfDay 0 0 0 0) #-}
{-# RULES "timeGetTimeOfDay/DateTime" timeGetTimeOfDay = dtTime #-}

-- | For the given value for a point in time, yield the corresponding date and

-- time (a specialization of 'timeConvert').

timeGetDateTimeOfDay :: Timeable t => t -> DateTime
timeGetDateTimeOfDay :: forall t. Timeable t => t -> DateTime
timeGetDateTimeOfDay t
t = ElapsedP -> DateTime
dateTimeFromUnixEpochP (ElapsedP -> DateTime) -> ElapsedP -> DateTime
forall a b. (a -> b) -> a -> b
$ t -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t
t
{-# INLINE[2] timeGetDateTimeOfDay #-}
{-# RULES "timeGetDateTimeOfDay/ID" timeGetDateTimeOfDay = id #-}
{-# RULES "timeGetDateTimeOfDay/Date" timeGetDateTimeOfDay = flip DateTime (TimeOfDay 0 0 0 0) #-}

-- | Add the given period of time to the given value for a point time.

--

-- Example:

--

-- > t1 `timeAdd` mempty { durationHours = 12 }

timeAdd :: (Time t, TimeInterval ti) => t -> ti -> t
timeAdd :: forall t ti. (Time t, TimeInterval ti) => t -> ti -> t
timeAdd t
t ti
ti =
  ElapsedP -> t
forall t. Time t => ElapsedP -> t
timeFromElapsedP (ElapsedP -> t) -> ElapsedP -> t
forall a b. (a -> b) -> a -> b
$ ElapsedP -> Seconds -> ElapsedP
elapsedTimeAddSecondsP (t -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t
t) (ti -> Seconds
forall i. TimeInterval i => i -> Seconds
toSeconds ti
ti)

-- | For the two given points in time, yields the difference in seconds

-- between them.

--

-- Effectively:

--

-- > t2 `timeDiff` t1 = t2 - t1

timeDiff :: (Timeable t1, Timeable t2) => t1 -> t2 -> Seconds
timeDiff :: forall t1 t2. (Timeable t1, Timeable t2) => t1 -> t2 -> Seconds
timeDiff t1
t1 t2
t2 = Seconds
sec where (Elapsed Seconds
sec) = t1 -> Elapsed
forall t. Timeable t => t -> Elapsed
timeGetElapsed t1
t1 Elapsed -> Elapsed -> Elapsed
forall a. Num a => a -> a -> a
- t2 -> Elapsed
forall t. Timeable t => t -> Elapsed
timeGetElapsed t2
t2

-- | For the two given points in time, yields the difference in seconds and

-- nanoseconds between them.

--

-- Effectively:

--

-- > @t2 `timeDiffP` t1 = t2 - t1

timeDiffP :: (Timeable t1, Timeable t2) => t1 -> t2 -> (Seconds, NanoSeconds)
timeDiffP :: forall t1 t2.
(Timeable t1, Timeable t2) =>
t1 -> t2 -> (Seconds, NanoSeconds)
timeDiffP t1
t1 t2
t2 = (Seconds
sec, NanoSeconds
ns)
 where
  (ElapsedP (Elapsed Seconds
sec) NanoSeconds
ns) = t1 -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t1
t1 ElapsedP -> ElapsedP -> ElapsedP
forall a. Num a => a -> a -> a
- t2 -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t2
t2