-----------------------------------------------------------------------------
-- |
-- Module      :  Data.HodaTime.OffsetDateTime
-- Copyright   :  (C) 2016 Jason Johnson
-- License     :  BSD-style (see the file LICENSE)
-- Maintainer  :  Jason Johnson <jason.johnson.081@gmail.com>
-- Stability   :  experimental
-- Portability :  POSIX, Windows
--
-- An 'OffsetDateTime' is a date and time combined with an offset from UTC time.  'OffsetDateTime' is the form that HTTP uses to deal with dates and times.
----------------------------------------------------------------------------
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.HodaTime.OffsetDateTime
(
  -- * Types
   OffsetDateTime
  -- * Constructors
  ,fromInstantWithOffset
  ,fromCalendarDateTimeWithOffset
  -- * Math
  -- * Conversion
  ,toCalendarDateTime
  ,offset
)
where

import Data.HodaTime.Offset.Internal
import Data.HodaTime.Instant.Internal (Instant)
import Data.HodaTime.CalendarDateTime.Internal (CalendarDateTime, IsCalendarDateTime(..), Date)
import Data.HodaTime.ZonedDateTime.Internal (ZonedDateTime(..))
import Data.HodaTime.TimeZone.Internal (TimeZone(..), TZIdentifier(..), TransitionInfo, tiUtcOffset, fixedOffsetZone)
import Data.Hashable (Hashable(..))

-- | A 'CalendarDateTime' with a UTC offset.  This is the format used by e.g. HTTP.  This type has a fixed 'TimeZone' with the name "UTC(+/-)offset".  If the offset is
-- empty, the name of the 'TimeZone' will be UTC
newtype OffsetDateTime cal = OffsetDateTime (ZonedDateTime cal)

deriving instance Eq (Date cal) => Eq (OffsetDateTime cal)
deriving instance (IsCalendarDateTime cal, Eq (Date cal)) => Ord (OffsetDateTime cal)

-- | Renders an 'OffsetDateTime' as the 'fromInstantWithOffset' call that reconstructs it.
instance IsCalendarDateTime cal => Show (OffsetDateTime cal) where
  showsPrec :: Int -> OffsetDateTime cal -> ShowS
showsPrec Int
p (OffsetDateTime (ZonedDateTime CalendarDateTime cal
cdt TimeZone
_ TransitionInfo
ti)) = Bool -> ShowS -> ShowS
showParen (Int
p Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
10) (ShowS -> ShowS) -> ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$
      String -> ShowS
showString String
"fromInstantWithOffset " ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Instant -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec Int
11 Instant
inst ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> ShowS
showChar Char
' ' ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Offset -> ShowS
forall a. Show a => Int -> a -> ShowS
showsPrec Int
11 Offset
off
    where
      off :: Offset
off = TransitionInfo -> Offset
tiUtcOffset TransitionInfo
ti
      inst :: Instant
inst = Offset -> Instant -> Instant
adjustInstant (Offset -> Offset
negateOffset Offset
off) (CalendarDateTime cal -> Instant
forall cal.
IsCalendarDateTime cal =>
CalendarDateTime cal -> Instant
toUnadjustedInstant CalendarDateTime cal
cdt)
      negateOffset :: Offset -> Offset
negateOffset (Offset Int
s) = Int -> Offset
Offset (Int -> Int
forall a. Num a => a -> a
negate Int
s)

-- NOTE: no 'NFData' instance is provided because 'OffsetDateTime' embeds a 'TimeZone', whose fingertree-based
-- transition maps cannot be forced (see 'Data.HodaTime.TimeZone.Internal').
instance Hashable (Date cal) => Hashable (OffsetDateTime cal) where
  hashWithSalt :: Int -> OffsetDateTime cal -> Int
hashWithSalt Int
s (OffsetDateTime ZonedDateTime cal
z) = Int -> ZonedDateTime cal -> Int
forall a. Hashable a => Int -> a -> Int
hashWithSalt Int
s ZonedDateTime cal
z

-- | Create an 'OffsetDateTime' from an 'Instant' and an 'Offset'.
fromInstantWithOffset :: IsCalendarDateTime cal => Instant -> Offset -> OffsetDateTime cal
fromInstantWithOffset :: forall cal.
IsCalendarDateTime cal =>
Instant -> Offset -> OffsetDateTime cal
fromInstantWithOffset Instant
inst Offset
offset' = ZonedDateTime cal -> OffsetDateTime cal
forall cal. ZonedDateTime cal -> OffsetDateTime cal
OffsetDateTime (ZonedDateTime cal -> OffsetDateTime cal)
-> ZonedDateTime cal -> OffsetDateTime cal
forall a b. (a -> b) -> a -> b
$ CalendarDateTime cal
-> TimeZone -> TransitionInfo -> ZonedDateTime cal
forall cal.
CalendarDateTime cal
-> TimeZone -> TransitionInfo -> ZonedDateTime cal
ZonedDateTime CalendarDateTime cal
cdt TimeZone
tz TransitionInfo
tInfo
  where
    (TimeZone
tz, TransitionInfo
tInfo) = Offset -> (TimeZone, TransitionInfo)
makeFixedTimeZone Offset
offset'
    cdt :: CalendarDateTime cal
