{- |
   Module     : Data.Convertible.Instances.Time
   Copyright  : Copyright (C) 2009-2011 John Goerzen
   License    : BSD3

   Maintainer : John Goerzen <jgoerzen@complete.org>
   Stability  : provisional
   Portability: portable

Instances to convert between various time structures, both old- and new-style.

At present, this module does not do full input validation.  That is, it is possible
to get an exception rather than a Left result from these functions if your input is
invalid, particularly when converting from the old-style System.Time structures.

Copyright (C) 2009-2011 John Goerzen <jgoerzen@complete.org>

All rights reserved.

For license and copyright information, see the file LICENSE

-}

module Data.Convertible.Instances.Time()
where

import Data.Convertible.Base
import Data.Convertible.Utils
import Data.Convertible.Instances.Num()
import qualified System.Time as ST
import Data.Time
import Data.Time.Clock.POSIX
import Data.Time.Calendar.OrdinalDate
import Data.Ratio
import Foreign.C.Types

----------------------------------------------------------------------
-- Intra-System.Time stuff
----------------------------------------------------------------------

instance Convertible ST.ClockTime ST.CalendarTime where
    safeConvert = return . ST.toUTCTime

instance Convertible ST.CalendarTime ST.ClockTime where
    safeConvert = return . ST.toClockTime

instance Convertible ST.ClockTime Integer where
    safeConvert (ST.TOD x _) = return x

instance Convertible Integer ST.ClockTime where
    safeConvert x = return $ ST.TOD x 0

----------------------------------------------------------------------
-- Intra-Data.Time stuff
----------------------------------------------------------------------

------------------------------ POSIX and UTC times
{- Covered under Real a
instance Convertible Rational POSIXTime where
    safeConvert = return . fromRational
-}

instance Convertible Rational POSIXTime where
    safeConvert = return . fromRational
instance Convertible Integer POSIXTime where
    safeConvert = return . fromInteger
instance Convertible Int POSIXTime where
    safeConvert = return . fromIntegral
instance Convertible Double POSIXTime where
    safeConvert = return . realToFrac

instance Convertible POSIXTime Integer where
    safeConvert = return . truncate
instance Convertible POSIXTime Rational where
    safeConvert = return . toRational
instance Convertible POSIXTime Double where
    safeConvert = return . realToFrac
instance Convertible POSIXTime Int where
    safeConvert = boundedConversion (return . truncate)

instance Convertible POSIXTime UTCTime where
    safeConvert = return . posixSecondsToUTCTime
instance Convertible UTCTime POSIXTime where
    safeConvert = return . utcTimeToPOSIXSeconds

instance Convertible Rational UTCTime where
    safeConvert a = posixSecondsToUTCTime <$> safeConvert a
instance Convertible Integer UTCTime where
    safeConvert a = posixSecondsToUTCTime <$> safeConvert a
instance Convertible Int UTCTime where
    safeConvert a = posixSecondsToUTCTime <$> safeConvert a
instance Convertible Double UTCTime where
    safeConvert a = posixSecondsToUTCTime <$> safeConvert a

instance Convertible UTCTime Rational where
    safeConvert = safeConvert . utcTimeToPOSIXSeconds
instance Convertible UTCTime Integer where
    safeConvert = safeConvert . utcTimeToPOSIXSeconds
instance Convertible UTCTime Double where
    safeConvert = safeConvert . utcTimeToPOSIXSeconds
instance Convertible UTCTime Int where
    safeConvert = boundedConversion (safeConvert . utcTimeToPOSIXSeconds)

------------------------------ LocalTime stuff

instance Convertible UTCTime ZonedTime where
    safeConvert = return . utcToZonedTime utc
instance Convertible POSIXTime ZonedTime where
    safeConvert = return . utcToZonedTime utc . posixSecondsToUTCTime
instance Convertible ZonedTime UTCTime where
    safeConvert = return . zonedTimeToUTC
instance Convertible ZonedTime POSIXTime where
    safeConvert = return . utcTimeToPOSIXSeconds . zonedTimeToUTC

{- Too obvious?
instance Convertible LocalTime Day where
    safeConvert = return . localDay
instance Convertible LocalTime TimeOfDay where
    safeConvert = return . localTimeOfDay
-}

----------------------------------------------------------------------
-- Conversions between old and new time
----------------------------------------------------------------------
instance Convertible ST.CalendarTime ZonedTime where
    safeConvert ct = return $ ZonedTime {
     zonedTimeToLocalTime = LocalTime {
       localDay = fromGregorian (fromIntegral $ ST.ctYear ct)
                  (1 + fromEnum (ST.ctMonth ct))
                  (ST.ctDay ct),
       localTimeOfDay = TimeOfDay {
         todHour = ST.ctHour ct,
         todMin = ST.ctMin ct,
         todSec = fromIntegral (ST.ctSec ct) +
                  fromRational (ST.ctPicosec ct % 1000000000000)
                        }
                            },
     zonedTimeZone = TimeZone {
                       timeZoneMinutes = ST.ctTZ ct `div` 60,
                       timeZoneSummerOnly = ST.ctIsDST ct,
                       timeZoneName = ST.ctTZName ct}
}

instance Convertible ST.CalendarTime POSIXTime where
    safeConvert = convertVia (undefined::ST.ClockTime)
instance Convertible ST.CalendarTime UTCTime where
    safeConvert = convertVia (undefined::POSIXTime)

