{-# 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           Time.Diff ( elapsedTimeAddSecondsP )
import           Time.Time ( Time, Timeable (..), timeConvert )
import           Time.Types ( TimezoneOffset (..), timezoneOffsetToSeconds )

-- | Type representing local times.

data LocalTime t = LocalTime
  { forall t. LocalTime t -> t
localTimeUnwrap      :: t
    -- ^ The local time.

  , forall t. LocalTime t -> TimezoneOffset
localTimeGetTimezone :: TimezoneOffset
    -- ^ The timezone offset.

  }

-- FIXME add instance Read too.


instance Show t => Show (LocalTime t) where
  show :: LocalTime t -> String
show (LocalTime t
t TimezoneOffset
tz) = t -> String
forall a. Show a => a -> String
show t
t String -> ShowS
forall a. [a] -> [a] -> [a]
++ TimezoneOffset -> String
forall a. Show a => a -> String
show TimezoneOffset
tz

instance Eq t => Eq (LocalTime t) where
  LocalTime t
t1 TimezoneOffset
tz1 == :: LocalTime t -> LocalTime t -> Bool
== LocalTime t
t2 TimezoneOffset
tz2 = TimezoneOffset
tz1 TimezoneOffset -> TimezoneOffset -> Bool
forall a. Eq a => a -> a -> Bool
== TimezoneOffset
tz2 Bool -> Bool -> Bool
&& t
t1 t -> t -> Bool
forall a. Eq a => a -> a -> Bool
== t
t2

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

instance Functor LocalTime where
  fmap :: forall a b. (a -> b) -> LocalTime a -> LocalTime b
fmap a -> b
f (LocalTime a
t TimezoneOffset
tz) = b -> TimezoneOffset -> LocalTime b
forall t. t -> TimezoneOffset -> LocalTime t
LocalTime (a -> b
f a
t) TimezoneOffset
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 :: forall t. Time t => TimezoneOffset -> t -> LocalTime t
localTime TimezoneOffset
tz t
t = t -> TimezoneOffset -> LocalTime t
forall t. t -> TimezoneOffset -> LocalTime t
LocalTime t
t TimezoneOffset
tz

-- | For the given t'LocalTime' value, yield the corresponding global time.

localTimeToGlobal :: Time t => LocalTime t -> t
localTimeToGlobal :: forall t. Time t => LocalTime t -> t
localTimeToGlobal (LocalTime t
local TimezoneOffset
tz)
  | TimezoneOffset
tz TimezoneOffset -> TimezoneOffset -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> TimezoneOffset
TimezoneOffset Int
0 = t
local
  | Bool
otherwise =
      ElapsedP -> t
forall t1 t2. (Timeable t1, Time t2) => t1 -> t2
timeConvert (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
local) Seconds
tzSecs
 where
  tzSecs :: Seconds
tzSecs = Seconds -> Seconds
forall a. Num a => a -> a
negate (Seconds -> Seconds) -> Seconds -> Seconds
forall a b. (a -> b) -> a -> b
$ TimezoneOffset -> Seconds
timezoneOffsetToSeconds TimezoneOffset
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 :: forall t. Time t => t -> LocalTime t
localTimeFromGlobal = TimezoneOffset -> t -> LocalTime t
forall t. Time t => TimezoneOffset -> t -> LocalTime t
localTime (Int -> TimezoneOffset
TimezoneOffset Int
0)

-- | For the given timezone offset and local time, yield the corresponding local

-- time.

localTimeSetTimezone :: Time t => TimezoneOffset -> LocalTime t -> LocalTime t
localTimeSetTimezone :: forall t. Time t => TimezoneOffset -> LocalTime t -> LocalTime t
localTimeSetTimezone TimezoneOffset
tz currentLocal :: LocalTime t
currentLocal@(LocalTime t
t TimezoneOffset
currentTz)
  | Seconds
diffTz Seconds -> Seconds -> Bool
forall a. Eq a => a -> a -> Bool
== Seconds
0 = LocalTime t
currentLocal
  | Bool
otherwise   = t -> TimezoneOffset -> LocalTime t
forall t. t -> TimezoneOffset -> LocalTime t
LocalTime (ElapsedP -> t
forall t1 t2. (Timeable t1, Time t2) => t1 -> t2
timeConvert ElapsedP
t') TimezoneOffset
tz
 where
  t' :: ElapsedP
t'        = ElapsedP -> Seconds -> ElapsedP
elapsedTimeAddSecondsP (t -> ElapsedP
forall t. Timeable t => t -> ElapsedP
timeGetElapsedP t
t) Seconds
diffTz
  diffTz :: Seconds
diffTz    = TimezoneOffset -> Seconds
timezoneOffsetToSeconds TimezoneOffset
tz Seconds -> Seconds -> Seconds
forall a. Num a => a -> a -> a
- TimezoneOffset -> Seconds
timezoneOffsetToSeconds TimezoneOffset
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 :: forall t1 t2. (Time t1, Time t2) => LocalTime t1 -> LocalTime t2
localTimeConvert = (t1 -> t2) -> LocalTime t1 -> LocalTime t2
forall a b. (a -> b) -> LocalTime a -> LocalTime b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap t1 -> t2
forall t1 t2. (Timeable t1, Time t2) => t1 -> t2
timeConvert