megaparsec-time-0.0.1.0: test/Text/Megaparsec/TimeSpec.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Text.Megaparsec.TimeSpec (
spec,
) where
import Control.Monad (forM_)
import Data.Bifunctor (Bifunctor (first))
import Data.Char (toLower)
import Data.Either (isLeft)
import Data.Time (
Day,
DayOfWeek (..),
TimeOfDay (..),
defaultTimeLocale,
formatTime,
fromGregorian,
secondsToNominalDiffTime,
)
import Data.Void (Void)
import Test.Hspec (
Spec,
context,
describe,
it,
shouldBe,
shouldSatisfy,
)
import Test.QuickCheck (
Arbitrary (..),
Gen,
elements,
forAll,
property,
suchThat,
)
import Text.Megaparsec (Parsec, errorBundlePretty, runParser)
import Text.Megaparsec.Time (
dateParser,
dayParser,
durationParser,
gregorianDayParser,
hoursParser,
minutesParser,
secondsParser,
timeParser,
)
import Text.Printf (printf)
instance Arbitrary TimeOfDay where
arbitrary =
( TimeOfDay . (`mod` 24) . abs
<$> arbitrary
)
<*> ((`mod` 60) . abs <$> arbitrary)
<*> pure 0
instance Arbitrary DayOfWeek where
arbitrary = elements [Monday .. Sunday]
instance Arbitrary Day where
arbitrary =
fromGregorian
<$> elements [1970 .. 2100]
<*> elements [1 .. 12]
<*> arbitrary
positive
:: Num a
=> Arbitrary a
=> Gen a
positive = abs <$> arbitrary
parseOrPrettyError
:: Parsec Void String a
-> String
-> Either String a
parseOrPrettyError p = first errorBundlePretty . runParser p "test"
spec :: Spec
spec = do
describe "date" $ do
it "time only" $
parseOrPrettyError dateParser "11:03"
`shouldBe` Right (Nothing, TimeOfDay 11 3 0)
context "with day" $ do
it "short" $
parseOrPrettyError dateParser "tue 11:03"
`shouldBe` Right (Just (Right Tuesday), TimeOfDay 11 3 0)
it "long" $
parseOrPrettyError dateParser "tuesday 11:03"
`shouldBe` Right (Just (Right Tuesday), TimeOfDay 11 3 0)
context "with rel day" $ do
it "positive" $
parseOrPrettyError dateParser "+1 11:03"
`shouldBe` Right (Just (Left 1), TimeOfDay 11 3 0)
it "negative" $
parseOrPrettyError dateParser "-1 11:03"
`shouldBe` Right (Just (Left (-1)), TimeOfDay 11 3 0)
it "with yesterday" $
parseOrPrettyError dateParser "yesterday 11:03"
`shouldBe` Right (Just (Left (-1)), TimeOfDay 11 3 0)
describe "day" $ do
let weekday d = d < Saturday
forM_
[ ("full", "%A")
, ("short", "%a")
]
$ \(title, format) ->
context title $ do
it "nominal" . forAll (arbitrary `suchThat` weekday) $ \d ->
parseOrPrettyError dayParser (formatTime defaultTimeLocale format d)
`shouldBe` Right (Right d)
it "lowercase" . forAll (arbitrary `suchThat` weekday) $ \d ->
parseOrPrettyError
dayParser
(fmap toLower (formatTime defaultTimeLocale format d))
`shouldBe` Right (Right d)
it "weekend" . forAll (arbitrary `suchThat` (not . weekday)) $ \d ->
parseOrPrettyError dayParser (formatTime defaultTimeLocale format d)
`shouldSatisfy` isLeft
context "tomorrow" $ do
it "capitalized" $
parseOrPrettyError dayParser "Tomorrow" `shouldBe` Right (Left 1)
it "lowercase" $
parseOrPrettyError dayParser "tomorrow" `shouldBe` Right (Left 1)
it "future day" . forAll (abs <$> arbitrary) $ \x ->
parseOrPrettyError dayParser (printf "+%d" x) `shouldBe` Right (Left x)
it "invalid" . forAll (negate . (+ 1) . abs <$> arbitrary) $ \x ->
parseOrPrettyError dayParser (printf "+%d" (x :: Int)) `shouldSatisfy` isLeft
describe "duration" $ do
it "hours" . forAll positive $ \h ->
parseOrPrettyError hoursParser (printf "%dh" h)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger h * 3600))
it "minutes" . forAll positive $ \m ->
parseOrPrettyError minutesParser (printf "%dm" m)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger m * 60))
context "seconds" $ do
it "raw" . forAll positive $ \s ->
parseOrPrettyError secondsParser (printf "%d" s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger s))
it "with suffix" . forAll positive $ \s ->
parseOrPrettyError secondsParser (printf "%ds" s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger s))
context "duration" $ do
context "hours" $ do
it "hms" . forAll ((,,) <$> positive <*> positive <*> positive) $ \(h, m, s) ->
parseOrPrettyError durationParser (printf "%dh %dm %ds" h m s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger (((h * 60 + m) * 60) + s)))
it "hm" . forAll ((,) <$> positive <*> positive) $ \(h, m) ->
parseOrPrettyError durationParser (printf "%dh %dm" h m)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger ((h * 60 + m) * 60)))
it "hs" . forAll ((,) <$> positive <*> positive) $ \(h, s) ->
parseOrPrettyError durationParser (printf "%dh %ds" h s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger (h * 3600 + s)))
context "minutes" $ do
it "ms" . forAll ((,) <$> positive <*> positive) $ \(m, s) ->
parseOrPrettyError durationParser (printf "%dm%ds" m s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger (m * 60 + s)))
it "m" . forAll positive $ \m ->
parseOrPrettyError durationParser (printf "%dm" m)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger (m * 60)))
context "seconds" $ do
it "s" . forAll positive $ \s ->
parseOrPrettyError durationParser (printf "%ds" s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger s))
it "no suffix" . forAll positive $ \s ->
parseOrPrettyError durationParser (show s)
`shouldBe` Right (secondsToNominalDiffTime (fromInteger s))
describe "gregorian day" $ do
forM_ ["%F", "%d/%m/%Y"] $ \format ->
it format . property $ \d ->
parseOrPrettyError gregorianDayParser (formatTime defaultTimeLocale format d)
`shouldBe` Right d
describe "time" $ do
it "invalid hours" . forAll (arbitrary `suchThat` (\h -> h > 23 && h < 100)) $ \h ->
parseOrPrettyError timeParser (printf "%02d:00" (h :: Int)) `shouldSatisfy` isLeft
it "invalid hours" . forAll (arbitrary `suchThat` (\m -> m > 59 && m < 100)) $ \m ->
parseOrPrettyError timeParser (printf "00:%02d" (m :: Int)) `shouldSatisfy` isLeft
it "valid" . property $ \t ->
parseOrPrettyError timeParser (formatTime defaultTimeLocale "%R" t)
`shouldBe` Right t