packages feed

fb-util-0.1.0.1: Util/Time.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# LANGUAGE NamedFieldPuns #-}
module Util.Time
  ( -- * absolute time
    EpochClock(..)
  , getEpochTime, toEpochSeconds, toEpochNanos
  , epochClockToUTCTime, showUTC, readUTC
  , showEpochTime
    -- * time point differences
  , TimePoint(..), DiffTimePoints(..), getTimePoint
  , addToTimePoint
  , diffTimePoints, addDiffTimePoints
  , getElapsedTime, elapsedTime
  , toDiffMillis, toDiffMicros, toDiffNanos, toDiffSeconds
    -- * utils
  , delay, nanoseconds, seconds, minutes, hours
  ) where

import Control.Concurrent
import Data.Ratio
import qualified Data.Text as T
import qualified Data.Time.Clock as TC ( UTCTime )
import Data.Time.Clock.POSIX ( posixSecondsToUTCTime, POSIXTime )
import Data.Time.Format ( defaultTimeLocale, formatTime, parseTimeM )
import qualified System.Clock as SC

import Compat.Prettyprinter as P

newtype EpochClock = EpochClock SC.TimeSpec deriving (Eq, Ord, Show)

-- | Use 'Clock' of 'Realtime'
getEpochTime :: IO EpochClock
getEpochTime = EpochClock <$> SC.getTime SC.Realtime

-- | Using this with 'getEpochTime' and 'showEpochTime' agrees with running
--
-- > /usr/local/bin/date +'%s'
toEpochSeconds :: EpochClock -> Double
toEpochSeconds (EpochClock SC.TimeSpec{sec, nsec}) = fromRational $
  fromIntegral sec + (fromIntegral nsec % 1000000000)

toEpochNanos :: EpochClock -> Integer
toEpochNanos (EpochClock timeSpec) = SC.toNanoSecs timeSpec

-- | Using this with 'getEpochTime' and 'showEpochTime' agrees with running
--
-- > /usr/local/bin/date --rfc-3339=ns
epochClockToUTCTime :: EpochClock -> TC.UTCTime
epochClockToUTCTime (EpochClock SC.TimeSpec{sec, nsec}) =
  let posixSeconds :: POSIXTime
      posixSeconds = fromRational $
        fromIntegral sec + (fromIntegral nsec % 1000000000)
  in posixSecondsToUTCTime posixSeconds

-- | For seconds, this has variable number of digits after decimal point.
-- We want to log RFC 3339 format
showUTC :: TC.UTCTime -> T.Text
showUTC = T.pack . formatTime defaultTimeLocale "%FT%X%QZ"

readUTC :: T.Text -> Maybe TC.UTCTime
readUTC = parseTimeM True defaultTimeLocale "%FT%X%QZ" . T.unpack

-- | We want to log RFC 3339 format
showEpochTime :: EpochClock -> T.Text
showEpochTime = showUTC . epochClockToUTCTime

-- -----------------------------------------------------------------------------

newtype TimePoint = TimePoint SC.TimeSpec deriving (Eq, Ord, Show)

instance P.Pretty TimePoint where
  pretty (TimePoint SC.TimeSpec{..}) = P.pretty $ "TimePoint "
    <> T.pack (show sec) <> "." <> T.justifyRight 9 '0' (T.pack (show nsec))

-- | Use 'Clock' of 'MonotonicRaw'
getTimePoint :: IO TimePoint
getTimePoint = TimePoint <$> SC.getTime SC.MonotonicRaw

newtype DiffTimePoints = DiffTimePoints SC.TimeSpec
  deriving (Eq, Ord, Show, Num)

instance P.Pretty DiffTimePoints where
  pretty (DiffTimePoints SC.TimeSpec{..}) = P.pretty $ "DiffTimePoints "
    <> T.pack (show sec) <> "." <> T.justifyRight 9 '0' (T.pack (show nsec))

addDiffTimePoints :: DiffTimePoints -> DiffTimePoints -> DiffTimePoints
addDiffTimePoints (DiffTimePoints a) (DiffTimePoints b) = DiffTimePoints (a+b)

-- | This takes the earlier (smaller) then the later (larger) 'TimePoint'
diffTimePoints :: TimePoint -> TimePoint -> DiffTimePoints
diffTimePoints (TimePoint small) (TimePoint big) =
  DiffTimePoints (big - small)

addToTimePoint :: TimePoint -> DiffTimePoints -> TimePoint
addToTimePoint (TimePoint time) (DiffTimePoints diff) =
  TimePoint (time + diff)

getElapsedTime :: TimePoint -> IO DiffTimePoints
getElapsedTime small = do
  big <- getTimePoint
  return (diffTimePoints small big)

-- | Measure the elapsed time of an IO action
elapsedTime :: IO a -> IO (DiffTimePoints, a)
elapsedTime io = do
  t0 <- getTimePoint
  a <- io
  delta <- getElapsedTime t0
  return (delta, a)

-- | We want to log milliseconds, use 'round'
toDiffMillis :: DiffTimePoints -> Int
toDiffMillis (DiffTimePoints SC.TimeSpec{sec,nsec}) =
  1000 * fromIntegral sec +
    (round :: Rational -> Int) (fromIntegral nsec % 1000000)

toDiffMicros :: DiffTimePoints -> Int
toDiffMicros (DiffTimePoints SC.TimeSpec{sec,nsec}) =
  1000000 * fromIntegral sec +
    (round :: Rational -> Int) (fromIntegral nsec % 1000)

toDiffNanos :: DiffTimePoints -> Int
toDiffNanos (DiffTimePoints SC.TimeSpec{sec,nsec}) =
  1000000000 * fromIntegral sec + fromIntegral nsec

toDiffSeconds :: DiffTimePoints -> Double
toDiffSeconds (DiffTimePoints SC.TimeSpec{sec,nsec}) =
  fromIntegral sec + 1e-9 * fromIntegral nsec

delay :: DiffTimePoints -> IO ()
delay = threadDelay . toDiffMicros

nanoseconds :: Int -> DiffTimePoints
nanoseconds n = DiffTimePoints (SC.fromNanoSecs (fromIntegral n))

seconds :: Int -> DiffTimePoints
seconds s = DiffTimePoints (SC.TimeSpec (fromIntegral s) 0)

minutes :: Int -> DiffTimePoints
minutes m = seconds (m * 60)

hours :: Int -> DiffTimePoints
hours h = minutes (h * 60)