{-# LANGUAGE CPP #-}

-- | Time and memory efficient time encoding helper functions.
module Katip.Format.Time
  ( formatAsLogTime,
    formatAsIso8601,
  )
where

import Control.Monad.ST (ST)
import Data.Int (Int64)
import Data.Text (Text)
import qualified Data.Text.Array as TA
import Data.Text.Internal (Text (..))
import Data.Time (Day, DiffTime, UTCTime (..), toGregorian)
import Data.Word
#if MIN_VERSION_text(2,0,0)
  (Word8)
#else
  (Word16)
#endif
import Unsafe.Coerce (unsafeCoerce)

-- Note: All functions here are optimized to never allocate anything
-- on heap. At least on ghc 8.0.1 no extra strictness annotations are
-- seem to be needed.
--
-- Exported functions are INLINEABLE

-- | Format 'UTCTime' into a short human readable format.
--
-- >>> formatAsLogTime $ UTCTime (fromGregorian 2016 1 23) 5025.123456789012
-- "2016-01-23 01:23:45"
formatAsLogTime :: UTCTime -> Text
formatAsLogTime :: UTCTime -> Text
formatAsLogTime (UTCTime Day
day DiffTime
time) = (Array, Int) -> Text
toText forall a b. (a -> b) -> a -> b
$
  forall a. (forall s. ST s (MArray s, a)) -> (Array, a)
TA.run2 forall a b. (a -> b) -> a -> b
$ do
    MArray s
buf <- forall s. Int -> ST s (MArray s)
TA.new Int
19 -- length "2016-10-20 12:34:56"
    Int
_ <- forall s. MArray s -> Int -> Day -> ST s Int
writeDay MArray s
buf Int
0 Day
day
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
10 Word8
0x20 -- space
    Int
_ <- forall s. Bool -> MArray s -> Int -> TimeOfDay64 -> ST s Int
writeTimeOfDay Bool
False MArray s
buf Int
11 (DiffTime -> TimeOfDay64
diffTimeOfDay64 DiffTime
time)
    forall (m :: * -> *) a. Monad m => a -> m a
return (MArray s
buf, Int
19)
  where
    toText :: (Array, Int) -> Text
toText (Array
arr, Int
len) = Array -> Int -> Int -> Text
Text Array
arr Int
0 Int
len
{-# INLINEABLE formatAsLogTime #-}

-- | Format 'UTCTime' into a Iso8601 format.
--
--  Note that this function may overcommit up to 12*2 bytes, depending
--  on sub-second precision. If this is an issue, make a copy with a
--  'Data.Text.copy'.
--
-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025.123456789012
-- "2016-11-23T01:23:45.123456789012Z"
-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025.123
-- "2016-01-23T01:23:45.123Z"
-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025
-- "2016-01-23T01:23:45Z"

--
formatAsIso8601 :: UTCTime -> Text
formatAsIso8601 :: UTCTime -> Text
formatAsIso8601 (UTCTime Day
day DiffTime
time) = (Array, Int) -> Text
toText forall a b. (a -> b) -> a -> b
$
  forall a. (forall s. ST s (MArray s, a)) -> (Array, a)
TA.run2 forall a b. (a -> b) -> a -> b
$ do
    MArray s
buf <- forall s. Int -> ST s (MArray s)
TA.new Int
33 -- length "2016-10-20 12:34:56.123456789012Z"
    Int
_ <- forall s. MArray s -> Int -> Day -> ST s Int
writeDay MArray s
buf Int
0 Day
day
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
10 Word8
0x54 -- T
    Int
next <- forall s. Bool -> MArray s -> Int -> TimeOfDay64 -> ST s Int
writeTimeOfDay Bool
True MArray s
buf Int
11 (DiffTime -> TimeOfDay64
diffTimeOfDay64 DiffTime
time)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
next Word8
0x5A -- Z
    forall (m :: * -> *) a. Monad m => a -> m a
return (MArray s
buf, Int
next forall a. Num a => a -> a -> a
+ Int
1)
  where
    toText :: (Array, Int) -> Text
toText (Array
arr, Int
len) = Array -> Int -> Int -> Text
Text Array
arr Int
0 Int
len
{-# INLINEABLE formatAsIso8601 #-}

-- | Writes the @YYYY-MM-DD@ part of timestamp
writeDay :: TA.MArray s -> Int -> Day -> ST s Int
writeDay :: forall s. MArray s -> Int -> Day -> ST s Int
writeDay MArray s
buf Int
off Day
day =
  do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
0) (Int -> Word8
digit Int
y1)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) (Int -> Word8
digit Int
y2)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
2) (Int -> Word8
digit Int
y3)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
3) (Int -> Word8
digit Int
y4)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
4) Word8
0x2d -- dash
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
5) Word8
m1
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
6) Word8
m2
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
7) Word8
0x2d -- dash
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
8) Word8
d1
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
9) Word8
d2
    forall (m :: * -> *) a. Monad m => a -> m a
