{-# LANGUAGE CPP, DeriveDataTypeable #-}
module Data.Time.LocalTime.Compat (
    -- * Time zones
    TimeZone(..),timeZoneOffsetString,timeZoneOffsetString',minutesToTimeZone,hoursToTimeZone,utc,

    -- getting the locale time zone
    getTimeZone,getCurrentTimeZone,

    -- * Time of day
    TimeOfDay(..),midnight,midday,makeTimeOfDayValid,
    timeToDaysAndTimeOfDay,daysAndTimeOfDayToTime,
    utcToLocalTimeOfDay,localToUTCTimeOfDay,
    timeToTimeOfDay,timeOfDayToTime,
    dayFractionToTimeOfDay,timeOfDayToDayFraction,
    pastMidnight, sinceMidnight,

    -- * CalendarDiffTime
    CalendarDiffTime (..),
    calendarTimeDays, calendarTimeTime, scaleCalendarDiffTime,

    -- * Local Time
    LocalTime(..),

    addLocalTime,diffLocalTime,

    -- converting UTC and UT1 times to LocalTime
    utcToLocalTime,localTimeToUTC,ut1ToLocalTime,localTimeToUT1,

    -- * Zoned Time
    ZonedTime(..),utcToZonedTime,zonedTimeToUTC,getZonedTime,utcToLocalZonedTime,
    ) where

import Data.Time.Orphans ()

import Data.Time.LocalTime
import Data.Time.Clock.Compat
import Data.Time.Calendar.Compat

import Data.Fixed (Pico (..), showFixed, divMod')
import Data.Monoid (Monoid (..))
import Data.Data (Data, Typeable)
import Data.Semigroup (Semigroup (..))

-------------------------------------------------------------------------------
-- TimeOfDay
-------------------------------------------------------------------------------

#if !MIN_VERSION_time(1,9,0)

-- | Convert a period of time into a count of days and a time of day since midnight.
-- The time of day will never have a leap second.
timeToDaysAndTimeOfDay :: NominalDiffTime -> (Integer,TimeOfDay)
timeToDaysAndTimeOfDay dt = let
    s = realToFrac dt
    (m,ms) = divMod' s 60
    (h,hm) = divMod' m 60
    (d,dh) = divMod' h 24
    in (d,TimeOfDay dh hm ms)

-- | Convert a count of days and a time of day since midnight into a period of time.
daysAndTimeOfDayToTime :: Integer -> TimeOfDay -> NominalDiffTime
daysAndTimeOfDayToTime d (TimeOfDay dh hm ms) = (+) (realToFrac ms) $ (*) 60 $ (+) (realToFrac hm) $ (*) 60 $ (+) (realToFrac dh) $ (*) 24 $ realToFrac d

#endif

#if !MIN_VERSION_time(1,10,0)
-- | Same as 'timeToTimeOfDay'.
pastMidnight :: DiffTime -> TimeOfDay
pastMidnight = timeToTimeOfDay

-- | Same as 'timeOfDayToTime'.
sinceMidnight :: TimeOfDay -> DiffTime
sinceMidnight = timeOfDayToTime
#endif

-------------------------------------------------------------------------------
-- CalendarDiffTime
-------------------------------------------------------------------------------

#if MIN_VERSION_time(1,9,0) && !MIN_VERSION_base(1,9,2)
deriving instance Typeable CalendarDiffTime
deriving instance Data CalendarDiffTime
#endif

#if !MIN_VERSION_time(1,9,2)

data CalendarDiffTime = CalendarDiffTime
    { ctMonths :: Integer
    , ctTime :: NominalDiffTime
    } deriving (Eq,
    Data
    ,Typeable
    )

-- | Additive
instance Semigroup CalendarDiffTime where

    CalendarDiffTime m1 d1 <> CalendarDiffTime m2 d2 = CalendarDiffTime (m1 + m2) (d1 + d2)
instance Monoid CalendarDiffTime where
    mempty = CalendarDiffTime 0 0
    mappend = (<>)

instance Show CalendarDiffTime where
    show (CalendarDiffTime m t) = "P" ++ show m ++ "MT" ++ showFixed True (realToFrac t :: Pico) ++ "S"

calendarTimeDays :: CalendarDiffDays -> CalendarDiffTime
calendarTimeDays (CalendarDiffDays m d) = CalendarDiffTime m $ fromInteger d * nominalDay

calendarTimeTime :: NominalDiffTime -> CalendarDiffTime
calendarTimeTime dt = CalendarDiffTime 0 dt

-- | Scale by a factor. Note that @scaleCalendarDiffTime (-1)@ will not perfectly invert a duration, due to variable month lengths.
scaleCalendarDiffTime :: Integer -> CalendarDiffTime -> CalendarDiffTime
scaleCalendarDiffTime k (CalendarDiffTime m d) = CalendarDiffTime (k * m) (fromInteger k * d)
#endif

-------------------------------------------------------------------------------
-- LocalTime
-------------------------------------------------------------------------------

#if !MIN_VERSION_time(1,9,0)

-- | addLocalTime a b = a + b
addLocalTime :: NominalDiffTime -> LocalTime -> LocalTime
addLocalTime x = utcToLocalTime utc . addUTCTime x . localTimeToUTC utc

-- | diffLocalTime a b = a - b
diffLocalTime :: LocalTime -> LocalTime -> NominalDiffTime
diffLocalTime a b = diffUTCTime (localTimeToUTC utc a) (localTimeToUTC utc b)

#endif