module Katip.Format.Time
( formatAsLogTime
, formatAsIso8601
) where
import Control.Monad.ST (ST)
import Data.Int (Int64)
import qualified Data.Text.Array as TA
import Data.Text (Text)
import Data.Text.Internal (Text(..))
import Data.Time (UTCTime(..), toGregorian, Day, DiffTime)
import Data.Word (Word16)
import Unsafe.Coerce (unsafeCoerce)
formatAsLogTime :: UTCTime -> Text
formatAsLogTime (UTCTime day time) = toText $ TA.run2 $ do
buf <- TA.new 19
_ <- writeDay buf 0 day
TA.unsafeWrite buf 10 0x20
_ <- writeTimeOfDay False buf 11 (diffTimeOfDay64 time)
return (buf, 19)
where
toText (arr, len) = Text arr 0 len
formatAsIso8601 :: UTCTime -> Text
formatAsIso8601 (UTCTime day time) = toText $ TA.run2 $ do
buf <- TA.new 33
_ <- writeDay buf 0 day
TA.unsafeWrite buf 10 0x54
next <- writeTimeOfDay True buf 11 (diffTimeOfDay64 time)
TA.unsafeWrite buf next 0x5A
return (buf, next+1)
where
toText (arr, len) = Text arr 0 len
writeDay :: TA.MArray s -> Int -> Day -> ST s Int
writeDay buf off day =
do
TA.unsafeWrite buf (off + 0) (digit y1)
TA.unsafeWrite buf (off + 1) (digit y2)
TA.unsafeWrite buf (off + 2) (digit y3)
TA.unsafeWrite buf (off + 3) (digit y4)
TA.unsafeWrite buf (off + 4) 0x2d
TA.unsafeWrite buf (off + 5) m1
TA.unsafeWrite buf (off + 6) m2
TA.unsafeWrite buf (off + 7) 0x2d
TA.unsafeWrite buf (off + 8) d1
TA.unsafeWrite buf (off + 9) d2
return (off + 10)
where
(yr,m,d) = toGregorian day
(y1, ya) = fromIntegral (abs yr) `quotRem` 1000
(y2, yb) = ya `quotRem` 100
(y3, y4) = yb `quotRem` 10
T m1 m2 = twoDigits m
T d1 d2 = twoDigits d
writeTimeOfDay :: Bool -> TA.MArray s -> Int -> TimeOfDay64 -> ST s Int
writeTimeOfDay doSubSeconds buf off (TOD hh mm ss) =
do
TA.unsafeWrite buf off h1
TA.unsafeWrite buf (off + 1) h2
TA.unsafeWrite buf (off + 2) 0x3A
TA.unsafeWrite buf (off + 3) m1
TA.unsafeWrite buf (off + 4) m2
TA.unsafeWrite buf (off + 5) 0x3A
TA.unsafeWrite buf (off + 6) s1
TA.unsafeWrite buf (off + 7) s2
if doSubSeconds && frac /= 0
then writeFracSeconds buf (off + 8) frac
else return (off + 8)
where
T h1 h2 = twoDigits hh
T m1 m2 = twoDigits mm
T s1 s2 = twoDigits (fromIntegral real)
(real,frac) = ss `quotRem` pico
pico = 1000000000000
writeFracSeconds :: TA.MArray s -> Int -> Int64 -> ST s Int
writeFracSeconds buf off frac =
do
TA.unsafeWrite buf off 0x2e
if mills == 0
then do
writeTrunc6 buf (off + 1) (fromIntegral mics)
else do
writeDigit6 buf (off + 1) (fromIntegral mics)
writeTrunc6 buf (off + 7) (fromIntegral mills)
where
(mics, mills) = frac `quotRem` micro
micro = 1000000
writeDigit6 :: TA.MArray s -> Int -> Int -> ST s ()
writeDigit6 buf off i =
do
writeDigit3 buf off f1
writeDigit3 buf (off+3) f2
where
(f1, f2) = i `quotRem` 1000
writeDigit3 :: TA.MArray s -> Int -> Int -> ST s ()
writeDigit3 buf off i =
do
TA.unsafeWrite buf off (digit d1)
TA.unsafeWrite buf (off+1) (digit d2)
TA.unsafeWrite buf (off+2) (digit d3)
where
(d1, d) = i `quotRem` 100
(d2, d3) = d `quotRem` 10
writeTrunc6 :: TA.MArray s -> Int -> Int -> ST s Int
writeTrunc6 buf off i =
if f2 == 0
then writeTrunc3 buf off f1
else do
writeDigit3 buf off f1
writeTrunc3 buf (off+3) f2
where
(f1, f2) = i `quotRem` 1000
writeTrunc3 :: TA.MArray s -> Int -> Int -> ST s Int
writeTrunc3 buf off i
| d == 0 = do
TA.unsafeWrite buf off (digit d1)
return (off+1)
| d3 == 0 = do
TA.unsafeWrite buf off (digit d1)
TA.unsafeWrite buf (off+1) (digit d2)
return (off+2)
| otherwise = do
TA.unsafeWrite buf off (digit d1)
TA.unsafeWrite buf (off+1) (digit d2)
TA.unsafeWrite buf (off+2) (digit d3)
return (off+3)
where
(d1, d) = i `quotRem` 100
(d2, d3) = d `quotRem` 10
data T = T !Word16 !Word16
twoDigits :: Int -> T
twoDigits a = T (digit hi) (digit lo)
where (hi,lo) = a `quotRem` 10
digit :: Int -> Word16
digit x = fromIntegral (x + 48)
data TimeOfDay64 = TOD !Int
!Int
!Int64
diffTimeOfDay64 :: DiffTime -> TimeOfDay64
diffTimeOfDay64 t = TOD (fromIntegral h) (fromIntegral m) s
where (h,mp) = fromIntegral pico `quotRem` 3600000000000000
(m,s) = mp `quotRem` 60000000000000
pico = unsafeCoerce t :: Integer