{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreifycifgs5zgecsqax53hwdyj6hb2sprehjrt7jmk3wlmdnbfon5eu",
    "uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mgndv7rrqi42"
  },
  "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"
}