{-# LANGUAGE CPP #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveAnyClass #-}
module Data.Time.Clock.TAI.Support (
TAISync, UpdatePolicy
, initSync
, getTAI, absGuessUtc, utcGuessAbs
, currentLeapMap
, periodicBackgroundDownload
) where
import qualified Control.Exception as E
import Control.Monad
import Control.Monad.Trans
import Data.Int
import Data.IORef
import Data.Maybe
import Data.Time
import Data.Time.Clock.TAI
import Data.Time.Clock.TAI.LeapData
import Data.Time.Clock.TAI.Parser
import qualified GHC.Event as TM
import qualified System.Clock as Clock
import System.Mem.Weak
data TAISync =
TAISync
{ _tsSystemBootEpoch :: AbsoluteTime
, _lslRef :: IORef LeapSecondList
}
type UpdatePolicy = IO (IORef LeapSecondList)
data TimeSyncException =
TimeSyncTooLongException
deriving (Show, E.Exception)
initSync :: MonadIO m => UpdatePolicy -> m TAISync
initSync uppolicy = liftIO $ do
sinceBoot <- Clock.getTime Clock.Boottime
now <- getCurrentTime
sinceBoot' <- Clock.getTime Clock.Boottime
lslRef <- liftIO uppolicy
lsl <- readIORef lslRef
let nowTAI = utcGuessAbs' lsl now
let d1 = timeSpec2DiffTime sinceBoot
let d2 = timeSpec2DiffTime sinceBoot'
let diffSinceBootMiddle = (d1 + d2) / 2
unless (d2-d1 < 10^^(-4::Int)) . E.throw $ TimeSyncTooLongException
return $ TAISync ((negate diffSinceBootMiddle) `addAbsoluteTime` nowTAI) lslRef
periodicBackgroundDownload :: LeapSources -> Int -> Int -> UpdatePolicy
periodicBackgroundDownload ls dbetween dretry = do
lsl <- sourceLeapData ls
lslRef <- newIORef lsl
lslWRef <- mkWeakIORef lslRef (return ())
regTimed dbetween lslWRef
return lslRef
where
regTimed :: Int -> Weak (IORef LeapSecondList) -> IO ()
regTimed d wr = do
tm <- TM.getSystemTimerManager
void $ TM.registerTimeout tm (d*24*60*60*1000000) (timedUpdater wr)
timedUpdater :: Weak (IORef LeapSecondList) -> IO ()
timedUpdater wr = do
mr <- deRefWeak wr
case mr of
Nothing -> return ()
Just r -> E.handle (\(_::E.SomeException) -> regTimed dretry wr) $ do
lsl <- sourceLeapData ls
atomicModifyIORef' r (const (lsl, ()))
regTimed dbetween wr
timeSpec2DiffTime :: Clock.TimeSpec -> DiffTime
timeSpec2DiffTime ct =
picosecondsToDiffTime (10^(12::Int) * (fromIntegral $ Clock.sec ct)
+ 1000 * (fromIntegral $ Clock.nsec ct))
getTAI :: MonadIO m => TAISync -> m AbsoluteTime
getTAI (TAISync btb _) =
liftIO $ ((`addAbsoluteTime` btb) . timeSpec2DiffTime) <$> Clock.getTime Clock.Boottime
lookupDayInList :: LeapSecondList -> Day -> Maybe Int32
lookupDayInList list day
| day >= expirationDate list = Nothing
| otherwise = foldl go Nothing $ leapSeconds list
where
go Nothing (dayOfLeapSecond, dtai)
| day >= dayOfLeapSecond = Just dtai
go (Just dtai) (dayOfLeapSecond, dtai')
| day >= dayOfLeapSecond = Just $ max dtai dtai'
go x _ = x
handlingOutOfRange :: LeapSecondList -> Day -> Integer
handlingOutOfRange lsl day = fromIntegral $
let (maxMapDay, maxDayLeaps) = maximum . leapSeconds $ lsl
(minMapDay, minDayLeaps) = minimum . leapSeconds $ lsl
in case (day < minMapDay, day > maxMapDay) of
(True, False) -> minDayLeaps
(False, True) -> maxDayLeaps
(False, False) -> fromJust $ lookupDayInList lsl day
_ -> error "Day both larger then max and smaller then min!"
absGuessUtc :: MonadIO m => TAISync -> AbsoluteTime -> m UTCTime
absGuessUtc (TAISync _ lr) at = liftIO $ (`absGuessUtc'` at) <$> readIORef lr
absGuessUtc' :: LeapSecondList -> AbsoluteTime -> UTCTime
utcGuessAbs :: MonadIO m => TAISync -> UTCTime -> m AbsoluteTime
utcGuessAbs (TAISync _ lr) ut = liftIO $ (`utcGuessAbs'` ut) <$> readIORef lr
utcGuessAbs' :: LeapSecondList -> UTCTime -> AbsoluteTime
#if MIN_VERSION_time(1,7,0)
currentLeapMap :: MonadIO m => TAISync -> m LeapSecondMap
currentLeapMap = fmap leapListToMap . liftIO . readIORef . _lslRef
leapListToMap :: LeapSecondList -> LeapSecondMap
leapListToMap lsl day = fmap fromIntegral . lookup day . leapSeconds $ lsl
absGuessUtc' lsl = fromJust . taiToUTCTime (Just . fromIntegral . handlingOutOfRange lsl)
utcGuessAbs' lsl = fromJust . utcToTAITime (Just . fromIntegral . handlingOutOfRange lsl)
#else
currentLeapMap :: MonadIO m => TAISync -> m LeapSecondTable
currentLeapMap = fmap leapListToMap . liftIO . readIORef . _lslRef
leapListToMap :: LeapSecondList -> LeapSecondTable
leapListToMap lsl day = fromIntegral . fromJust . lookup day . leapSeconds $ lsl
absGuessUtc' lsl = taiToUTCTime (handlingOutOfRange lsl)
utcGuessAbs' lsl = utcToTAITime (handlingOutOfRange lsl)
#endif