packages feed

iso8601-duration-0.1.1.0: test/Data/Time/ISO8601/DurationSpec.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Time.ISO8601.DurationSpec (main, spec) where

import           Data.Time.ISO8601.Duration
import           Data.Time
import qualified Data.ByteString.Char8 as BS8
import           Data.String
import           Test.Hspec
import           Test.Hspec.QuickCheck (prop)
import           Test.QuickCheck

main :: IO ()
main = hspec spec

spec :: Spec
spec = do

  describe "format/parse" $ do

    prop "is idempotent" $ \(dur :: Duration) ->
      counterexample (BS8.unpack (formatDuration dur)) $
        parseDuration (formatDuration dur) === Right dur

    describe "works on hand picked examples" $ do
      tryExample "P3Y6M4DT12H30M5S"
      tryExample "P6M4D"

  describe "addDuration" $ do
    itAddsDuration "P6D" (datetime 2016 11 22 13 14 0) (datetime 2016 11 28 13 14 0)
    itAddsDuration "P6DT5H4M" (datetime 2016 11 22 13 14 0) (datetime 2016 11 28 18 18 0)
    itAddsDuration "P6DT5H4M61S" (datetime 2016 11 22 13 14 0) (datetime 2016 11 28 18 19 1)
    itAddsDuration "P8W" (datetime 2016 11 22 13 14 0) (datetime 2017 1 17 13 14 0)

itAddsDuration :: Duration -> UTCTime -> UTCTime -> SpecWith (Arg Expectation)
itAddsDuration d t e = it msg $ d `addDuration` t `shouldBe` e where
  msg = show (formatDuration d) ++ " `addDuration` " ++ show t ++ " == " ++ show e
    
unsafeParseDuration :: BS8.ByteString -> Duration
unsafeParseDuration = either (const (error "unparsable")) id . parseDuration

datetime :: Integer -> Int -> Int -> Int -> Int -> Int -> UTCTime
datetime y m d h m' s = UTCTime (fromGregorian y m d) (fromIntegral (h*3600+m'*60+s))

instance IsString Duration where
  fromString = unsafeParseDuration . fromString

tryExample :: BS8.ByteString -> SpecWith (Arg Expectation)
tryExample str = it ("parses "  ++ BS8.unpack str) $
  fmap formatDuration (parseDuration str) `shouldBe` Right str


instance Arbitrary DurSecond where arbitrary = DurSecond <$> (getPositive <$> arbitrary)
instance Arbitrary DurMinute where arbitrary = DurMinute <$> (getPositive <$> arbitrary) <*> arbitrary
instance Arbitrary DurHour   where arbitrary = DurHour   <$> (getPositive <$> arbitrary) <*> arbitrary
instance Arbitrary DurTime   where arbitrary = oneof [ DurTimeHour <$> arbitrary
                                                     , DurTimeMinute <$> arbitrary
                                                     , DurTimeSecond <$> arbitrary
                                                     ]
instance Arbitrary DurDay   where arbitrary = DurDay   <$> (getPositive <$> arbitrary)
instance Arbitrary DurWeek  where arbitrary = DurWeek  <$> (getPositive <$> arbitrary)
instance Arbitrary DurMonth where arbitrary = DurMonth <$> (getPositive <$> arbitrary) <*> arbitrary
instance Arbitrary DurYear  where arbitrary = DurYear  <$> (getPositive <$> arbitrary) <*> arbitrary
instance Arbitrary DurDate  where arbitrary = oneof [ DurDateDay   <$> arbitrary <*> arbitrary
                                                    , DurDateMonth <$> arbitrary <*> arbitrary
                                                    , DurDateYear  <$> arbitrary <*> arbitrary
                                                    ]
instance Arbitrary Duration where arbitrary = oneof [ DurationDate <$> arbitrary
                                                    , DurationTime <$> arbitrary
                                                    , DurationWeek <$> arbitrary
                                                    ]