@@ -12,19 +12,27 @@
13module Database.PostgreSQL.Simple.Time.Implementation where
13module Database.PostgreSQL.Simple.Time.Implementation where
15−import Prelude hiding (take)
15+import Prelude hiding (take, (++))
16−--import Blaze.ByteString.Builder(fromByteString)
16+import Blaze.ByteString.Builder(Builder, fromByteString)
17−--import Blaze.ByteString.Builder.Char8(fromString, fromChar)
17+import Blaze.ByteString.Builder.Char8(fromChar)
18+import Blaze.Text.Int(integral)
18import Control.Arrow((***))
19import Control.Arrow((***))
19import Control.Applicative
20import Control.Applicative
20import Control.Monad(when)
21import Control.Monad(when)
21import Data.Bits((.&.))
22import Data.Bits((.&.))
22import qualified Data.ByteString as B
23import qualified Data.ByteString as B
23−import Data.ByteString.Internal (c2w)
24+import Data.ByteString.Internal (c2w, w2c)
24import Data.Time hiding (getTimeZone, getZonedTime)
25import Data.Time hiding (getTimeZone, getZonedTime)
25import Data.Typeable
26import Data.Typeable
26import Data.Word(Word8)
27import Data.Word(Word8)
27import qualified Data.Attoparsec.Char8 as A
28import qualified Data.Attoparsec.Char8 as A
29+import Data.Monoid(Monoid(..))
30+import Data.Fixed (Pico)
33+(++) :: Monoid a => a -> a -> a
29data Unbounded a
37data Unbounded a
30 = NegInfinity
38 = NegInfinity
@@ -169,3 +177,103 @@ digits msg = do
169 then return $! (10 * digit (c2w x) + digit (c2w y))
177 then return $! (10 * digit (c2w x) + digit (c2w y))
170 else fail (msg ++ " is not 2 digits")
178 else fail (msg ++ " is not 2 digits")
171{-# INLINE digits #-}
179{-# INLINE digits #-}
181+dayToBuilder :: Day -> Builder
182+dayToBuilder (toGregorian -> (y,m,d)) = do
183+ pad4 y ++ fromChar '-' ++ pad2 m ++ fromChar '-' ++ pad2 d
185+timeOfDayToBuilder :: TimeOfDay -> Builder
186+timeOfDayToBuilder (TimeOfDay h m s) = do
187+ pad2 h ++ fromChar ':' ++ pad2 m ++ fromChar ':' ++ showSec s
189+ showSec s = case toMicroseconds s of
191+ (s,us) -> pad2 s ++ fromChar '.' ++ showMicroseconds us
193+timeZoneToBuilder :: TimeZone -> Builder
195+ | m == 0 = sign h ++ pad2 (abs h)
196+ | otherwise = sign h ++ pad2 (abs h) ++ fromChar ':' ++ pad2 (abs m)
198+ (h,m) = timeZoneMinutes tz `quotRem` 60
199+ sign h | h >= 0 = fromChar '+'
200+ | otherwise = fromChar '-'
202+utcTimeToBuilder :: UTCTime -> Builder
203+utcTimeToBuilder (UTCTime day time) =
204+ dayToBuilder day ++ fromChar ' '
205+ ++ timeOfDayToBuilder (timeToTimeOfDay time) ++ fromByteString "+00"
207+zonedTimeToBuilder :: ZonedTime -> Builder
208+zonedTimeToBuilder (ZonedTime localTime tz) =
209+ localTimeToBuilder localTime ++ timeZoneToBuilder tz
211+localTimeToBuilder :: LocalTime -> Builder
212+localTimeToBuilder (LocalTime day tod) =
213+ dayToBuilder day ++ fromChar ' ' ++ timeOfDayToBuilder tod
215+unboundedToBuilder :: (a -> Builder) -> (Unbounded a -> Builder)
216+unboundedToBuilder finiteToBuilder unbounded
218+ NegInfinity -> fromByteString "-infinity"
219+ Finite a -> finiteToBuilder a
220+ PosInfinity -> fromByteString "infinity"
222+utcTimestampToBuilder :: UTCTimestamp -> Builder
223+utcTimestampToBuilder = unboundedToBuilder utcTimeToBuilder
225+zonedTimestampToBuilder :: ZonedTimestamp -> Builder
226+zonedTimestampToBuilder = unboundedToBuilder zonedTimeToBuilder
228+localTimestampToBuilder :: LocalTimestamp -> Builder
229+localTimestampToBuilder = unboundedToBuilder localTimeToBuilder
231+dateToBuilder :: Date -> Builder
232+dateToBuilder = unboundedToBuilder dayToBuilder
234+toMicroseconds :: Pico -> (Int,Int)
235+-- A kludge to work around the fact that Data.Fixed isn't very fast and doesn't
236+-- give me access to the MkFixed constructor.
237+toMicroseconds p = let (x,y) = (unsafeCoerce p :: Integer)
238+ `quotRem` 1000000000000
239+ (y',r) = y `quotRem` 1000000
240+ y'' = case compare r 500000 of
244+ in ( fromIntegral x, fromIntegral y'' )
246+-- Assumes the input is within the range [0..999999]
247+showMicroseconds :: Int -> Builder
248+showMicroseconds us =
249+ case us `quotRem` 1000 of
250+ (ms, 0) -> case ms `quotRem` 100 of
252+ (a,ms) -> case ms `quotRem` 10 of
254+ (b,c) -> p a ++ p b ++ p c
255+ (ms,us) -> case ms `quotRem` 100 of
256+ (a,ms) -> case ms `quotRem` 10 of
257+ (b ,c) -> case us `quotRem` 100 of
258+ (d, 0) -> p a ++ p b ++ p c ++ p d
259+ (d,us) -> case us `quotRem` 10 of
260+ (e , 0) -> p a ++ p b ++ p c ++ p d ++ p e
261+ (e , f) -> p a ++ p b ++ p c ++ p d ++ p e ++ p f
263+-- | p assumes its input is in the range [0..9]
264+p :: Integral n => n -> Builder
265+p n = fromChar (w2c (fromIntegral (n + 48)))
268+-- | pad2 assumes its input is in the range [0..99]
269+pad2 :: Integral n => n -> Builder
270+pad2 n = let (a,b) = n `quotRem` 10 in p a ++ p b
273+-- | pad4 assumes its input is positive
274+pad4 :: (Integral n, Show n) => n -> Builder
275+pad4 n | n >= 1000 = integral n
276+ | n >= 100 = fromChar '0' ++ integral n
277+ | n >= 10 = fromByteString "00" ++ integral n
278+ | otherwise = fromByteString "000" ++ integral n