@@ -0,0 +1,176 @@
1+{-# LANGUAGE QuasiQuotes #-}
7+fromString . toString == id ** Todo?
9+toString . fromString == almost id ** Todo?
11+postgresql -> haskell -> postgresql * Done
13+haskell -> postgresql -> haskell ** Todo?
15+But still, what we really want to establish is that the two values
16+correspond; for example, a conversion that consistently added hour
17+when printed to a string and subtracted an hour when parsed from string
18+would still pass these tests.
21+Right now, we are checking that 1400+ timestamps in the range of 1860 to
22+2060 round trip from postgresql to haskell and back in 5 different timezones.
23+In addition to UTC, the four timezones were selected so that 2 have a positive
24+offset, and 2 have a negative offset, and that 2 have an offset of a
25+whole number of hours, while the other two do not.
27+It may be worth adding a few more timezones to ensure better test coverage.
29+We are checking a handful of selected timestamps to ensure we hit
30+various corner-cases in the code, in addition to 1400 timestamps randomly
31+generated with granularity of seconds down to microseconds in powers of ten.
35+module Interval (testInterval) where
38+import Control.Monad(forM_, replicateM_)
39+import Data.Time.Compat
40+import Data.Time.LocalTime.Compat (CalendarDiffTime(..))
41+import Data.ByteString(ByteString)
42+import Database.PostgreSQL.Simple.SqlQQ
44+data IntervalTestCase = IntervalTestCase
46+ , inputMonths :: Integer
47+ , inputSeconds :: NominalDiffTime
52+testInterval :: TestEnv -> Assertion
53+testInterval env@TestEnv{..} = do
57+ let milliseconds = 0.001
59+ minutes = 60 * seconds
66+ mapM (checkRoundTrip env)
75+ , inputMonths = 1 * years
81+ , inputMonths = 2 * months
88+ , inputSeconds = 3 * weeks
94+ , inputSeconds = 4 * days
100+ , inputSeconds = 5 * hours
104+ { label = "6 minutes"
106+ , inputSeconds = 6 * minutes
110+ { label = "7 seconds"
112+ , inputSeconds = 7 * seconds
116+ { label = "8 milliseconds"
118+ , inputSeconds = 8 * milliseconds
119+ , asText = "PT0.008S"
122+ { label = "combination of intervals (day-size or bigger)"
123+ , inputMonths = 2 * years + 4 * months
124+ , inputSeconds = 3 * weeks + 5 * days
125+ , asText = "P2Y4M3W5D"
128+ { label = "combination of intervals (smaller than day-size)"
130+ , inputSeconds = 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
131+ , asText = "PT18H56M23.563S"
134+ { label = "full combination of intervals"
135+ , inputMonths = 2 * years + 4 * months
136+ , inputSeconds = 3 * weeks + 5 * days + 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
137+ , asText = "P2Y4M3W5DT18H56M23.563S"
143+initializeTable :: TestEnv -> IO ()
144+initializeTable TestEnv{..} = withTransaction conn $ do
146+ [sql| CREATE TEMPORARY TABLE testinterval
147+ ( id serial, sample interval, PRIMARY KEY(id) ) |]
151+checkRoundTrip :: TestEnv -> IntervalTestCase -> IO ()
152+checkRoundTrip TestEnv{..} IntervalTestCase{..} = do
154+ let input = CalendarDiffTime
155+ { ctMonths = inputMonths
156+ , ctTime = inputSeconds
159+ [(returnedId :: Int, output :: CalendarDiffTime)] <- query conn [sql|
160+ INSERT INTO testinterval (sample)
162+ RETURNING id, sample
165+ assertBool ("CalendarDiffTime did not round-trip from Haskell to SQL and back (" ++ label ++ ")") $
168+ [(Only isExpectedIso)] <- query conn [sql|
169+ SELECT sample = (?)::interval
172+ |] (asText, returnedId)
174+ assertBool ("CalendarDiffTime inserted did not match ISO8601 equivalent \"" ++ asText ++ "\". (" ++ label ++ ")")