@@ -14,6 +14,7 @@ import Data.Aeson.Types
14import Data.Aeson.TH
14import Data.Aeson.TH
15import Data.Attoparsec.Number
15import Data.Attoparsec.Number
16import Data.Data (Typeable, Data)
16import Data.Data (Typeable, Data)
17+import Data.Function (on)
17import Data.Text (Text)
18import Data.Text (Text)
18import Test.Framework (Test, defaultMain, testGroup)
19import Test.Framework (Test, defaultMain, testGroup)
19import Test.Framework.Providers.QuickCheck2 (testProperty)
20import Test.Framework.Providers.QuickCheck2 (testProperty)
@@ -25,8 +26,10 @@ import qualified Data.ByteString.Lazy.Char8 as L
25import qualified Data.Text as T
26import qualified Data.Text as T
26import qualified Data.Map as Map
27import qualified Data.Map as Map
27import qualified Data.HashMap.Strict as H
28import qualified Data.HashMap.Strict as H
29+import Data.Time.Calendar (Day)
30+import Data.Time.Clock (DiffTime, UTCTime(..), picosecondsToDiffTime)
31+import Data.Time (ZonedTime(..), LocalTime(..), TimeZone(..),
28−import Data.Time (ZonedTime(..), LocalTime(..), TimeZone(..), hoursToTimeZone, Day(..), TimeOfDay(..))
32+ hoursToTimeZone, Day(..), TimeOfDay(..))
30import Options
33import Options
32#ifdef GENERICS
35#ifdef GENERICS
@@ -57,13 +60,24 @@ genericTo _ v = G.toJSON v == toJSON v
57genericFrom :: (Eq a, Data a, ToJSON a) => a -> a -> Bool
60genericFrom :: (Eq a, Data a, ToJSON a) => a -> a -> Bool
58genericFrom _ v = G.fromJSON (toJSON v) == Success v
61genericFrom _ v = G.fromJSON (toJSON v) == Success v
60−approxEq :: Double -> Double -> Bool
63+approxEq :: (Fractional a, Ord a) => a -> a -> Bool
61−approxEq a b = a == b ||
64+approxEq = approxEqWith 1e-15 1e-15
66+approxEqWith :: (Fractional a, Ord a) => a -> a -> a -> a -> Bool
67+approxEqWith maxAbsoluteError maxRelativeError a b =
62− d < maxAbsoluteError ||
68+ a == b || d < maxAbsoluteError ||
63− d / max (abs b) (abs a) <= maxRelativeError
69+ d / max (abs b) (abs a) <= maxRelativeError
64− where d = abs (a - b)
70+ where d = abs (a - b)
65− maxAbsoluteError = 1e-15
71+
66− maxRelativeError = 1e-15
72+-- Compare equality to within a millisecond, allowing for rounding
73+-- error (ECMA 262 requires milliseconds to rounded to zero, not
74+-- rounded to nearest).
75+approxEqUTC :: UTCTime -> UTCTime -> Bool
76+approxEqUTC a b = ((==) `on` utctDay) a b &&
77+ (approxEqWith 1 1 `on` ((* 1e3) . utctDayTime)) a b
79+approxEqNet :: DotNetTime -> DotNetTime -> Bool
80+approxEqNet (DotNetTime a) (DotNetTime b) = approxEqUTC a b
68toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a) => a -> Bool
82toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a) => a -> Bool
69toFromJSON x = case fromJSON . toJSON $ x of
83toFromJSON x = case fromJSON . toJSON $ x of
@@ -297,6 +311,18 @@ instance Arbitrary TimeZone where
297 offset <- choose (0,2) :: Gen Int
311 offset <- choose (0,2) :: Gen Int
298 return $ hoursToTimeZone offset
312 return $ hoursToTimeZone offset
314+instance Arbitrary Day where
315+ arbitrary = ModifiedJulianDay `liftM` arbitrary
317+instance Arbitrary DiffTime where
318+ arbitrary = picosecondsToDiffTime `liftM` choose (0, 86400000000000000)
320+instance Arbitrary UTCTime where
321+ arbitrary = liftM2 UTCTime arbitrary arbitrary
323+instance Arbitrary DotNetTime where
324+ arbitrary = DotNetTime `liftM` arbitrary
300instance Arbitrary ZonedTime where
326instance Arbitrary ZonedTime where
301 arbitrary = liftM2 ZonedTime arbitrary arbitrary
327 arbitrary = liftM2 ZonedTime arbitrary arbitrary
@@ -345,6 +371,8 @@ tests = [
345 , testProperty "String" $ roundTripEq (""::String)
371 , testProperty "String" $ roundTripEq (""::String)
346 , testProperty "Text" $ roundTripEq T.empty
372 , testProperty "Text" $ roundTripEq T.empty
347 , testProperty "Foo" $ roundTripEq (undefined::Foo)
373 , testProperty "Foo" $ roundTripEq (undefined::Foo)
374+ , testProperty "DotNetTime" $ roundTrip approxEqNet undefined
375+ , testProperty "UTCTime" $ roundTrip approxEqUTC undefined
348 , testProperty "ZonedTime" $ roundTripEq (undefined::ZonedTime)
376 , testProperty "ZonedTime" $ roundTripEq (undefined::ZonedTime)
350 testGroup "toFromJSON" [
378 testGroup "toFromJSON" [