type safe diffUTCTime.
Both arguments of diffUTCTime function from time package have the same type. It is easy to mix them.
f = do
started <- getCurrentTime
threadDelay 10_000_000
ended <- getCurrentTime
pure $ started `diffUTCTime` endedThis package provides a stricter diffUTCTime that significantly reduces possibility of mixing its arguments by an accident.
import Data.Time.Clock.NonNegativeTimeDiff
f = do
started <- getCurrentTime
threadDelay 10_000_000
ended <- getTimeAfter started
pure $ ended `diffUTCTime` startedSTM use case
The STM package is shipped without a function to get current time. Let’s consider a situtation like this:
data Ctx
= Ctx { m :: Map Int UTCTime
, s :: TVar NominalDiffTime
, q :: TQueue Int
}
f (c :: Ctx) = do
now <- getCurrentTime
atomically $ do
i <- readTQueue q
lookup i c.m >>= \case
Nothing -> pure ()
Just t -> modifyTVar' c.s (+ diffUTCTime now t)now might be less than t because the queue might be empty by the time f is invoked. The package API can correct the above snippet as follows:
data Ctx
= Ctx { m :: Map Int UtcBox
, s :: TVar NominalDiffTime
, q :: TQueue Int
}
f (c :: Ctx) = do
atomically $ do
i <- readTQueue q
lookup i c.m >>= \case
Nothing -> pure ()
Just t ->
doAfter tb \t -> do
now <- getTimeAfter t
modifyTVar' c.s (+ diffUTCTime now t)File access time
Another popular usecase where original diffUTCTime might be misused.
isFileOlderThan :: FilePath -> NominalDiffTime -> IO Bool
isFileOlderThan fp maxAge = do
now <- getCurrentTime
mt <- getModificationTime fp
when (mt `diffUTCTime` now > maxAge) $ do
removeFile fpFile age is always negative in the above example - this eventually would cause a space leak on disk.
Corrected version:
isFileOlderThan :: FilePath -> NominalDiffTime -> IO Bool
isFileOlderThan fp maxAge =
getModificationTime fp >>= (`doAfter` \mt -> do
now <- getTimeAfter mt
when (now `diffUTCTime` mt > maxAge) $ do
removeFile fp)Requirements
Unboxing UtcBox values requires a GHC natnormalise plugin:
{-# GHC_OPTIONS -fplugin GHC.TypeLits.Normalise #-}Static linking
In case of static linking define macro STATIC to disable natnormalise GHC plugin that is not available in such setup. UtcBox version for the static build is less strict, because it does not have existential variable.
cabal build --ghc-option=-optP=-DSTATIC