return (Int
off forall a. Num a => a -> a -> a
+ Int
10)
  where
    (Year
yr, Int
m, Int
d) = Day -> (Year, Int, Int)
toGregorian Day
day
    (Int
y1, Int
ya) = forall a b. (Integral a, Num b) => a -> b
fromIntegral (forall a. Num a => a -> a
abs Year
yr) forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
1000
    (Int
y2, Int
yb) = Int
ya forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
100
    (Int
y3, Int
y4) = Int
yb forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
10
    T Word8
m1 Word8
m2 = Int -> T
twoDigits Int
m
    T Word8
d1 Word8
d2 = Int -> T
twoDigits Int
d
{-# INLINE writeDay #-}

-- | Write time of day, optionally with sub seconds
writeTimeOfDay :: Bool -> TA.MArray s -> Int -> TimeOfDay64 -> ST s Int
writeTimeOfDay :: forall s. Bool -> MArray s -> Int -> TimeOfDay64 -> ST s Int
writeTimeOfDay Bool
doSubSeconds MArray s
buf Int
off (TOD Int
hh Int
mm Int64
ss) =
  do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
off Word8
h1
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) Word8
h2
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
2) Word8
0x3A -- colon
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
3) Word8
m1
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
4) Word8
m2
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
5) Word8
0x3A -- colon
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
6) Word8
s1
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
7) Word8
s2
    if Bool
doSubSeconds Bool -> Bool -> Bool
&& Int64
frac forall a. Eq a => a -> a -> Bool
/= Int64
0
      then forall s. MArray s -> Int -> Int64 -> ST s Int
writeFracSeconds MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
8) Int64
frac
      else forall (m :: * -> *) a. Monad m => a -> m a
return (Int
off forall a. Num a => a -> a -> a
+ Int
8)
  where
    T Word8
h1 Word8
h2 = Int -> T
twoDigits Int
hh
    T Word8
m1 Word8
m2 = Int -> T
twoDigits Int
mm
    T Word8
s1 Word8
s2 = Int -> T
twoDigits (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
real)
    (Int64
real, Int64
frac) = Int64
ss forall a. Integral a => a -> a -> (a, a)
`quotRem` Int64
pico
    pico :: Int64
pico = Int64
1000000000000 -- number of picoseconds  in 1 second

writeFracSeconds :: TA.MArray s -> Int -> Int64 -> ST s Int
writeFracSeconds :: forall s. MArray s -> Int -> Int64 -> ST s Int
writeFracSeconds MArray s
buf Int
off Int64
frac =
  do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
off Word8
0x2e -- period
    if Int64
mills forall a. Eq a => a -> a -> Bool
== Int64
0
      then do
        forall s. MArray s -> Int -> Int -> ST s Int
writeTrunc6 MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
mics)
      else do
        forall s. MArray s -> Int -> Int -> ST s ()
writeDigit6 MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
mics)
        forall s. MArray s -> Int -> Int -> ST s Int
writeTrunc6 MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
7) (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
mills)
  where
    (Int64
mics, Int64
mills) = Int64
frac forall a. Integral a => a -> a -> (a, a)
`quotRem` Int64
micro
    micro :: Int64
micro = Int64
1000000 -- number of microseconds in 1 second

writeDigit6 :: TA.MArray s -> Int -> Int -> ST s ()
writeDigit6 :: forall s. MArray s -> Int -> Int -> ST s ()
writeDigit6 MArray s
buf Int
off Int
i =
  do
    forall s. MArray s -> Int -> Int -> ST s ()
writeDigit3 MArray s
buf Int
off Int
f1
    forall s. MArray s -> Int -> Int -> ST s ()
