@@ -9,6 +9,7 @@ import Data.Aeson.Parser (value)
99import Data.Aeson.Types
1010import Data.Attoparsec.Number
1111import Data.Data (Typeable, Data)
12+import Data.Function (on)
1213import Data.Text (Text)
1314import Test.Framework (Test, defaultMain, testGroup)
1415import Test.Framework.Providers.QuickCheck2 (testProperty)
@@ -18,7 +19,10 @@ import qualified Data.Attoparsec.Lazy as L
1819import qualified Data.ByteString.Lazy.Char8 as L
1920import qualified Data.Text as T
2021import qualified Data.Map as Map
21−import Data.Time (ZonedTime(..), LocalTime(..), TimeZone(..), hoursToTimeZone, Day(..), TimeOfDay(..))
22+import Data.Time.Calendar (Day)
23+import Data.Time.Clock (DiffTime, UTCTime(..), picosecondsToDiffTime)
24+import Data.Time (ZonedTime(..), LocalTime(..), TimeZone(..),
25+ hoursToTimeZone, Day(..), TimeOfDay(..))
2226
2327encodeDouble :: Double -> Double -> Bool
2428encodeDouble num denom
@@ -44,13 +48,24 @@ genericTo _ v = G.toJSON v == toJSON v
4448genericFrom :: (Eq a, Data a, ToJSON a) => a -> a -> Bool
4549genericFrom _ v = G.fromJSON (toJSON v) == Success v
4650
47−approxEq :: Double -> Double -> Bool
48−approxEq a b = a == b ||
49− d < maxAbsoluteError ||
50− d / max (abs b) (abs a) <= maxRelativeError
51− where d = abs (a - b)
52− maxAbsoluteError = 1e-15
53− maxRelativeError = 1e-15
51+approxEq :: (Fractional a, Ord a) => a -> a -> Bool
52+approxEq = approxEqWith 1e-15 1e-15
53+
54+approxEqWith :: (Fractional a, Ord a) => a -> a -> a -> a -> Bool
55+approxEqWith maxAbsoluteError maxRelativeError a b =
56+ a == b || d < maxAbsoluteError ||
57+ d / max (abs b) (abs a) <= maxRelativeError
58+ where d = abs (a - b)
59+
60+-- Compare equality to within a millisecond, allowing for rounding
61+-- error (ECMA 262 requires milliseconds to rounded to zero, not
62+-- rounded to nearest).
63+approxEqUTC :: UTCTime -> UTCTime -> Bool
64+approxEqUTC a b = ((==) `on` utctDay) a b &&
65+ (approxEqWith 1 1 `on` ((* 1e3) . utctDayTime)) a b
66+
67+approxEqNet :: DotNetTime -> DotNetTime -> Bool
68+approxEqNet (DotNetTime a) (DotNetTime b) = approxEqUTC a b
5469
5570toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a) => a -> Bool
5671toFromJSON x = case fromJSON . toJSON $ x of
@@ -120,6 +135,18 @@ instance Arbitrary TimeZone where
120135 offset <- choose (0,2) :: Gen Int
121136 return $ hoursToTimeZone offset
122137
138+instance Arbitrary Day where
139+ arbitrary = ModifiedJulianDay `liftM` arbitrary
140+
141+instance Arbitrary DiffTime where
142+ arbitrary = picosecondsToDiffTime `liftM` choose (0, 86400000000000000)
143+
144+instance Arbitrary UTCTime where
145+ arbitrary = liftM2 UTCTime arbitrary arbitrary
146+
147+instance Arbitrary DotNetTime where
148+ arbitrary = DotNetTime `liftM` arbitrary
149+
123150instance Arbitrary ZonedTime where
124151 arbitrary = liftM2 ZonedTime arbitrary arbitrary
125152
@@ -168,6 +195,8 @@ tests = [
168195 , testProperty "String" $ roundTripEq (""::String)
169196 , testProperty "Text" $ roundTripEq T.empty
170197 , testProperty "Foo" $ roundTripEq (undefined::Foo)
198+ , testProperty "DotNetTime" $ roundTrip approxEqNet undefined
199+ , testProperty "UTCTime" $ roundTrip approxEqUTC undefined
171200 , testProperty "ZonedTime" $ roundTripEq (undefined::ZonedTime)
172201 ],
173202 testGroup "toFromJSON" [