{-# LANGUAGE OverloadedStrings #-}
module Data.Time.LocalTime.TimeZone.Detect
( TimeZoneName
, TimeZoneDatabase
, openTimeZoneDatabase
, closeTimeZoneDatabase
, withTimeZoneDatabase
, lookupTimeZoneName
, lookupTimeZoneNameFromFile
, timeAtPointToUTC
, timeAtPointToUTCFromFile
, timeInTimeZoneToUTC
) where
import Foreign.ZoneDetect
import Foreign.C.String (peekCAString, withCAString)
import Foreign (Ptr, nullPtr)
import Data.Time
import Data.Time.LocalTime.TimeZone.Olson
import Data.Time.LocalTime.TimeZone.Series
import System.IO.Unsafe (unsafePerformIO)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Fail (MonadFail, fail)
import Prelude hiding (fail)
import Control.Exception (bracket)
type TimeZoneName = FilePath
type TimeZoneDatabase = Ptr ZoneDetectInfo
lookupTimeZoneName :: MonadFail m => TimeZoneDatabase -> Double -> Double -> m TimeZoneName
lookupTimeZoneName :: TimeZoneDatabase -> Double -> Double -> m TimeZoneName
lookupTimeZoneName TimeZoneDatabase
database Double
lat Double
lng =
IO (m TimeZoneName) -> m TimeZoneName
forall a. IO a -> a
unsafePerformIO (IO (m TimeZoneName) -> m TimeZoneName)
-> IO (m TimeZoneName) -> m TimeZoneName
forall a b. (a -> b) -> a -> b
$ do
if TimeZoneDatabase
database TimeZoneDatabase -> TimeZoneDatabase -> Bool
forall a. Eq a => a -> a -> Bool
== TimeZoneDatabase
forall a. Ptr a
nullPtr then
m TimeZoneName -> IO (m TimeZoneName)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (m TimeZoneName -> IO (m TimeZoneName))
-> m TimeZoneName -> IO (m TimeZoneName)
forall a b. (a -> b) -> a -> b
$ TimeZoneName -> m TimeZoneName
forall (m :: * -> *) a. MonadFail m => TimeZoneName -> m a
fail TimeZoneName
"Invalid timezone database."
else do
CString
tzName <- TimeZoneDatabase -> CFloat -> CFloat -> IO CString
c_ZDHelperSimpleLookupString TimeZoneDatabase
database
(Double -> CFloat
forall a b. (Real a, Fractional b) => a -> b
realToFrac Double
lat)
(Double -> CFloat
forall a b. (Real a, Fractional b) => a -> b
realToFrac Double
lng)
if CString
tzName CString -> CString -> Bool
forall a. Eq a => a -> a -> Bool
== CString
forall a. Ptr a
nullPtr then
m TimeZoneName -> IO (m TimeZoneName)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (m TimeZoneName -> IO (m TimeZoneName))
-> m TimeZoneName -> IO (m TimeZoneName)
forall a b. (a -> b) -> a -> b
$ TimeZoneName -> m TimeZoneName
forall (m :: * -> *) a. MonadFail m => TimeZoneName -> m a
fail TimeZoneName
"Invalid coordinates."
else
CString -> IO TimeZoneName
peekCAString CString
tzName IO TimeZoneName
-> (TimeZoneName -> IO (m TimeZoneName)) -> IO (m TimeZoneName)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (m TimeZoneName -> IO (m TimeZoneName)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (m TimeZoneName -> IO (m TimeZoneName))
-> (TimeZoneName -> m TimeZoneName)
-> TimeZoneName
-> IO (m TimeZoneName)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TimeZoneName -> m TimeZoneName
forall (m :: * -> *) a. Monad m => a -> m a
return)
lookupTimeZoneNameFromFile :: FilePath -> Double -> Double -> IO TimeZoneName
lookupTimeZoneNameFromFile :: TimeZoneName -> Double -> Double -> IO TimeZoneName
lookupTimeZoneNameFromFile TimeZoneName
databaseLocation Double
lat Double
lng =
TimeZoneName
-> (TimeZoneDatabase -> IO TimeZoneName) -> IO TimeZoneName
forall a. TimeZoneName -> (TimeZoneDatabase -> IO a) -> IO a
withTimeZoneDatabase TimeZoneName
databaseLocation
(\TimeZoneDatabase
db -> TimeZoneDatabase -> Double -> Double -> IO TimeZoneName
forall (m :: * -> *).
MonadFail m =>
TimeZoneDatabase -> Double -> Double -> m TimeZoneName
lookupTimeZoneName TimeZoneDatabase
db Double
lat Double
lng)
timeInTimeZoneToUTC :: TimeZoneName -> LocalTime -> IO UTCTime
timeInTimeZoneToUTC :: TimeZoneName -> LocalTime -> IO UTCTime
timeInTimeZoneToUTC TimeZoneName
tzName LocalTime
referenceTime = do
TimeZoneSeries
tzSeries <- TimeZoneName -> IO TimeZoneSeries
getTimeZoneSeriesFromOlsonFileUNIX TimeZoneName
tzName
UTCTime -> IO UTCTime
forall (m :: * -> *) a. Monad m => a -> m a
return (UTCTime -> IO UTCTime) -> UTCTime -> IO UTCTime
forall a b. (a -> b) -> a -> b
$ TimeZoneSeries -> LocalTime -> UTCTime
localTimeToUTC' TimeZoneSeries
tzSeries LocalTime
referenceTime
timeAtPointToUTC :: TimeZoneDatabase -> Double -> Double -> LocalTime -> IO UTCTime
timeAtPointToUTC :: TimeZoneDatabase -> Double -> Double -> LocalTime -> IO UTCTime
timeAtPointToUTC TimeZoneDatabase
database Double
lat Double
lng LocalTime
referenceTime = do
TimeZoneName
tzName <- TimeZoneDatabase -> Double -> Double -> IO TimeZoneName
forall (m :: * -> *).
MonadFail m =>
TimeZoneDatabase -> Double -> Double -> m TimeZoneName
lookupTimeZoneName TimeZoneDatabase
database Double
lat Double
lng
TimeZoneName -> LocalTime -> IO UTCTime
timeInTimeZoneToUTC TimeZoneName
tzName LocalTime
referenceTime
timeAtPointToUTCFromFile :: FilePath -> Double -> Double -> LocalTime -> IO UTCTime
timeAtPointToUTCFromFile :: TimeZoneName -> Double -> Double -> LocalTime -> IO UTCTime
timeAtPointToUTCFromFile TimeZoneName
databaseLocation Double
lat Double
lng LocalTime
referenceTime =
TimeZoneName -> (TimeZoneDatabase -> IO UTCTime) -> IO UTCTime
forall a. TimeZoneName -> (TimeZoneDatabase -> IO a) -> IO a
withTimeZoneDatabase TimeZoneName
databaseLocation
(\TimeZoneDatabase
db -> TimeZoneDatabase -> Double -> Double -> LocalTime -> IO UTCTime
timeAtPointToUTC TimeZoneDatabase
db Double
lat Double
lng LocalTime
referenceTime)
getTimeZoneSeriesFromOlsonFileUNIX :: TimeZoneName -> IO TimeZoneSeries
getTimeZoneSeriesFromOlsonFileUNIX :: TimeZoneName -> IO TimeZoneSeries
getTimeZoneSeriesFromOlsonFileUNIX TimeZoneName
tzName =
TimeZoneName -> IO TimeZoneSeries
getTimeZoneSeriesFromOlsonFile (TimeZoneName -> IO TimeZoneSeries)
-> TimeZoneName -> IO TimeZoneSeries
forall a b. (a -> b) -> a -> b
$ TimeZoneName
"/usr/share/zoneinfo/" TimeZoneName -> TimeZoneName -> TimeZoneName
forall a. [a] -> [a] -> [a]
++ TimeZoneName
tzName
openTimeZoneDatabase :: FilePath -> IO TimeZoneDatabase
openTimeZoneDatabase :: TimeZoneName -> IO TimeZoneDatabase
openTimeZoneDatabase TimeZoneName
databaseLocation =
TimeZoneName
-> (CString -> IO TimeZoneDatabase) -> IO TimeZoneDatabase
forall a. TimeZoneName -> (CString -> IO a) -> IO a
withCAString TimeZoneName
databaseLocation ((CString -> IO TimeZoneDatabase) -> IO TimeZoneDatabase)
-> (CString -> IO TimeZoneDatabase) -> IO TimeZoneDatabase
forall a b. (a -> b) -> a -> b
$ \CString
dbl -> CString -> IO TimeZoneDatabase
c_ZDOpenDatabase CString
dbl
closeTimeZoneDatabase :: TimeZoneDatabase -> IO ()
closeTimeZoneDatabase :: TimeZoneDatabase -> IO ()
closeTimeZoneDatabase = TimeZoneDatabase -> IO ()
c_ZDCloseDatabase
withTimeZoneDatabase :: FilePath -> (TimeZoneDatabase -> IO a) -> IO a
withTimeZoneDatabase :: TimeZoneName -> (TimeZoneDatabase -> IO a) -> IO a
withTimeZoneDatabase TimeZoneName
databaseLocation =
IO TimeZoneDatabase
-> (TimeZoneDatabase -> IO ())
-> (TimeZoneDatabase -> IO a)
-> IO a
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket (TimeZoneName -> IO TimeZoneDatabase
openTimeZoneDatabase TimeZoneName
databaseLocation)
(TimeZoneDatabase -> IO ()
closeTimeZoneDatabase)