{-# LANGUAGE FlexibleInstances #-}

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

A local time is a global time together with a timezone.
-}

module Time.LocalTime
  ( -- * Local time
    -- ** Local time type
    LocalTime
    -- ** Local time creation and manipulation
  , localTime
  , localTimeUnwrap
  , localTimeToGlobal
  , localTimeFromGlobal
  , localTimeGetTimezone
  , localTimeSetTimezone
  , localTimeConvert
  ) where

import           Text.Read ( Read (..), readListPrecDefault )
import           Time.Diff ( elapsedTimeAddSecondsP )
import           Time.Time ( Time, Timeable (..), timeConvert )
import           Time.Types ( TimezoneOffset (..), timezoneOffsetToSeconds )

-- | Type representing local times.
data LocalTime t = LocalTime
  { localTimeUnwrap      :: t
    -- ^ The local time.
  , localTimeGetTimezone :: TimezoneOffset
    -- ^ The timezone offset.
  }

-- | Show the 'localTimeUnwrap' field and then the 'localTimeGetTimezone' field.
instance Show t => Show (LocalTime t) where
  show (LocalTime t tz) = show t ++ show tz

-- | Read a 'LocalTime'. Read the 'localTimeUnwrap' field and then the
-- 'localTimeGetTimezone' field.
instance Read t => Read (LocalTime t) where
  readPrec = LocalTime
    <$> readPrec
    <*> readPrec

  readListPrec = readListPrecDefault

instance Eq t => Eq (LocalTime t) where
  LocalTime t1 tz1 == LocalTime t2 tz2 = tz1 == tz2 && t1 == t2

instance (Ord t, Time t) => Ord (LocalTime t) where
  compare l1@(LocalTime g1 tz1) l2@(LocalTime g2 tz2) =
    case compare tz1 tz2 of
      EQ -> compare g1 g2
      _  -> let t1 = localTimeToGlobal l1
                t2 = localTimeToGlobal l2
            in  compare t1 t2

instance Functor LocalTime where
  fmap f (LocalTime t tz) = LocalTime (f t) tz

-- | For the given timezone offset and time value (assumed to be the local
-- time), yield the corresponding local time.
localTime ::
     Time t
  => TimezoneOffset
  -> t
     -- ^ The local time.
  -> LocalTime t
localTime tz t = LocalTime t tz

-- | For the given t'LocalTime' value, yield the corresponding global time.
localTimeToGlobal :: Time t => LocalTime t -> t
localTimeToGlobal (LocalTime local tz)
  | tz == TimezoneOffset 0 = local
  | otherwise =
      timeConvert $ elapsedTimeAddSecondsP (timeGetElapsedP local) tzSecs
 where
  tzSecs = negate $ timezoneOffsetToSeconds tz

-- | For the given time value, yield the corresponding t'LocalTime' value
-- assuming that there is no timezone offset.
localTimeFromGlobal :: Time t => t -> LocalTime t
localTimeFromGlobal = localTime (TimezoneOffset 0)

-- | For the given timezone offset and local time, yield the corresponding local
-- time.
localTimeSetTimezone :: Time t => TimezoneOffset -> LocalTime t -> LocalTime t
localTimeSetTimezone tz currentLocal@(LocalTime t currentTz)
  | diffTz == 0 = currentLocal
  | otherwise   = LocalTime (timeConvert t') tz
 where
  t'        = elapsedTimeAddSecondsP (timeGetElapsedP t) diffTz
  diffTz    = timezoneOffsetToSeconds tz - timezoneOffsetToSeconds currentTz

-- | For the given local time of one type, yield the corresponding local time of
-- a different type. This will not compile unless the compiler can infer the
-- types of the two local times.
localTimeConvert :: (Time t1, Time t2) => LocalTime t1 -> LocalTime t2
localTimeConvert = fmap timeConvert
