{-# LINE 1 "Data/Time/ZoneInfo.hsc" #-}
-- | Provides access to the Olson zone-info database, using an adapted version of the Olson zone-info library.
{-# LINE 2 "Data/Time/ZoneInfo.hsc" #-}

module Data.Time.ZoneInfo (
    Olson,
    ZoneInfo,
    newOlson,
    getZoneInfo,
    utcZoneInfo,
    getZoneName,
    getZoneMinutes,
    posixToZonedTime,
    utcToZonedTime',
    localToPOSIXZoned,
    localToUTCZoned,
    convertTimeZone
) where


{-# LINE 19 "Data/Time/ZoneInfo.hsc" #-}

import Data.Fixed
import Data.Time.Calendar
import Data.Time.Clock
import Data.Time.Clock.POSIX
import Data.Time.LocalTime

import Foreign
import Foreign.C

data COlson
data CZoneInfo

type Olson = ForeignPtr COlson

-- | A 'ZoneInfo' object's lifetime is bound to its 'Olson' parent.  The pair
-- includes the 'Olson' parent to ensure that it is not garbage collected
-- while the 'ZoneInfo' object is still in use.

type ZoneInfo = (Olson, Ptr CZoneInfo)

-- Foreign ctime interface.

-- struct tm

data CTm

foreign import ccall unsafe "olson_create"
  c_create :: CString -> IO (Ptr COlson)

foreign import ccall unsafe "&olson_destroy"
  c_destroy :: FunPtr (Ptr COlson -> IO ())

foreign import ccall unsafe "olson_get"
  c_get :: Ptr COlson -> CString -> IO (Ptr CZoneInfo)

foreign import ccall unsafe "olson_gmt"
  c_gmt :: Ptr COlson -> IO (Ptr CZoneInfo)

foreign import ccall unsafe "olson_localtime"
  c_localtime :: Ptr CZoneInfo -> Ptr CTime -> Ptr CTm -> IO (Ptr CTm)

foreign import ccall unsafe "olson_mktime"
  c_mktime :: Ptr CZoneInfo -> Ptr CTm -> IO CTime

foreign import ccall unsafe "olson_gmtoff"
  c_gmtoff :: Ptr CZoneInfo -> CInt -> IO CLong

foreign import ccall unsafe "olson_zone"
  c_zone :: Ptr CZoneInfo -> CInt -> IO (Ptr CChar)

-- Private conversions.

toPico :: Real a => a -> Pico -> Pico
toPico i =
    (+) (realToFrac i)

posixToUTCTime :: POSIXTime -> UTCTime
posixToUTCTime =
    posixSecondsToUTCTime

utcToPOSIXTime :: UTCTime -> POSIXTime
utcToPOSIXTime =
    utcTimeToPOSIXSeconds

cToPOSIXTime :: CTime -> Pico -> POSIXTime
cToPOSIXTime ct =
    realToFrac . toPico ct

-- Day

setCTmDay :: Ptr CTm -> Day -> IO ()
setCTmDay tm day = do

    ((\hsc_ptr -> pokeByteOff hsc_ptr 20)) tm (fromIntegral year - 1900 :: CInt)
{-# LINE 94 "Data/Time/ZoneInfo.hsc" #-}
    ((\hsc_ptr -> pokeByteOff hsc_ptr 16)) tm (fromIntegral mon - 1 :: CInt)
{-# LINE 95 "Data/Time/ZoneInfo.hsc" #-}
    ((\hsc_ptr -> pokeByteOff hsc_ptr 12)) tm (fromIntegral mday :: CInt)
{-# LINE 96 "Data/Time/ZoneInfo.hsc" #-}
    return ()

  where
    (year, mon, mday) = toGregorian day

getCTmDay :: Ptr CTm -> IO Day
getCTmDay tm = do

    year <- ((\hsc_ptr -> peekByteOff hsc_ptr 20)) tm :: IO CInt
{-# LINE 105 "Data/Time/ZoneInfo.hsc" #-}
    mon <- ((\hsc_ptr -> peekByteOff hsc_ptr 16)) tm :: IO CInt
{-# LINE 106 "Data/Time/ZoneInfo.hsc" #-}
    mday <- ((\hsc_ptr -> peekByteOff hsc_ptr 12)) tm :: IO CInt
{-# LINE 107 "Data/Time/ZoneInfo.hsc" #-}

    return $ fromGregorian (fromIntegral year + 1900)
              (fromIntegral mon + 1) (fromIntegral mday)

-- TimeOfDay

setCTmTimeOfDay :: Ptr CTm -> TimeOfDay -> IO ()
setCTmTimeOfDay tm tod = do

    ((\hsc_ptr -> pokeByteOff hsc_ptr 8)) tm (fromIntegral hour :: CInt)
{-# LINE 117 "Data/Time/ZoneInfo.hsc" #-}
    ((\hsc_ptr -> pokeByteOff hsc_ptr 4)) tm (fromIntegral min' :: CInt)
{-# LINE 118 "Data/Time/ZoneInfo.hsc" #-}
    ((\hsc_ptr -> pokeByteOff hsc_ptr 0)) tm (truncate sec :: CInt)
{-# LINE 119 "Data/Time/ZoneInfo.hsc" #-}

    return ()

  where
    hour = todHour tod
    min' = todMin tod
    sec = todSec tod

getCTmTimeOfDay :: Ptr CTm -> Pico -> IO TimeOfDay
getCTmTimeOfDay tm psec = do

    hour <- ((\hsc_ptr -> peekByteOff hsc_ptr 8)) tm :: IO CInt
{-# LINE 131 "Data/Time/ZoneInfo.hsc" #-}
    min' <- ((\hsc_ptr -> peekByteOff hsc_ptr 4)) tm :: IO CInt
{-# LINE 132 "Data/Time/ZoneInfo.hsc" #-}
    sec <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) tm :: IO CInt
{-# LINE 133 "Data/Time/ZoneInfo.hsc" #-}

    return $ TimeOfDay (fromIntegral hour) (fromIntegral min')
               (toPico sec psec)

-- TimeZone

getCTmTimeZone :: Ptr CZoneInfo -> Ptr CTm -> IO TimeZone
getCTmTimeZone zi tm = do

    isdst <- ((\hsc_ptr -> peekByteOff hsc_ptr 32)) tm :: IO CInt
{-# LINE 143 "Data/Time/ZoneInfo.hsc" #-}
    gmtoff <- c_gmtoff zi isdst
    zone <- c_zone zi isdst >>= peekCString

    return $ TimeZone (fromIntegral $ div gmtoff 60) (isdst /= 0) zone

-- ZonedTime

ctmToZonedTime :: Ptr CZoneInfo -> Ptr CTm -> Pico -> IO ZonedTime
ctmToZonedTime zi tm psec = do

    day <- getCTmDay tm
    tod <- getCTmTimeOfDay tm psec
    zone <- getCTmTimeZone zi tm

    return $ ZonedTime (LocalTime day tod) zone

-- CTime

ctimeToZonedTime :: Ptr CZoneInfo -> Ptr CTime -> Pico -> IO ZonedTime
ctimeToZonedTime zi ct psec = do
    allocaBytes (44) $ \ tm -> do
{-# LINE 164 "Data/Time/ZoneInfo.hsc" #-}
                                c_localtime zi ct tm
                                ctmToZonedTime zi tm psec

-- Olson finalizer.

newOlsonFinalizer :: Ptr COlson -> IO Olson
newOlsonFinalizer =
    newForeignPtr c_destroy

-- Exports.

-- | Create a new 'Olson' context object.  A path to the zone-info database
-- may be specified.  Otherwise, the TZDIR environment variable, or a
-- reasonable default, will be used.  An 'IOError' will be thrown on failure.

newOlson :: Maybe String -> IO Olson

newOlson Nothing = do
    throwErrnoIfNull "Data.Time.ZoneInfo.newOlson" io
    io >>= newOlsonFinalizer
  where
    io = c_create nullPtr

newOlson (Just s) = do
    throwErrnoIfNull "Data.Time.ZoneInfo.newOlson" io
    io >>= newOlsonFinalizer
  where
    io = withCString s c_create

-- | Obtain a 'ZoneInfo' object based on the specified Olson identifier or
-- time-zone.  If the zone-info database cannot be found, or the time-zone not
-- recognised, then an 'IOError' will be thrown.  Calls to this function must
-- be serialised across multiple threads.

getZoneInfo :: Olson -> String -> IO ZoneInfo
getZoneInfo ols s = do
    io' <- throwErrnoIfNull "Data.Time.ZoneInfo.getZoneInfo" io
    return (ols, io')
  where
    ols' = unsafeForeignPtrToPtr ols
    io = withCString s $ c_get ols'

-- | 'ZoneInfo' for the UTC time-zone.

utcZoneInfo :: Olson -> ZoneInfo
utcZoneInfo ols =
    (ols, unsafePerformIO $ c_gmt ols')
  where
    ols' = unsafeForeignPtrToPtr ols

-- | Returns the zone-name for either the standard or daylight saving zone,
-- depending on the 'isdst' argument.

getZoneName :: ZoneInfo -> Bool -> IO (String)
getZoneName (_, zi) isdst = do
    c_zone zi isdst' >>= peekCString
  where
    isdst' = if isdst then 1 else 0

-- | Returns the UTC offset for either the standard or daylight saving zone,
-- depending on the 'isdst' argument.

getZoneMinutes :: ZoneInfo -> Bool -> IO Int
getZoneMinutes (_, zi) isdst = do
    tz <- c_gmtoff zi isdst'
    return $ fromIntegral $ div tz 60
  where
    isdst' = if isdst then 1 else 0

-- | Convert from 'POSIXTime' to zoned 'LocalTime'.

posixToZonedTime :: ZoneInfo -> POSIXTime -> IO ZonedTime
posixToZonedTime (_, zi) pt = do

  -- "with" creates a Ptr CTime.

    with (fromInteger sec :: CTime) $ \ ct -> do
                           ctimeToZonedTime zi ct (realToFrac psec)
  where
    (sec, psec) = divMod' pt 1

-- | Convert from 'UTCTime' to zoned 'LocalTime'.

utcToZonedTime' :: ZoneInfo -> UTCTime -> IO ZonedTime
utcToZonedTime' zi =
   posixToZonedTime zi . utcToPOSIXTime

-- | Convert 'LocalTime' to a daylight saving adjusted pair.

localToPOSIXZoned :: ZoneInfo -> LocalTime -> IO (POSIXTime, ZonedTime)
localToPOSIXZoned (_, zi) lt =
    allocaBytes (44) $ \ tm -> do
{-# LINE 256 "Data/Time/ZoneInfo.hsc" #-}
      setCTmDay tm day
      setCTmTimeOfDay tm tod
      ((\hsc_ptr -> pokeByteOff hsc_ptr 32)) tm (-1 :: CInt)
{-# LINE 259 "Data/Time/ZoneInfo.hsc" #-}
      ct <- c_mktime zi tm
      zt <- ctmToZonedTime zi tm psec
      return (cToPOSIXTime ct psec, zt)
  where
    day = localDay lt
    tod = localTimeOfDay lt
    sec = todSec tod
    psec = mod' sec 1

-- | Convert 'LocalTime' to a daylight saving adjusted pair.

localToUTCZoned :: ZoneInfo -> LocalTime -> IO (UTCTime, ZonedTime)
localToUTCZoned zi lt = do
    (pt, zi') <- localToPOSIXZoned zi lt
    return (posixToUTCTime pt, zi')

-- | Convert the 'LocalTime' argument from the source to destination
-- 'TimeZone'.

convertTimeZone :: ZoneInfo -> LocalTime -> ZoneInfo -> IO ZonedTime
convertTimeZone src lt dst = do
    (pt, _) <- localToPOSIXZoned src lt
    posixToZonedTime dst pt