wireform-proto-0.2.0.0: src/Proto/Google/Protobuf/Timestamp/Util.hs
{-# LANGUAGE ScopedTypeVariables #-}
{- | Utility functions for @google.protobuf.Timestamp@.
Provides conversions to\/from standard Haskell time types ('UTCTime',
'POSIXTime'), arithmetic with 'Duration', a current-time helper,
an 'Ord' instance, and validation — mirroring utilities found in
Go (@timestamppb@), Java (@com.google.protobuf.util.Timestamps@),
and Rust (@prost-types@).
-}
module Proto.Google.Protobuf.Timestamp.Util (
-- * Conversions
timestampFromUTCTime,
timestampToUTCTime,
timestampFromPOSIXTime,
timestampToPOSIXTime,
-- * Current time
getCurrentTimestamp,
-- * Arithmetic
addDuration,
subtractTimestamps,
-- * Validation
isValidTimestamp,
-- * Comparison
compareTimestamp,
) where
import Data.Int (Int32, Int64)
import Data.Time.Clock (UTCTime)
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime, posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
import Proto.Google.Protobuf.Duration (Duration (..), defaultDuration)
import Proto.Google.Protobuf.Timestamp (Timestamp (..), defaultTimestamp)
nanosPerSecond :: Int64
nanosPerSecond = 1000000000
-- | Convert a 'UTCTime' to a 'Timestamp'.
timestampFromUTCTime :: UTCTime -> Timestamp
timestampFromUTCTime = timestampFromPOSIXTime . utcTimeToPOSIXSeconds
-- | Convert a 'Timestamp' to a 'UTCTime'.
timestampToUTCTime :: Timestamp -> UTCTime
timestampToUTCTime = posixSecondsToUTCTime . timestampToPOSIXTime
{- | Convert a 'POSIXTime' to a 'Timestamp'.
Fractional seconds are preserved at nanosecond granularity.
-}
timestampFromPOSIXTime :: POSIXTime -> Timestamp
timestampFromPOSIXTime pt =
let totalNanos = round (pt * fromIntegral nanosPerSecond) :: Int64
(s, n) = totalNanos `quotRem` nanosPerSecond
in defaultTimestamp
{ timestampSeconds = s
, timestampNanos = fromIntegral n
}
-- | Convert a 'Timestamp' to a 'POSIXTime'.
timestampToPOSIXTime :: Timestamp -> POSIXTime
timestampToPOSIXTime ts =
let s = fromIntegral (timestampSeconds ts) :: POSIXTime
n = fromIntegral (timestampNanos ts) / fromIntegral nanosPerSecond :: POSIXTime
in s + n
-- | Get the current system time as a 'Timestamp'.
getCurrentTimestamp :: IO Timestamp
getCurrentTimestamp = timestampFromPOSIXTime <$> getPOSIXTime
-- | Add a 'Duration' to a 'Timestamp', producing a new 'Timestamp'.
addDuration :: Timestamp -> Duration -> Timestamp
addDuration ts dur =
let totalNanos =
(fromIntegral (timestampSeconds ts) * nanosPerSecond + fromIntegral (timestampNanos ts))
+ (fromIntegral (durationSeconds dur) * nanosPerSecond + fromIntegral (durationNanos dur))
(s, n) = totalNanos `quotRem` nanosPerSecond
in defaultTimestamp
{ timestampSeconds = s
, timestampNanos = fromIntegral n
}
-- | Compute the 'Duration' between two 'Timestamp' values (@a - b@).
subtractTimestamps :: Timestamp -> Timestamp -> Duration
subtractTimestamps a b =
let aNanos = fromIntegral (timestampSeconds a) * nanosPerSecond + fromIntegral (timestampNanos a)
bNanos = fromIntegral (timestampSeconds b) * nanosPerSecond + fromIntegral (timestampNanos b)
diff = aNanos - bNanos
(s, n) = diff `quotRem` nanosPerSecond
in defaultDuration
{ durationSeconds = s
, durationNanos = fromIntegral n
}
{- | 'Timestamp' is valid when seconds is in @[0001-01-01T00:00:00Z, 9999-12-31T23:59:59Z]@
and nanos is in @[0, 999999999]@.
These are the bounds from the proto spec.
-}
isValidTimestamp :: Timestamp -> Bool
isValidTimestamp ts =
timestampSeconds ts >= minTimestampSeconds
&& timestampSeconds ts <= maxTimestampSeconds
&& timestampNanos ts >= 0
&& timestampNanos ts <= 999999999
where
minTimestampSeconds = -62135596800 -- 0001-01-01T00:00:00Z
maxTimestampSeconds = 253402300799 -- 9999-12-31T23:59:59Z
-- | Compare two 'Timestamp' values. Compares seconds first, then nanos.
compareTimestamp :: Timestamp -> Timestamp -> Ordering
compareTimestamp a b =
compare (timestampSeconds a) (timestampSeconds b)
<> compare (timestampNanos a) (timestampNanos b)
instance Ord Timestamp where
compare = compareTimestamp