cdt = Instant -> CalendarDateTime cal
forall cal.
IsCalendarDateTime cal =>
Instant -> CalendarDateTime cal
fromAdjustedInstant (Instant -> CalendarDateTime cal)
-> (Instant -> Instant) -> Instant -> CalendarDateTime cal
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Offset -> Instant -> Instant
adjustInstant Offset
offset' (Instant -> CalendarDateTime cal)
-> Instant -> CalendarDateTime cal
forall a b. (a -> b) -> a -> b
$ Instant
inst

-- | Create an 'OffsetDateTime' from a 'CalendarDateTime' and an 'Offset'.
fromCalendarDateTimeWithOffset :: CalendarDateTime cal -> Offset -> OffsetDateTime cal
fromCalendarDateTimeWithOffset :: forall cal. CalendarDateTime cal -> Offset -> OffsetDateTime cal
fromCalendarDateTimeWithOffset CalendarDateTime cal
cdt Offset
offset' = ZonedDateTime cal -> OffsetDateTime cal
forall cal. ZonedDateTime cal -> OffsetDateTime cal
OffsetDateTime (ZonedDateTime cal -> OffsetDateTime cal)
-> ZonedDateTime cal -> OffsetDateTime cal
forall a b. (a -> b) -> a -> b
$ CalendarDateTime cal
-> TimeZone -> TransitionInfo -> ZonedDateTime cal
forall cal.
CalendarDateTime cal
-> TimeZone -> TransitionInfo -> ZonedDateTime cal
ZonedDateTime CalendarDateTime cal
cdt TimeZone
tz TransitionInfo
tInfo
  where
    (TimeZone
tz, TransitionInfo
tInfo) = Offset -> (TimeZone, TransitionInfo)
makeFixedTimeZone Offset
offset'

-- | The local (wall-clock) 'CalendarDateTime' of the 'OffsetDateTime', before the offset is applied.
toCalendarDateTime :: OffsetDateTime cal -> CalendarDateTime cal
toCalendarDateTime :: forall cal. OffsetDateTime cal -> CalendarDateTime cal
toCalendarDateTime (OffsetDateTime ZonedDateTime cal
zdt) = ZonedDateTime cal -> CalendarDateTime cal
forall cal. ZonedDateTime cal -> CalendarDateTime cal
zdtCalendarDateTime ZonedDateTime cal
zdt

-- | The UTC 'Offset' of the 'OffsetDateTime'.
offset :: OffsetDateTime cal -> Offset
offset :: forall cal. OffsetDateTime cal -> Offset
offset (OffsetDateTime ZonedDateTime cal
zdt) = TransitionInfo -> Offset
tiUtcOffset (TransitionInfo -> Offset)
-> (ZonedDateTime cal -> TransitionInfo)
-> ZonedDateTime cal
-> Offset
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ZonedDateTime cal -> TransitionInfo
forall cal. ZonedDateTime cal -> TransitionInfo
zdtActiveTransition (ZonedDateTime cal -> Offset) -> ZonedDateTime cal -> Offset
forall a b. (a -> b) -> a -> b
$ ZonedDateTime cal
zdt

-- helper functions

makeFixedTimeZone :: Offset -> (TimeZone, TransitionInfo)
makeFixedTimeZone :: Offset -> (TimeZone, TransitionInfo)
makeFixedTimeZone Offset
offset' = (TZIdentifier
-> UtcTransitionsMap -> CalDateTransitionsMap -> TimeZone
TimeZone (String -> TZIdentifier
Zone String
tzName) UtcTransitionsMap
utcM CalDateTransitionsMap
calDateM, TransitionInfo
tInfo)
  where
    tzName :: String
tzName = Offset -> String
toStringRep Offset
offset'
    (UtcTransitionsMap
utcM, CalDateTransitionsMap
calDateM, TransitionInfo
tInfo) = String
-> Offset
-> (UtcTransitionsMap, CalDateTransitionsMap, TransitionInfo)
fixedOffsetZone String
tzName Offset
offset'