Skip to content

Commit f267ffd

Browse files
committed
feat: add time parsers
1 parent 95d155c commit f267ffd

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

megaparsec-utils.cabal

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extra-source-files:
1919
library
2020
exposed-modules:
2121
Lib
22+
Text.Megaparsec.Time
2223
Text.Megaparsec.Utils
2324
other-modules:
2425
Paths_megaparsec_utils
@@ -27,15 +28,18 @@ library
2728
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
2829
build-depends:
2930
base >=4.7 && <5
31+
, extra
3032
, megaparsec
3133
, parser-combinators
34+
, time
3235
, uuid
3336
default-language: Haskell2010
3437

3538
test-suite megaparsec-utils-test
3639
type: exitcode-stdio-1.0
3740
main-is: Spec.hs
3841
other-modules:
42+
Text.Megaparsec.TimeSpec
3943
Text.Megaparsec.UtilsSpec
4044
Paths_megaparsec_utils
4145
hs-source-dirs:
@@ -44,9 +48,11 @@ test-suite megaparsec-utils-test
4448
build-depends:
4549
QuickCheck
4650
, base >=4.7 && <5
51+
, extra
4752
, hspec
4853
, megaparsec
4954
, megaparsec-utils
5055
, parser-combinators
56+
, time
5157
, uuid
5258
default-language: Haskell2010

package.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ description: Utilities for the Megaparsec library.
1313

1414
dependencies:
1515
- base >= 4.7 && < 5
16+
- extra
1617
- megaparsec
1718
- parser-combinators
19+
- time
1820
- uuid
1921

2022
ghc-options:

src/Text/Megaparsec/Time.hs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
3+
module Text.Megaparsec.Time
4+
( dayParser
5+
, timeParser
6+
) where
7+
8+
import Control.Monad (replicateM, void)
9+
import Control.Monad.Combinators (choice, some)
10+
import Data.Functor (($>))
11+
import Data.Time (DayOfWeek (..), TimeOfDay (..))
12+
import Data.Void (Void)
13+
import Text.Megaparsec (Parsec, try)
14+
import Text.Megaparsec.Char (char, digitChar, string')
15+
16+
dayParser :: Parsec Void String (Either Int DayOfWeek)
17+
dayParser = choice
18+
[ Right <$> shortDay
19+
, Right <$> longDay
20+
, Left <$> (try (string' "tomorrow") $> 1)
21+
, Right <$> absoluteDay
22+
, Left <$> relativeDay
23+
]
24+
where shortDay = choice $ map (ciString (take 3 . show)) weekDays
25+
longDay = choice $ map (ciString show) weekDays
26+
ciString f d = try (string' (f d)) $> d
27+
weekDays = [Monday .. Friday]
28+
absoluteDay = toEnum . read <$> try (some digitChar)
29+
relativeDay = char '+' >> read <$> try (some digitChar)
30+
31+
timeParser :: Parsec Void String TimeOfDay
32+
timeParser = do
33+
h <- read <$> replicateM 2 digitChar
34+
void $ char ':'
35+
m <- read <$> replicateM 2 digitChar
36+
37+
return $ TimeOfDay h m 0

test/Text/Megaparsec/TimeSpec.hs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+
module Text.Megaparsec.TimeSpec
4+
( spec
5+
) where
6+
7+
import Control.Monad (forM_)
8+
import Data.Either (isLeft)
9+
import Data.List.Extra (lower)
10+
import Data.Time (DayOfWeek (..), TimeOfDay (..),
11+
defaultTimeLocale, formatTime)
12+
import Test.Hspec (Spec, context, describe, it, shouldBe,
13+
shouldSatisfy)
14+
import Test.QuickCheck (Arbitrary (..), elements, forAll,
15+
property, suchThat)
16+
import Text.Megaparsec (runParser)
17+
import Text.Megaparsec.Time (dayParser, timeParser)
18+
import Text.Printf (printf)
19+
20+
instance Arbitrary TimeOfDay where
21+
arbitrary = TimeOfDay
22+
<$> ((`mod` 24) . abs <$> arbitrary)
23+
<*> ((`mod` 60) . abs <$> arbitrary)
24+
<*> pure 0
25+
26+
instance Arbitrary DayOfWeek where
27+
arbitrary = elements [Monday .. Sunday]
28+
29+
spec :: Spec
30+
spec = do
31+
describe "day" $ do
32+
let weekday d = d < Saturday
33+
34+
forM_
35+
[ ("full", "%A")
36+
, ("short", "%a")
37+
] $ \(title, format) ->
38+
context title $ do
39+
it "nominal" . forAll (arbitrary `suchThat` weekday) $ \d ->
40+
runParser dayParser "day" (formatTime defaultTimeLocale format d) `shouldBe`
41+
Right (Right d)
42+
43+
it "lowercase" . forAll (arbitrary `suchThat` weekday) $ \d ->
44+
runParser dayParser "day" (lower (formatTime defaultTimeLocale format d)) `shouldBe`
45+
Right (Right d)
46+
47+
it "weekend" . forAll (arbitrary `suchThat` (not . weekday)) $ \d ->
48+
runParser dayParser "day" (formatTime defaultTimeLocale format d) `shouldSatisfy`
49+
isLeft
50+
51+
context "tomorrow" $ do
52+
it "capitalized" $
53+
runParser dayParser "day" "Tomorrow "`shouldBe` Right (Left 1)
54+
55+
it "lowercase" $
56+
runParser dayParser "day" "tomorrow "`shouldBe` Right (Left 1)
57+
58+
it "future day" . forAll (abs <$> arbitrary) $ \x ->
59+
runParser dayParser "day" (printf "+%d" x) `shouldBe` Right (Left x)
60+
61+
it "invalid" . forAll (negate . (+1) . abs <$> arbitrary) $ \x ->
62+
runParser dayParser "day" (printf "+%d" (x :: Int)) `shouldSatisfy` isLeft
63+
64+
describe "time" $ do
65+
it "valid" . property $ \t ->
66+
runParser timeParser "time" (formatTime defaultTimeLocale "%R" t) `shouldBe`
67+
Right t

0 commit comments

Comments
 (0)