module TextBuilderDev.Domains.Time where

import TextBuilder
import TextBuilderDev.Domains.Padding
import TextBuilderDev.Prelude

-- | UTC time in ISO8601 format.
--
-- >>> utcTimeIso8601Timestamp (read "2021-11-24 12:11:02 UTC")
-- "2021-11-24T12:11:02Z"
utcTimeIso8601Timestamp :: UTCTime -> TextBuilder
utcTimeIso8601Timestamp :: UTCTime -> TextBuilder
utcTimeIso8601Timestamp UTCTime {Day
DiffTime
utctDay :: Day
utctDayTime :: DiffTime
utctDay :: UTCTime -> Day
utctDayTime :: UTCTime -> DiffTime
..} =
  let (Integer
year, Int
month, Int
day) = Day -> (Integer, Int, Int)
toGregorian Day
utctDay
      daySeconds :: Int
daySeconds = DiffTime -> Int
forall b. Integral b => DiffTime -> b
forall a b. (RealFrac a, Integral b) => a -> b
round DiffTime
utctDayTime
      (Int
dayMinutes, Int
second) = Int -> Int -> (Int, Int)
forall a. Integral a => a -> a -> (a, a)
divMod Int
daySeconds Int
60
      (Int
hour, Int
minute) = Int -> Int -> (Int, Int)
forall a. Integral a => a -> a -> (a, a)
divMod Int
dayMinutes Int
60
   in Int -> Int -> Int -> Int -> Int -> Int -> TextBuilder
utcTimestampInIso8601 (Integer -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
year) Int
month Int
day Int
hour Int
minute Int
second

-- |
-- General template for formatting date values according to the ISO8601 standard.
-- The format is the following:
--
-- Integrations with various time-libraries can be easily derived from that.
--
-- >>> utcTimestampInIso8601 2021 11 24 12 11 02
-- "2021-11-24T12:11:02Z"
utcTimestampInIso8601 ::
  -- | Year.
  Int ->
  -- | Month.
  Int ->
  -- | Day.
  Int ->
  -- | Hour.
  Int ->
  -- | Minute.
  Int ->
  -- | Second.
  Int ->
  TextBuilder
utcTimestampInIso8601 :: Int -> Int -> Int -> Int -> Int -> Int -> TextBuilder
utcTimestampInIso8601 Int
y Int
mo Int
d Int
h Int
mi Int
s =
  [TextBuilder] -> TextBuilder
forall a. Monoid a => [a] -> a
mconcat
    [ Int -> Int -> TextBuilder
forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal Int
4 Int
y,
      TextBuilder
"-",
      Int -> Int -> TextBuilder
forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal Int
2 Int
mo,
      TextBuilder
"-",
      Int -> Int -> TextBuilder
forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal Int
2 Int
d,
      TextBuilder
"T",
      Int -> Int -> TextBuilder
forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal Int
2 Int
h,
      TextBuilder
":",
      Int -> Int -> TextBuilder
forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal Int
2 Int
mi,
      TextBuilder
":",
      Int -> Int -> TextBuilder
forall a. Integral a => Int -> a -> TextBuilder
fixedLengthDecimal Int
2 Int
s,
      TextBuilder
"Z"
    ]

