{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifycifgs5zgecsqax53hwdyj6hb2sprehjrt7jmk3wlmdnbfon5eu",
"uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mgn5665qjdz2"
},
"path": "/t/ann-type-safe-diffutctime/13778#post_1",
"publishedAt": "2026-03-09T13:23:02.000Z",
"site": "https://discourse.haskell.org",
"tags": [
"github.com",
"GitHub - yaitskov/non-negative-time-diff: type safe diffUTCTime"
],
"textContent": "github.com\n\n### GitHub - yaitskov/non-negative-time-diff: type safe diffUTCTime\n\ntype safe diffUTCTime\n\nBoth arguments of `diffUTCTime` function from `time` package have the same type. It is easy to mix them.\n\n\n f = do\n started <- getCurrentTime\n threadDelay 10_000_000\n ended <- getCurrentTime\n pure $ started `diffUTCTime` ended\n\n\nThis package provides a stricter `diffUTCTime` that significantly\nreduces possibility of mixing its arguments by an accident.\n\n\n import Data.Time.Clock.NonNegativeTimeDiff\n f = do\n started <- getCurrentTime\n threadDelay 10_000_000\n ended <- getTimeAfter started\n pure $ ended `diffUTCTime` started\n\n\n## STM use case\n\nThe STM package is shipped without a function to get current time.\nLet’s consider a situtation like this:\n\n\n data Ctx\n = Ctx { m :: Map Int UTCTime\n , s :: TVar NominalDiffTime\n , q :: TQueue Int\n }\n\n f (c :: Ctx) = do\n now <- getCurrentTime\n atomically $ do\n i <- readTQueue q\n lookup i c.m >>= \\case\n Nothing -> pure ()\n Just t -> modifyTVar' c.s (+ diffUTCTime now t)\n\n\n`now` might be less than `t` because the queue might be empty by the time `f` is invoked. The package API enforces the correct way:\n\n\n data Ctx\n = Ctx { m :: Map Int UtcBox\n , s :: TVar NominalDiffTime\n , q :: TQueue Int\n }\n\n f (c :: Ctx) = do\n atomically $ do\n i <- readTQueue q\n lookup i c.m >>= \\case\n Nothing -> pure ()\n Just t ->\n doAfter tb \\t -> do\n now <- getTimeAfter t\n modifyTVar' c.s (+ diffUTCTime now t)\n\n\n## File access time\n\nAnother popular use case where original `diffUTCTime` might be misused.\n\n\n isFileOlderThan :: FilePath -> NominalDiffTime -> IO Bool\n isFileOlderThan fp maxAge = do\n now <- getCurrentTime\n mt <- getModificationTime fp\n when (mt `diffUTCTime` now > maxAge) $ do\n removeFile fp\n\n\nFile age is always negative in the above example - this eventually\nwould cause a space leak on disk.\n\nCorrected version:\n\n\n isFileOlderThan :: FilePath -> NominalDiffTime -> IO Bool\n isFileOlderThan fp maxAge =\n getModificationTime fp >>= (`doAfter` \\mt -> do\n now <- getTimeAfter mt\n when (now `diffUTCTime` mt > maxAge) $ do\n removeFile fp)\n",
"title": "[ANN] type safe diffUTCTime"
}