non-negative-time-diff: 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` ended
This 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` started
STM 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
Downloads
- non-negative-time-diff-0.0.2.tar.gz [browse] (Cabal source package)
- Package description (as included in the package)
Maintainer's Corner
For package maintainers and hackage trustees
Candidates
- No Candidates
| Versions [RSS] | 0.0.1, 0.0.2 |
|---|---|
| Change log | changelog.md |
| Dependencies | aeson (<3), base (>=4.7 && <5), deepseq (<2), directory (<2), ghc-typelits-natnormalise (<1), mtl (<3), safecopy (<1), time (<2) [details] |
| Tested with | ghc ==9.12.2 |
| License | BSD-3-Clause |
| Copyright | Daniil Iaitkov 2026 |
| Author | Daniil Iaitskov |
| Maintainer | dyaitskov@gmail.com |
| Uploaded | by DaniilIaitskov at 2026-03-14T12:57:42Z |
| Category | System |
| Home page | http://github.com/yaitskov/non-negative-time-diff |
| Bug tracker | https://github.com/yaitskov/non-negative-time-diff/issues |
| Source repo | head: git clone https://github.com/yaitskov/non-negative-time-diff.git |
| Distributions | |
| Downloads | 3 total (3 in the last 30 days) |
| Rating | (no votes yet) [estimated by Bayesian average] |
| Your Rating | |
| Status | Docs uploaded by user Build status unknown [no reports yet] |