packages feed

rel8-1.7.0.0: src/Rel8/Type/Builder/Time.hs

{-# language BangPatterns #-}
{-# language NumericUnderscores #-}
{-# language OverloadedStrings #-}
{-# language PatternSynonyms #-}
{-# language PartialTypeSignatures #-}
{-# language TypeApplications #-}
{-# language ViewPatterns #-}

{-# options_ghc -Wno-partial-type-signatures #-}
-- bytestring does not export Monoidal so we can't write a complete type
-- signature for 'divide'

{-# options_ghc -Wno-unused-top-binds #-}
-- GHC considers the YMD pattern unused but we use its selectors

module Rel8.Type.Builder.Time (
  calendarDiffTime,
  day,
  localTime,
  timeOfDay,
  utcTime,
) where

-- base
import Data.Char (chr)
import Data.Fixed (Fixed (MkFixed), Pico)
import Data.Int (Int32, Int64)
import Prelude hiding ((<>))

-- bytestring
import Data.ByteString.Builder (Builder, string7)
import Data.ByteString.Builder.Prim (
  BoundedPrim, condB, emptyB, liftFixedToBounded,
  FixedPrim, char8, int32Dec,
  (>$<), (>*<),
 )

-- time
import Data.Time.Calendar (Day, toGregorian)
import Data.Time.Clock (UTCTime (utctDay, utctDayTime))
import Data.Time.Format.ISO8601 (iso8601Show)
import Data.Time.LocalTime (
  CalendarDiffTime,
  LocalTime (localDay, localTimeOfDay),
  TimeOfDay (todHour, todMin, todSec),
  timeToTimeOfDay
 )


digit :: FixedPrim Int
digit = (\x -> chr (x + 48)) >$< char8


digits2 :: FixedPrim Int
digits2 = divide (`quotRem` 10) digit digit


digits3 :: FixedPrim Int
digits3 = divide (`quotRem` 10) digits2 digit


digits4 :: FixedPrim Int
digits4 = divide (`quotRem` 10) digits3 digit


frac :: BoundedPrim Int64
frac = condB (== 0) emptyB $ liftFixedToBounded (char '.') <> trunc12
  where
    trunc12 =
      divide
        (`quotRem` 1_000_000)
        (fromIntegral >$< ifZero trunc6 (liftFixedToBounded digits6))
        (fromIntegral >$< nonZero trunc6)

    digitB = liftFixedToBounded digit

    digits6 = divide (`quotRem` 10) digits5 digit
    digits5 = divide (`quotRem` 10) digits4 digit

    trunc6 = divide (`quotRem` 100_000) digitB trunc5
    trunc5 = nonZero $ divide (`quotRem` 10_000) digitB trunc4
    trunc4 = nonZero $ divide (`quotRem` 1_000) digitB trunc3
    trunc3 = nonZero $ divide (`quotRem` 100) digitB trunc2
    trunc2 = nonZero $ divide (`quotRem` 10) digitB trunc1
    trunc1 = nonZero digitB

    nonZero = ifZero emptyB
    ifZero = condB (== 0)


seconds :: BoundedPrim Pico
seconds =
  (\(MkFixed s) -> fromIntegral s `quotRem` 1_000_000_000_000) >$<
  (liftFixedToBounded (fromIntegral >$< digits2) >*< frac)


year :: BoundedPrim Int32
year = condB (>= 10000) int32Dec (liftFixedToBounded (fromIntegral >$< digits4))


day :: BoundedPrim Day
day =
  (fromIntegral . ymdYear >$< year) <>
  liftFixedToBounded
    ( char '-' <> (ymdMonth >$< digits2) <> char '-' <> (ymdDay >$< digits2)
    )


pattern YMD :: Integer -> Int -> Int -> Day
pattern YMD {ymdYear, ymdMonth, ymdDay} <-
  (toGregorian -> (ymdYear, ymdMonth, ymdDay))


timeOfDay :: BoundedPrim TimeOfDay
timeOfDay =
  liftFixedToBounded
    ( (todHour >$< digits2) <> char ':' <> (todMin >$< digits2) <> char ':'
    ) <>
  (todSec >$< seconds)


utcTime :: BoundedPrim UTCTime
utcTime =
  (utctDay >$< day) <>
  liftFixedToBounded (char ' ') <>
  (timeToTimeOfDay . utctDayTime >$< timeOfDay) <>
  liftFixedToBounded (char 'Z')


localTime :: BoundedPrim LocalTime
localTime =
  (localDay >$< day) <>
  liftFixedToBounded (char ' ') <>
  (localTimeOfDay >$< timeOfDay)


calendarDiffTime :: CalendarDiffTime -> Builder
calendarDiffTime = string7 . iso8601Show


char :: Char -> FixedPrim a
char c = (\_ -> c) >$< char8


(<>) :: _ => f a -> f a -> f a
(<>) = divide (\a -> (a, a))
infixr 6 <>


divide :: _ => (a -> (b, c)) -> f b -> f c -> f a
divide f a b = f >$< (a >*< b)