File: Time.hs

package info (click to toggle)
haskell-postgresql-simple 0.7.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 564 kB
  • sloc: haskell: 6,438; makefile: 2
file content (119 lines) | stat: -rw-r--r-- 4,320 bytes parent folder | download | duplicates (2)
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]