content: {-# 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
  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]
  