-- |
-- Time interval in seconds.
-- Directly applicable to 'DiffTime' and 'NominalDiffTime'.
--
-- The format is the following:
--
-- > DD:HH:MM:SS
--
-- >>> realFracDdHhMmSsInterval @Double 59
-- "00:00:00:59"
--
-- >>> realFracDdHhMmSsInterval @Double 90
-- "00:00:01:30"
--
-- >>> realFracDdHhMmSsInterval @Double 86401
-- "01:00:00:01"
--
-- >>> realFracDdHhMmSsInterval @Double (356 * 86400)
-- "356:00:00:00"
{-# INLINEABLE realFracDdHhMmSsInterval #-}
realFracDdHhMmSsInterval :: (RealFrac seconds) => seconds -> TextBuilder
realFracDdHhMmSsInterval :: forall seconds. RealFrac seconds => seconds -> TextBuilder
realFracDdHhMmSsInterval seconds
interval = (State Int TextBuilder -> Int -> TextBuilder)
-> Int -> State Int TextBuilder -> TextBuilder
forall a b c. (a -> b -> c) -> b -> a -> c
flip State Int TextBuilder -> Int -> TextBuilder
forall s a. State s a -> s -> a
evalState (seconds -> Int
forall b. Integral b => seconds -> b
forall a b. (RealFrac a, Integral b) => a -> b
round seconds
interval :: Int) (State Int TextBuilder -> TextBuilder)
-> State Int TextBuilder -> TextBuilder
forall a b. (a -> b) -> a -> b
$ do
  seconds <- (Int -> (Int, Int)) -> StateT Int Identity Int
forall (m :: * -> *) s a. Monad m => (s -> (a, s)) -> StateT s m a
state ((Int, Int) -> (Int, Int)
forall a b. (a, b) -> (b, a)
swap ((Int, Int) -> (Int, Int))
-> (Int -> (Int, Int)) -> Int -> (Int, Int)
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (Int -> Int -> (Int, Int)) -> Int -> Int -> (Int, Int)
forall a b c. (a -> b -> c) -> b -> a -> c
flip Int -> Int -> (Int, Int)
forall a. Integral a => a -> a -> (a, a)
divMod Int
60)
  minutes <- state (swap . flip divMod 60)
  hours <- state (swap . flip divMod 24)
  days <- get
  return
    ( mconcat
        [ padFromLeft 2 '0' (decimal days),
          ":",
          padFromLeft 2 '0' (decimal hours),
          ":",
          padFromLeft 2 '0' (decimal minutes),
          ":",
          padFromLeft 2 '0' (decimal seconds)
        ]
    )

-- | DiffTime in a compact decimal format based on 'picoseconds'.
diffTimeSeconds :: DiffTime -> TextBuilder
diffTimeSeconds :: DiffTime -> TextBuilder
diffTimeSeconds = Integer -> TextBuilder
picoseconds (Integer -> TextBuilder)
-> (DiffTime -> Integer) -> DiffTime -> TextBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. DiffTime -> Integer
diffTimeToPicoseconds

-- | Amount of picoseconds represented in a compact decimal format using suffixes.
--
-- E.g., the following is @1_230_000_000@ picoseconds or 1.23 milliseconds or 1230 microseconds:
--
-- > 1230us
picoseconds :: Integer -> TextBuilder
picoseconds :: Integer -> TextBuilder
picoseconds Integer
x =
  Integer -> TextBuilder -> TextBuilder -> TextBuilder
attemptOr Integer
1_000_000_000_000 TextBuilder
"s"
    (TextBuilder -> TextBuilder) -> TextBuilder -> TextBuilder
forall a b. (a -> b) -> a -> b
$ Integer -> TextBuilder -> TextBuilder -> TextBuilder
attemptOr Integer
1_000_000_000 TextBuilder
"ms"
    (TextBuilder -> TextBuilder) -> TextBuilder -> TextBuilder
forall a b. (a -> b) -> a -> b
$ Integer -> TextBuilder -> TextBuilder -> TextBuilder
attemptOr Integer
1_000_000 TextBuilder
"us"
    (TextBuilder -> TextBuilder) -> TextBuilder -> TextBuilder
forall a b. (a -> b) -> a -> b
$ Integer -> TextBuilder -> TextBuilder -> TextBuilder
attemptOr Integer
1_000 TextBuilder
"ns"
    (TextBuilder -> TextBuilder) -> TextBuilder -> TextBuilder
forall a b. (a -> b) -> a -> b
$ Integer -> TextBuilder
forall a. Integral a => a -> TextBuilder
decimal Integer
x
    TextBuilder -> TextBuilder -> TextBuilder
forall a. Semigroup a => a -> a -> a
<> TextBuilder
"ps"
  where
    attemptOr :: Integer -> TextBuilder -> TextBuilder -> TextBuilder
attemptOr Integer
factor TextBuilder
suffix TextBuilder
alternative =
      if Integer
x Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
divided Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
factor
        then Integer -> TextBuilder
forall a. Integral a => a -> TextBuilder
decimal Integer
divided TextBuilder -> TextBuilder -> TextBuilder
forall a. Semigroup a => a -> a -> a
<> TextBuilder
suffix
        else TextBuilder
alternative
      where
        divided :: Integer
divided = Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
div Integer
x Integer
factor