{- 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. -} -- | Utilities for dealing with time in seconds. This is mostly for -- interacting with external systems that store or communicate using -- time in seconds. For higher-resolution times, see "Util.Timing". module Util.TimeSec ( Time(..) , TimeSpan(..) , TimeRange(..) , fromUTCTime , toUTCTime , addTime, subTime, timeDiff , addTimeSpan , now, timeAgo , minutes, hours, days, weeks , minute, hour, day, week , toSeconds, toMinutes, toHours, toDays , trBetween , trLength , trLast , ppUTCTime, ppDate, ppTime, ppTimeSpan, ppTimeSpanWithGranularity , PPTimeSpanGranularity(..) , showNominalDiffTime ) where import Data.Aeson import Data.Text (Text) import qualified Data.Text as Text import Data.Time.Clock import Data.Time.Clock.POSIX import Data.Time.Format import Data.Maybe (catMaybes) import TextShow import GHC.Generics import Control.DeepSeq import Data.Hashable -- | Unix timestamp. Time in seconds newtype Time = Time { timeInSeconds :: Int } deriving (Eq, Ord, Show, Generic, FromJSON, ToJSON, TextShow, Num, Enum) instance NFData Time instance Hashable Time -- | 'TimeSpan' means a time difference, duration in seconds, -- as opposed to 'Time', which is a specific point in time -- (a unix timestamp). newtype TimeSpan = TimeSpan { timeSpanInSeconds :: Int } deriving (Show, Eq, Ord) data TimeRange = TimeRange { start :: Time , end :: Time } deriving (Show, Eq) fromUTCTime :: UTCTime -> Time fromUTCTime = Time . truncate . utcTimeToPOSIXSeconds toUTCTime :: Time -> UTCTime toUTCTime (Time t) = posixSecondsToUTCTime $ fromIntegral t addTime :: Time -> TimeSpan -> Time addTime (Time t) (TimeSpan d) = Time (t + d) subTime :: Time -> TimeSpan -> Time subTime (Time t) (TimeSpan d) = Time (t - d) timeDiff :: Time -> Time -> TimeSpan timeDiff (Time e) (Time s) = TimeSpan (e - s) negateTimeSpan :: TimeSpan -> TimeSpan negateTimeSpan TimeSpan{..} = TimeSpan (- timeSpanInSeconds) addTimeSpan :: TimeSpan -> TimeSpan -> TimeSpan addTimeSpan t1 t2 = TimeSpan $ timeSpanInSeconds t1 + timeSpanInSeconds t2 now :: IO Time now = fromUTCTime <$> getCurrentTime timeAgo :: TimeSpan -> IO Time timeAgo d = (`subTime` d) <$> now minutes, hours, days, weeks :: Int -> TimeSpan minutes = TimeSpan . (60 *) hours = TimeSpan . (3600 *) days = TimeSpan . (86400 *) weeks = TimeSpan . (604800 *) minute, hour, day, week :: TimeSpan minute = minutes 1 hour = hours 1 day = days 1 week = weeks 1 trLength :: TimeRange -> TimeSpan trLength (TimeRange s e) = timeDiff e s trLast :: TimeSpan -> IO TimeRange trLast d = trBetween d (TimeSpan 0) trBetween :: TimeSpan -> TimeSpan -> IO TimeRange trBetween startDiff endDiff = do t <- now return TimeRange { start = t `subTime` startDiff , end = t `subTime` endDiff } -- Some generally useful pretty printing functions ----------------------------- ppUTCTime :: FormatTime t => t -> Text ppUTCTime t = ppDate t <> " at " <> ppTime t ppDate :: FormatTime t => t -> Text ppDate = Text.pack . formatTime defaultTimeLocale "%F" -- e.g. 2017-07-20 ppTime :: FormatTime t => t -> Text ppTime = Text.pack . formatTime defaultTimeLocale "%T UTC" -- e.g. 14:18:45 data PPTimeSpanGranularity = Day | Hour | Minute | Second secondsInOneDay :: Int secondsInOneDay = 3600 * 24 toDays, toHours, toMinutes, toSeconds :: TimeSpan -> Int toDays TimeSpan{..} = timeSpanInSeconds `div` secondsInOneDay toHours TimeSpan{..} = (timeSpanInSeconds `mod` secondsInOneDay) `div` 3600 toMinutes TimeSpan{..} = (timeSpanInSeconds `mod` 3600) `div` 60 toSeconds TimeSpan{..} = timeSpanInSeconds `mod` 60 plural :: Int -> Text -> Maybe Text plural 0 _ = Nothing plural 1 unitStr = Just $ "1 " <> unitStr plural n unitStr = Just $ showt n <> " " <> unitStr <> "s" joinTimeSectionString :: Text -> [Maybe Text] -> Text joinTimeSectionString unitStr maybeSecs = case catMaybes maybeSecs of [] -> "0" <> unitStr <> "s" s -> Text.intercalate ", " s ppTimeSpanWithGranularity :: PPTimeSpanGranularity -> TimeSpan -> Text ppTimeSpanWithGranularity g ts | timeSpanInSeconds ts < 0 = "-" <> ppTimeSpanWithGranularity g (negateTimeSpan ts) ppTimeSpanWithGranularity Day ts | timeSpanInSeconds ts < secondsInOneDay = ppTimeSpanWithGranularity Hour ts | otherwise = joinTimeSectionString "day" [ plural (toDays ts) "day" ] ppTimeSpanWithGranularity Hour ts | timeSpanInSeconds ts < 3600 = ppTimeSpanWithGranularity Minute ts | otherwise = joinTimeSectionString "hour" [ plural (toDays ts) "day" , plural (toHours ts) "hour" ] ppTimeSpanWithGranularity Minute ts | timeSpanInSeconds ts < 60 = ppTimeSpanWithGranularity Second ts | otherwise = joinTimeSectionString "minute" [ plural (toDays ts) "day" , plural (toHours ts) "hour" , plural (toMinutes ts) "minute" ] ppTimeSpanWithGranularity Second ts = joinTimeSectionString "second" [ plural (toDays ts) "day" , plural (toHours ts) "hour" , plural (toMinutes ts) "minute" , plural (toSeconds ts) "second" ] ppTimeSpan :: TimeSpan -> Text ppTimeSpan = ppTimeSpanWithGranularity Second showNominalDiffTime :: NominalDiffTime -> String showNominalDiffTime t = Text.unpack (ppTimeSpan (TimeSpan (round t)))