writeDigit3 MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
3) Int
f2
  where
    (Int
f1, Int
f2) = Int
i forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
1000
{-# INLINE writeDigit6 #-}

writeDigit3 :: TA.MArray s -> Int -> Int -> ST s ()
writeDigit3 :: forall s. MArray s -> Int -> Int -> ST s ()
writeDigit3 MArray s
buf Int
off Int
i =
  do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
off (Int -> Word8
digit Int
d1)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) (Int -> Word8
digit Int
d2)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
2) (Int -> Word8
digit Int
d3)
  where
    (Int
d1, Int
d) = Int
i forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
100
    (Int
d2, Int
d3) = Int
d forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
10
{-# INLINE writeDigit3 #-}

writeTrunc6 :: TA.MArray s -> Int -> Int -> ST s Int
writeTrunc6 :: forall s. MArray s -> Int -> Int -> ST s Int
writeTrunc6 MArray s
buf Int
off Int
i =
  if Int
f2 forall a. Eq a => a -> a -> Bool
== Int
0
    then forall s. MArray s -> Int -> Int -> ST s Int
writeTrunc3 MArray s
buf Int
off Int
f1
    else do
      forall s. MArray s -> Int -> Int -> ST s ()
writeDigit3 MArray s
buf Int
off Int
f1
      forall s. MArray s -> Int -> Int -> ST s Int
writeTrunc3 MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
3) Int
f2
  where
    (Int
f1, Int
f2) = Int
i forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
1000
{-# INLINE writeTrunc6 #-}

writeTrunc3 :: TA.MArray s -> Int -> Int -> ST s Int
writeTrunc3 :: forall s. MArray s -> Int -> Int -> ST s Int
writeTrunc3 MArray s
buf Int
off Int
i
  | Int
d forall a. Eq a => a -> a -> Bool
== Int
0 = do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
off (Int -> Word8
digit Int
d1)
    forall (m :: * -> *) a. Monad m => a -> m a
return (Int
off forall a. Num a => a -> a -> a
+ Int
1)
  | Int
d3 forall a. Eq a => a -> a -> Bool
== Int
0 = do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
off (Int -> Word8
digit Int
d1)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) (Int -> Word8
digit Int
d2)
    forall (m :: * -> *) a. Monad m => a -> m a
return (Int
off forall a. Num a => a -> a -> a
+ Int
2)
  | Bool
otherwise = do
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf Int
off (Int -> Word8
digit Int
d1)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
1) (Int -> Word8
digit Int
d2)
    forall s. MArray s -> Int -> Word8 -> ST s ()
TA.unsafeWrite MArray s
buf (Int
off forall a. Num a => a -> a -> a
+ Int
2) (Int -> Word8
digit Int
d3)
    forall (m :: * -> *) a. Monad m => a -> m a
return (Int
off forall a. Num a => a -> a -> a
+ Int
3)
  where
    (Int
d1, Int
d) = Int
i forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
100
    (Int
d2, Int
d3) = Int
d forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
10
{-# INLINE writeTrunc3 #-}

-- Following code was adapted from aeson package.
--
-- Copyright:   (c) 2015-2016 Bryan O'Sullivan
-- License:     BSD3

data T = T
#if MIN_VERSION_text(2,0,0)
  {-# UNPACK #-} !Word8 {-# UNPACK #-} !Word8
#else
  {-# UNPACK #-} !Word16 {-# UNPACK #-} !Word16
#endif

twoDigits :: Int -> T
twoDigits :: Int -> T
twoDigits Int
a = Word8 -> Word8 -> T
T (Int -> Word8
digit Int
hi) (Int -> Word8
digit Int
lo)
  where
    (Int
hi, Int
lo) = Int
a forall a. Integral a => a -> a -> (a, a)
`quotRem` Int
10

digit :: Int ->
#if MIN_VERSION_text(2,0,0)
  Word8
#else
  Word16
#endif
digit :: Int -> Word8
digit Int
x = forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int
x forall a. Num a => a -> a -> a
+ Int
48)

data TimeOfDay64
  = TOD
      {-# UNPACK #-} !Int
      {-# UNPACK #-} !Int
      {-# UNPACK #-} !Int64

diffTimeOfDay64 :: DiffTime -> TimeOfDay64
diffTimeOfDay64 :: DiffTime -> TimeOfDay64
diffTimeOfDay64 DiffTime
t = Int -> Int -> Int64 -> TimeOfDay64
TOD (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
h) (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int64
m) Int64
s
  where
    (Int64
h, Int64
mp) = forall a b. (Integral a, Num b) => a -> b
fromIntegral Year
pico forall a. Integral a => a -> a -> (a, a)
`quotRem` Int64
3600000000000000
    (Int64
m, Int64
s) = Int64
mp forall a. Integral a => a -> a -> (a, a)
`quotRem` Int64
60000000000000
    pico :: Year
pico = forall a b. a -> b
unsafeCoerce DiffTime
t :: Integer