1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
|
{-# LANGUAGE QuasiQuotes #-}
{-
Testing strategies:
fromString . toString == id ** Todo?
toString . fromString == almost id ** Todo?
postgresql -> haskell -> postgresql * Done
haskell -> postgresql -> haskell ** Todo?
But still, what we really want to establish is that the two values
correspond; for example, a conversion that consistently added hour
when printed to a string and subtracted an hour when parsed from string
would still pass these tests.
Right now, we are checking that 1400+ timestamps in the range of 1860 to
2060 round trip from postgresql to haskell and back in 5 different timezones.
In addition to UTC, the four timezones were selected so that 2 have a positive
offset, and 2 have a negative offset, and that 2 have an offset of a
whole number of hours, while the other two do not.
It may be worth adding a few more timezones to ensure better test coverage.
We are checking a handful of selected timestamps to ensure we hit
various corner-cases in the code, in addition to 1400 timestamps randomly
generated with granularity of seconds down to microseconds in powers of ten.
-}
module Time (testTime) where
import Common
import Control.Monad(forM_, replicateM_)
import Data.Time.Compat
import Data.ByteString(ByteString)
import Database.PostgreSQL.Simple.SqlQQ
numTests :: Int
numTests = 200
testTime :: TestEnv -> Assertion
testTime env@TestEnv{..} = do
initializeTable env
execute_ conn "SET timezone TO 'UTC'"
checkRoundTrips env "1860-01-01 00:00:00+00"
execute_ conn "SET timezone TO 'America/Chicago'" -- -5:00
checkRoundTrips env "1883-11-18 12:00:00-06"
execute_ conn "SET timezone TO 'Asia/Tokyo'" -- +9:00
checkRoundTrips env "1888-01-01 00:00:00+09"
execute_ conn "SET timezone TO 'Asia/Kathmandu'" -- +5:45
checkRoundTrips env "1919-12-31 23:48:44+05:30"
execute_ conn "SET timezone TO 'America/St_Johns'" -- -3:30
checkRoundTrips env "1935-03-30 00:00:52-03:30"
initializeTable :: TestEnv -> IO ()
initializeTable TestEnv{..} = withTransaction conn $ do
execute_ conn
[sql| CREATE TEMPORARY TABLE testtime
( x serial, y timestamptz, PRIMARY KEY(x) ) |]
let test :: ByteString -> IO () = \x -> do
execute conn [sql|
INSERT INTO testtime (y) VALUES (?)
|] (Only x)
return ()
-- America/Chicago
test "1883-11-18 11:59:59-05:50:36"
test "1883-11-18 12:09:23-05:50:36"
test "1883-11-18 12:00:00-06"
-- Asia/Tokyo
test "1887-12-31 23:59:59+09:18:59"
test "1888-01-01 00:18:58+09:18:59"
test "1888-01-01 00:00:00+09"
-- Asia/Kathmandu
test "1919-12-31 23:59:59+05:41:16"
test "1919-12-31 23:48:44+05:30"
test "1985-12-31 23:59:59+05:30"
test "1986-01-01 00:15:00+05:45"
-- America/St_Johns
test "1935-03-29 23:59:59-03:30:52"
test "1935-03-30 00:00:52-03:30"
-- While the above special cases are probably a decent start, there
-- are probably more that are well worth adding to ensure better
-- coverage.
let pop :: ByteString -> Double -> IO () = \x y ->
replicateM_ numTests $ execute conn
[sql| INSERT INTO testtime (y) VALUES
('1860-01-01 00:00:00+00'::timestamptz
+ ?::interval * ROUND(RANDOM() * ?)) |] (x,y)
pop "1 microsecond" 6.3113904e15
pop "10 microseconds" 6.3113904e14
pop "100 microseconds" 6.3113904e13
pop "1 millisecond" 6.3113904e12
pop "10 milliseconds" 6.3113904e11
pop "100 milliseconds" 6.3113904e10
pop "1 second" 6.3113904e9
checkRoundTrips :: TestEnv -> ByteString -> IO ()
checkRoundTrips TestEnv{..} limit = do
yxs :: [(UTCTime, Int)] <- query_ conn [sql| SELECT y, x FROM testtime |]
forM_ yxs $ \yx -> do
res <- query conn [sql| SELECT y=? FROM testtime WHERE x=? |] yx
assertBool "UTCTime did not round-trip from SQL to Haskell and back" $
res == [Only True]
yxs :: [(ZonedTime, Int)] <- query conn [sql|
SELECT y, x FROM testtime WHERE y > ?
|] (Only limit)
forM_ yxs $ \yx -> do
res <- query conn [sql| SELECT y=? FROM testtime WHERE x=? |] yx
assertBool "ZonedTime did not round-trip from SQL to Haskell and back" $
res == [Only True]
|