-- |
-- Module      : Data.Hourglass.Local
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Local time = global time + timezone
--
{-# LANGUAGE FlexibleInstances #-}
module Data.Hourglass.Local
    (
    -- * Local time
    -- ** Local time type
      LocalTime
    -- ** Local time creation and manipulation
    , localTime
    , localTimeUnwrap
    , localTimeToGlobal
    , localTimeFromGlobal
    , localTimeGetTimezone
    , localTimeSetTimezone
    , localTimeConvert
    ) where

import Data.Hourglass.Types
import Data.Hourglass.Time
import Data.Hourglass.Diff

-- | Local time representation
--
-- this is a time representation augmented by a timezone
-- to get back to a global time, the timezoneOffset needed to be added to the local time.
--
data LocalTime t = LocalTime
    { LocalTime t -> t
localTimeUnwrap      :: t              -- ^ unwrap the LocalTime value. the time value is local.
    , LocalTime t -> TimezoneOffset
localTimeGetTimezone :: TimezoneOffset -- ^ get the timezone associated with LocalTime
    }

-- 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 :: (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

-- | Create a local time type from a timezone and a time type.
--
-- The time value is assumed to be local to the timezone offset set,
-- so no transformation is done.
localTime :: Time t => TimezoneOffset -> t -> LocalTime t
localTime :: TimezoneOffset -> t -> LocalTime t
localTime TimezoneOffset
tz t
t = t -> TimezoneOffset -> LocalTime t
forall t. t -> TimezoneOffset -> LocalTime t
LocalTime t
t TimezoneOffset
tz

-- | Get back a global time value
localTimeToGlobal :: Time t => LocalTime t -> t
localTimeToGlobal :: 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

-- | create a local time value from a global one
localTimeFromGlobal :: Time t => t -> LocalTime t
localTimeFromGlobal :: t -> LocalTime t
localTimeFromGlobal = TimezoneOffset -> t -> LocalTime t
forall t. Time t => TimezoneOffset -> t -> LocalTime t
localTime (Int -> TimezoneOffset
TimezoneOffset Int
0)

-- | Change the timezone, and adjust the local value to represent the new local value.
localTimeSetTimezone :: Time t => TimezoneOffset -> LocalTime t -> LocalTime t
localTimeSetTimezone :: 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

-- | convert the local time representation to another time representation determined by context.
localTimeConvert :: (Time t1, Time t2) => LocalTime t1 -> LocalTime t2
localTimeConvert :: LocalTime t1 -> LocalTime t2
localTimeConvert = (t1 -> t2) -> LocalTime t1 -> LocalTime t2
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