@@ -0,0 +1,180 @@
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_)
40+import Data.Time.LocalTime (CalendarDiffTime(..))
41+import Data.Time.LocalTime (CalendarDiffTime(..))
42+import Data.ByteString(ByteString)
43+import Database.PostgreSQL.Simple.SqlQQ
45+data IntervalTestCase = IntervalTestCase
47+ , inputMonths :: Integer
48+ , inputSeconds :: NominalDiffTime
53+testInterval :: TestEnv -> Assertion
54+testInterval env@TestEnv{..} = do
58+ -- currently required for interval to work
59+ execute_ conn "SET intervalstyle TO 'iso_8601'"
61+ let milliseconds = 0.001
63+ minutes = 60 * seconds
70+ mapM (checkRoundTrip env)
79+ , inputMonths = 1 * years
85+ , inputMonths = 2 * months
92+ , inputSeconds = 3 * weeks
98+ , inputSeconds = 4 * days
104+ , inputSeconds = 5 * hours
108+ { label = "6 minutes"
110+ , inputSeconds = 6 * minutes
114+ { label = "7 seconds"
116+ , inputSeconds = 7 * seconds
120+ { label = "8 milliseconds"
122+ , inputSeconds = 8 * milliseconds
123+ , asText = "PT0.008S"
126+ { label = "combination of intervals (day-size or bigger)"
127+ , inputMonths = 2 * years + 4 * months
128+ , inputSeconds = 3 * weeks + 5 * days
129+ , asText = "P2Y4M3W5D"
132+ { label = "combination of intervals (smaller than day-size)"
134+ , inputSeconds = 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
135+ , asText = "PT18H56M23.563S"
138+ { label = "full combination of intervals"
139+ , inputMonths = 2 * years + 4 * months
140+ , inputSeconds = 3 * weeks + 5 * days + 18 * hours + 56 * minutes + 23 * seconds + 563 * milliseconds
141+ , asText = "P2Y4M3W5DT18H56M23.563S"
147+initializeTable :: TestEnv -> IO ()
148+initializeTable TestEnv{..} = withTransaction conn $ do
150+ [sql| CREATE TEMPORARY TABLE testinterval
151+ ( id serial, sample interval, PRIMARY KEY(id) ) |]
155+checkRoundTrip :: TestEnv -> IntervalTestCase -> IO ()
156+checkRoundTrip TestEnv{..} IntervalTestCase{..} = do
158+ let input = CalendarDiffTime
159+ { ctMonths = inputMonths
160+ , ctTime = inputSeconds
163+ [(returnedId :: Int, output :: CalendarDiffTime)] <- query conn [sql|
164+ INSERT INTO testinterval (sample)
166+ RETURNING id, sample
169+ assertBool ("CalendarDiffTime did not round-trip from Haskell to SQL and back (" ++ label ++ ")") $
172+ [(Only isExpectedIso)] <- query conn [sql|
173+ SELECT sample = (?)::interval
176+ |] (asText, returnedId)
178+ assertBool ("CalendarDiffTime inserted did not match ISO8601 equivalent \"" ++ asText ++ "\". (" ++ label ++ ")")