instance Convertible ST.ClockTime POSIXTime where
    safeConvert (ST.TOD x y) = return $ fromRational $
                                        fromInteger x + fromRational (y % 1000000000000)
instance Convertible ST.ClockTime UTCTime where
    safeConvert = convertVia (undefined::POSIXTime)
instance Convertible ST.ClockTime ZonedTime where
    safeConvert = convertVia (undefined::UTCTime)
instance Convertible ZonedTime ST.ClockTime where
    safeConvert = convertVia (undefined::POSIXTime)

instance Convertible POSIXTime ST.ClockTime where
    safeConvert x = return $ ST.TOD rsecs rpico
        where rsecs = floor x
              rpico = truncate $ abs $ 1000000000000 * (x - fromIntegral rsecs)
instance Convertible UTCTime ST.ClockTime where
    safeConvert = safeConvert . utcTimeToPOSIXSeconds

instance Convertible ZonedTime ST.CalendarTime where
    safeConvert zt = return $ ST.CalendarTime {
            ST.ctYear = fromIntegral year,
            ST.ctMonth = toEnum (month - 1),
            ST.ctDay = day,
            ST.ctHour = todHour ltod,
            ST.ctMin = todMin ltod,
            ST.ctSec = secs,
            ST.ctPicosec = pico,
            ST.ctWDay = toEnum . snd . sundayStartWeek . localDay . zonedTimeToLocalTime $ zt,
            ST.ctYDay = (snd . toOrdinalDate . localDay . zonedTimeToLocalTime $ zt) - 1,
            ST.ctTZName = timeZoneName . zonedTimeZone $ zt,
            ST.ctTZ = (timeZoneMinutes . zonedTimeZone $ zt) * 60,
            ST.ctIsDST = timeZoneSummerOnly . zonedTimeZone $ zt
          }
        where (year, month, day) = toGregorian . localDay . zonedTimeToLocalTime $ zt
              ltod = localTimeOfDay . zonedTimeToLocalTime $ zt
              secs = (truncate . todSec $ ltod)::Int
              picoRational = toRational (todSec ltod) - toRational secs
              pico = truncate (picoRational * 1000000000000)
instance Convertible POSIXTime ST.CalendarTime where
    safeConvert = convertVia (undefined::ZonedTime)
instance Convertible UTCTime ST.CalendarTime where
    safeConvert = safeConvert . utcTimeToPOSIXSeconds

instance Convertible ST.TimeDiff NominalDiffTime where
    {- This is a clever hack.  We convert the TimeDiff to a ClockTime, applying
       it as a diff vs. the epoch.  Converting this ClockTime to a POSIXTime yiels
       the NominalDiffTime we want, since a POSIXTime is a NominalDiffTime vs. the
       epoch. -}
    safeConvert td = safeConvert clockTime
        where clockTime = ST.addToClockTime td (ST.TOD 0 0)
instance Convertible NominalDiffTime ST.TimeDiff where
    {- Similar clever hack as above. -}
    safeConvert ndt =
        do clockt <- safeConvert ndt
           return (ST.diffClockTimes clockt (ST.TOD 0 0))

instance Convertible Integer ST.TimeDiff where
    safeConvert = convertVia (undefined::NominalDiffTime)
instance Convertible Double ST.TimeDiff where
    safeConvert = convertVia (undefined::NominalDiffTime)
instance Convertible ST.TimeDiff Integer where
    safeConvert = convertVia (undefined :: NominalDiffTime)
instance Convertible ST.TimeDiff Rational where
    safeConvert = convertVia (undefined :: NominalDiffTime)
instance Convertible ST.TimeDiff Double where
    safeConvert = convertVia (undefined :: NominalDiffTime)

----------------------------------------------------------------------
-- Foreign.C Types
----------------------------------------------------------------------

instance Convertible CTime POSIXTime where
    safeConvert = return . realToFrac
instance Convertible POSIXTime CTime where
    safeConvert = return . fromInteger . truncate

instance Convertible CTime Integer where
    safeConvert = return . truncate . toRational
instance Convertible Integer CTime where
    safeConvert = return . fromInteger

instance Convertible CTime Double where
    safeConvert = return . realToFrac
instance Convertible Double CTime where
    safeConvert = return . fromInteger . truncate

instance Convertible CTime Int where
    safeConvert x = do r1 <- safeConvert x
                       boundedConversion (return . fromInteger) r1
instance Convertible Int CTime where
    safeConvert = safeConvert . toInteger

instance Convertible CTime UTCTime where
    safeConvert = convertVia (undefined :: POSIXTime)
instance Convertible UTCTime CTime where
    safeConvert = convertVia (undefined :: POSIXTime)

instance Convertible CTime ST.ClockTime where
    safeConvert = convertVia (undefined :: POSIXTime)
instance Convertible ST.ClockTime CTime where
    safeConvert = convertVia (undefined :: POSIXTime)

instance Convertible CTime ST.CalendarTime where
    safeConvert = convertVia (undefined::POSIXTime)
instance Convertible ST.CalendarTime CTime where
    safeConvert = convertVia (undefined::POSIXTime)

instance Convertible CTime ZonedTime where
    safeConvert = convertVia (undefined::POSIXTime)
instance Convertible ZonedTime CTime where
    safeConvert = convertVia (undefined::POSIXTime)
