api-tools-0.9.0.0: tests/Data/API/Test/Time.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.API.Test.Time (timeTests) where
import Data.API.Time
import Data.Time
import qualified Data.Text as T
import Test.Tasty
import qualified Test.Tasty.QuickCheck as QC
timeTests :: TestTree
timeTests = testGroup "Time" [ QC.testProperty "parseUTC/printUTC round-trip" parseUTCPrintUTC
, QC.testProperty "parseUTC/show round-trip" parseUTCShow
, QC.testProperty "parseUTC/parseUTC_old agreement" parseUTCReference
]
-- | 'printUTC' drops the fractional seconds, so check that 'parseUTC' of its
-- result is the same up to removing them.
parseUTCPrintUTC :: QC.Property
parseUTCPrintUTC = QC.forAll moreArbitraryUTCTime $ \u -> parseUTC (printUTC u) QC.=== Just (stripFractionalSeconds u)
-- | 'show' does not drop the fractional seconds, so check that 'parseUTC' of
-- its result is the original time.
parseUTCShow :: QC.Property
parseUTCShow = QC.forAll moreArbitraryUTCTime $ \u -> parseUTC (T.pack (show u)) QC.=== Just u
stripFractionalSeconds :: UTCTime -> UTCTime
stripFractionalSeconds (UTCTime d t) = UTCTime d (fromIntegral (floor t :: Integer))
-- | Test that the reference implementation and optimized implementation of
-- 'parseUTC' agree on a variety of inputs.
--
-- Note that the optimized implementation is more permissive, where out-of-range
-- times are supplied (e.g. it allows seconds to exceed 60).
parseUTCReference :: QC.Property
parseUTCReference = QC.forAll genTimestamp (\ s -> parseUTC s QC.=== parseUTC_old s)
-- | Generate a (roughly) ISO 8601 format data/time string.
genTimestamp :: QC.Gen T.Text
genTimestamp = do
day <- fromGregorian <$> QC.choose (1999,2100) <*> QC.choose (1,12) <*> QC.choose(1,31)
sep <- QC.elements [" ", "T"]
hh <- gen_hh
mm <- gen_mm
mb_ss <- gen_ss
tz <- QC.oneof [ pure "", pure "Z", gen_tz_offset ]
pure (T.pack (show day ++ sep ++ hh ++ ":" ++ mm ++ maybe "" (":" ++) mb_ss ++ tz))
where
gen_hh = pad2 . show <$> QC.choose (0,23 :: Int)
gen_mm = pad2 . show <$> QC.choose (0,59 :: Int)
gen_ss = fmap (pad2 . show) <$> QC.oneof [Just <$> QC.choose (0,59 :: Int), pure Nothing]
-- Either + or - followed by one of HH:MM or HHMM
-- (the new implementation accepts just HH too, but the old one did not)
gen_tz_offset = do
sign <- QC.elements ["+","-"]
hh <- gen_hh
sep <- QC.elements [":", ""]
mm <- gen_mm
pure (sign ++ hh ++ sep ++ mm)
pad2 [c] = ['0',c]
pad2 cs = cs
-- | The @Arbitrary UTCTime@ instance defined in 'Data.API.Time' does not
-- generate subsecond-resolution times, whereas this generator does.
moreArbitraryUTCTime :: QC.Gen UTCTime
moreArbitraryUTCTime =
QC.oneof [ QC.arbitrary
, QC.elements [ unsafeParseUTC "1965-03-10T09:23:01.001Z"
, unsafeParseUTC "1965-03-10T09:23:01.000001Z"
]
, UTCTime <$> arbitraryDay <*> arbitraryDiffTime
]
where
arbitraryDay = fromGregorian <$> QC.choose (1999,2100) <*> QC.choose (1,12) <*> QC.choose(1,31)
arbitraryDiffTime = picosecondsToDiffTime <$> QC.choose (0, 86401*10^(12 :: Int))