megaparsec-time 0.2.0.1 → 0.3.0.0
raw patch · 7 files changed
+505/−154 lines, 7 filesdep +bytestringdep +textdep ~megaparsec-utilsPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring, text
Dependency ranges changed: megaparsec-utils
API changes (from Hackage documentation)
+ Text.Megaparsec.Time.Byte: dateParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m (Maybe DayResult, TimeOfDay)
+ Text.Megaparsec.Time.Byte: dayParser :: (MonadParsec e s m, Token s ~ Word8) => m DayResult
+ Text.Megaparsec.Time.Byte: durationParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m NominalDiffTime
+ Text.Megaparsec.Time.Byte: gregorianDayParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m Day
+ Text.Megaparsec.Time.Byte: hoursParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m NominalDiffTime
+ Text.Megaparsec.Time.Byte: minutesParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m NominalDiffTime
+ Text.Megaparsec.Time.Byte: secondsParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m NominalDiffTime
+ Text.Megaparsec.Time.Byte: timeParser :: (MonadFail m, MonadParsec e s m, Token s ~ Word8) => m TimeOfDay
+ Text.Megaparsec.Time.Char: dateParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m (Maybe DayResult, TimeOfDay)
+ Text.Megaparsec.Time.Char: dayParser :: (MonadParsec e s m, Token s ~ Char) => m DayResult
+ Text.Megaparsec.Time.Char: durationParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
+ Text.Megaparsec.Time.Char: gregorianDayParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m Day
+ Text.Megaparsec.Time.Char: hoursParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
+ Text.Megaparsec.Time.Char: minutesParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
+ Text.Megaparsec.Time.Char: secondsParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
+ Text.Megaparsec.Time.Char: timeParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m TimeOfDay
+ Text.Megaparsec.Time.Common: mkDateParser :: MonadParsec e s m => m () -> m DayResult -> m TimeOfDay -> m (Maybe DayResult, TimeOfDay)
+ Text.Megaparsec.Time.Common: mkDayParser :: MonadParsec e s m => (Token s -> m (Token s)) -> (Char -> Token s) -> m (Token s) -> (Token s -> Char) -> m DayResult
+ Text.Megaparsec.Time.Common: mkDurationParser :: MonadParsec e s m => m () -> m NominalDiffTime -> m NominalDiffTime -> m NominalDiffTime -> m NominalDiffTime
+ Text.Megaparsec.Time.Common: mkGregorianDayParser :: (MonadFail m, MonadParsec e s m) => (Token s -> Char) -> m Day
+ Text.Megaparsec.Time.Common: mkTimeParser :: MonadFail m => m t -> m t -> (t -> Char) -> m TimeOfDay
+ Text.Megaparsec.Time.Common: type DayResult = Either Int DayOfWeek
- Text.Megaparsec.Time: dateParser :: Ord e => Parsec e String (Maybe DayResult, TimeOfDay)
+ Text.Megaparsec.Time: dateParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m (Maybe DayResult, TimeOfDay)
- Text.Megaparsec.Time: dayParser :: Ord e => Parsec e String DayResult
+ Text.Megaparsec.Time: dayParser :: (MonadParsec e s m, Token s ~ Char) => m DayResult
- Text.Megaparsec.Time: durationParser :: Ord e => Parsec e String NominalDiffTime
+ Text.Megaparsec.Time: durationParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
- Text.Megaparsec.Time: gregorianDayParser :: Ord e => Parsec e String Day
+ Text.Megaparsec.Time: gregorianDayParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m Day
- Text.Megaparsec.Time: hoursParser :: Ord e => Parsec e String NominalDiffTime
+ Text.Megaparsec.Time: hoursParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
- Text.Megaparsec.Time: minutesParser :: Ord e => Parsec e String NominalDiffTime
+ Text.Megaparsec.Time: minutesParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
- Text.Megaparsec.Time: secondsParser :: Ord e => Parsec e String NominalDiffTime
+ Text.Megaparsec.Time: secondsParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m NominalDiffTime
- Text.Megaparsec.Time: timeParser :: Ord e => Parsec e String TimeOfDay
+ Text.Megaparsec.Time: timeParser :: (MonadFail m, MonadParsec e s m, Token s ~ Char) => m TimeOfDay
Files
- CHANGELOG.md +8/−4
- megaparsec-time.cabal +15/−9
- src/Text/Megaparsec/Time.hs +12/−141
- src/Text/Megaparsec/Time/Byte.hs +129/−0
- src/Text/Megaparsec/Time/Char.hs +127/−0
- src/Text/Megaparsec/Time/Common.hs +163/−0
- test/Text/Megaparsec/TimeStreamSpec.hs +51/−0
CHANGELOG.md view
@@ -1,14 +1,18 @@ Changelog -## [2.0.1](https://github.com/drlkf/megaparsec-time/compare/2.0.0...2.0.1) (2025-07-29)+# [3.0.0](https://github.com/haskell-github-trust/megaparsec-time/compare/2.0.2...3.0.0) (2026-10-25) -# [2.0.0](https://github.com/drlkf/megaparsec-time/compare/1.0.1...2.0.0) (2025-07-28)+## [2.0.2](https://github.com/haskell-github-trust/megaparsec-time/compare/2.0.1...2.0.2) (2025-08-07) -## [1.0.1](https://github.com/drlkf/megaparsec-time/compare/1.0.0...1.0.1) (2025-07-28)+## [2.0.1](https://github.com/haskell-github-trust/megaparsec-time/compare/2.0.0...2.0.1) (2025-07-29) +# [2.0.0](https://github.com/haskell-github-trust/megaparsec-time/compare/1.0.1...2.0.0) (2025-07-28)++## [1.0.1](https://github.com/haskell-github-trust/megaparsec-time/compare/1.0.0...1.0.1) (2025-07-28)+ # 1.0.0 (2025-07-28) ### Features -* initial release ([fcab952](https://github.com/drlkf/megaparsec-time/commit/fcab952d7f6331dd8b169cb6ef70ab08edec910b))+* initial release ([fcab952](https://github.com/haskell-github-trust/megaparsec-time/commit/fcab952d7f6331dd8b169cb6ef70ab08edec910b))
megaparsec-time.cabal view
@@ -1,16 +1,16 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0.+-- This file has been generated from package.yaml by hpack version 0.39.6. -- -- see: https://github.com/sol/hpack name: megaparsec-time-version: 0.2.0.1+version: 0.3.0.0 synopsis: Parsers and utilities for the Megaparsec library. description: Common parsers and utilities to use with the Megaparsec library. category: Parsing-homepage: https://github.com/drlkf/megaparsec-time-bug-reports: https://github.com/drlkf/megaparsec-time/issues+homepage: https://github.com/haskell-github-trust/megaparsec-time+bug-reports: https://github.com/haskell-github-trust/megaparsec-time/issues author: drlkf maintainer: drlkf@drlkf.net copyright: 2025 drlkf@@ -18,20 +18,23 @@ license-file: LICENSE build-type: Simple tested-with:- GHC == 9.8+ GHC == 9.10+ , GHC == 9.8 , GHC == 9.6- , GHC == 9.4 extra-source-files: README.md CHANGELOG.md source-repository head type: git- location: https://github.com/drlkf/megaparsec-time+ location: https://github.com/haskell-github-trust/megaparsec-time library exposed-modules: Text.Megaparsec.Time+ Text.Megaparsec.Time.Byte+ Text.Megaparsec.Time.Char+ Text.Megaparsec.Time.Common other-modules: Paths_megaparsec_time hs-source-dirs:@@ -40,7 +43,7 @@ build-depends: base >=4.7 && <5 , megaparsec >=9.0 && <10- , megaparsec-utils >=0.1 && <1+ , megaparsec-utils >=0.2 && <1 , parser-combinators >=1.0 && <2 , time >=1.10 && <2 default-language: Haskell2010@@ -50,6 +53,7 @@ main-is: Spec.hs other-modules: Text.Megaparsec.TimeSpec+ Text.Megaparsec.TimeStreamSpec Paths_megaparsec_time hs-source-dirs: test@@ -57,11 +61,13 @@ build-depends: QuickCheck , base >=4.7 && <5+ , bytestring , hspec , megaparsec >=9.0 && <10 , megaparsec-time- , megaparsec-utils >=0.1 && <1+ , megaparsec-utils >=0.2 && <1 , parser-combinators >=1.0 && <2+ , text , time >=1.10 && <2 default-language: Haskell2010 build-tool-depends: hspec-discover:hspec-discover == 2.*
src/Text/Megaparsec/Time.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE FlexibleContexts #-}- -- | -- Module : Text.Megaparsec.Time -- Description : Various parsers for types related to time.@@ -8,151 +6,24 @@ -- Maintainer : drlkf@drlkf.net -- Stability : experimental ----- Parsers for types related to time to use with 'megaparsec'.+-- Re-exports of 'Text.Megaparsec.Time.Char'. New code should use+-- 'Text.Megaparsec.Time.Char' or 'Text.Megaparsec.Time.Byte' directly. module Text.Megaparsec.Time ( -- * Types DayResult, -- * Simple parsers- hoursParser,- minutesParser,- secondsParser,- timeParser,- dayParser,- gregorianDayParser,+ Char.hoursParser,+ Char.minutesParser,+ Char.secondsParser,+ Char.timeParser,+ Char.dayParser,+ Char.gregorianDayParser, -- * Composite parsers- dateParser,- durationParser,+ Char.dateParser,+ Char.durationParser, ) where -import Control.Applicative (optional, (<|>))-import Control.Monad (replicateM)-import Control.Monad.Combinators (choice, some)-import Data.Char (toLower)-import Data.Functor (($>))-import Data.Maybe (fromMaybe)-import Data.Time (- Day,- DayOfWeek (..),- NominalDiffTime,- TimeOfDay (..),- defaultTimeLocale,- makeTimeOfDayValid,- parseTimeM,- secondsToNominalDiffTime,- )-import Text.Megaparsec (Parsec, takeRest, try)-import Text.Megaparsec.Char (- char,- digitChar,- space,- space1,- string',- )-import Text.Printf (printf)-import Text.Megaparsec.Utils (posNumParser)---- | Representation of a parser result with either a number of days relative to--- the current day, or a 'DayOfWeek'.-type DayResult = Either Int DayOfWeek---- | Parse a tuple containing a day or not, and a 'TimeOfDay', separated with a--- space.-dateParser- :: Ord e- => Parsec e String (Maybe DayResult, TimeOfDay)-dateParser = (,) <$> optional (try (dayParser <* space1)) <*> timeParser---- | Parse a day using one of the following, all case-insensitive:------ * a short (3-letters) or long day name e.g @mon@ or @monday@--- * @yesterday@ or @tomorrow@--- * a day number relative to the current day i.e @+2@ is two days from today--- * an absolute number for a 'DayOfWeek', refer to its 'Num' instance for more information.-dayParser- :: Ord e- => Parsec e String DayResult-dayParser =- choice- [ Right <$> longDay- , Right <$> shortDay- , Left <$> (string' "yesterday" $> -1)- , Left <$> (string' "tomorrow" $> 1)- , Right <$> absoluteDay- , Left <$> relativeDay- ]- where- shortDay = choice $ map (ciString (fmap toLower . take 3 . show)) weekDays- longDay = choice $ map (ciString (fmap toLower . show)) weekDays- ciString f d = try (string' (f d)) $> d- weekDays = [Monday .. Friday]- sign = (char '-' $> negate) <|> (char '+' $> id)- absoluteDay = toEnum . read <$> some digitChar- relativeDay = ($) <$> sign <*> (read <$> some digitChar)---- | Parse a 'NominalDiffTime' using strings like @1h23m45s@, with all--- components being optional as long as one is present.-durationParser- :: Ord e- => Parsec e String NominalDiffTime-durationParser = try hours <|> try minutes <|> secondsParser- where- hours = do- h <- hoursParser <* space- m <- fromMaybe zero <$> optional (try minutes)- s <- fromMaybe zero <$> optional secondsParser-- return (h + m + s)-- minutes = do- m <- minutesParser <* space- s <- fromMaybe zero <$> optional secondsParser-- return (m + s)---- | Parse a Gregorian 'Day' from a @%F@ or @%d\/%m\/%Y@ format.-gregorianDayParser- :: Ord e- => Parsec e String Day-gregorianDayParser = do- s <- takeRest-- let parseTime = flip (parseTimeM False defaultTimeLocale) s-- parseTime "%F" <|> parseTime "%d/%m/%Y"---- | Parse a 'NominalDiffTime' from a number of hours from a string like @1h@.-hoursParser- :: Ord e- => Parsec e String NominalDiffTime-hoursParser = secondsToNominalDiffTime . (* 3600) <$> posNumParser <* char 'h'---- | Parse a 'NominalDiffTime' from a number of minutes from a string like @1m@.-minutesParser- :: Ord e- => Parsec e String NominalDiffTime-minutesParser = secondsToNominalDiffTime . (* 60) <$> posNumParser <* char 'm'---- | Parse a 'NominalDiffTime' from a number of seconds from a string like @1s@.-secondsParser- :: Ord e- => Parsec e String NominalDiffTime-secondsParser = secondsToNominalDiffTime <$> posNumParser <* optional (char 's')---- | Parse a 'TimeOfDay' from a string like @01:23@.-timeParser- :: Ord e- => Parsec e String TimeOfDay-timeParser = do- h <- read <$> replicateM 2 digitChar <* char ':'- m <- read <$> replicateM 2 digitChar-- maybe- (fail (printf "invalid hours or minutes %02d:%02d" h m))- pure- (makeTimeOfDayValid h m 0)---- | Zero seconds in 'NominalDiffTime'.-zero :: NominalDiffTime-zero = secondsToNominalDiffTime 0+import qualified Text.Megaparsec.Time.Char as Char+import Text.Megaparsec.Time.Common (DayResult)
+ src/Text/Megaparsec/Time/Byte.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-- |+-- Module : Text.Megaparsec.Time.Byte+-- Description : Time parsers for streams of 'Word8'.+-- Copyright : (c) drlkf, 2026+-- License : GPL-3+-- Maintainer : drlkf@drlkf.net+-- Stability : experimental+--+-- Parsers for types related to time for streams of 'Word8' (strict and lazy+-- 'Data.ByteString.ByteString').+module Text.Megaparsec.Time.Byte (+ -- * Simple parsers+ hoursParser,+ minutesParser,+ secondsParser,+ timeParser,+ dayParser,+ gregorianDayParser,++ -- * Composite parsers+ dateParser,+ durationParser,+) where++import Control.Applicative (optional)+import Data.Time (Day, NominalDiffTime, TimeOfDay, secondsToNominalDiffTime)+import Data.Word (Word8)+import Text.Megaparsec (MonadParsec, Token)+import Text.Megaparsec.Byte (char, char', digitChar, space, space1)+import Text.Megaparsec.Time.Common (+ DayResult,+ mkDateParser,+ mkDayParser,+ mkDurationParser,+ mkGregorianDayParser,+ mkTimeParser,+ )+import Text.Megaparsec.Utils.Byte (posNumParser)++fromChar :: Char -> Word8+fromChar = toEnum . fromEnum++toChar :: Word8 -> Char+toChar = toEnum . fromIntegral++-- | Parse a tuple containing a day or not, and a 'TimeOfDay', separated with a+-- space.+--+-- @since 0.3.0.0+dateParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m (Maybe DayResult, TimeOfDay)+dateParser = mkDateParser space1 dayParser timeParser++-- | Parse a day, see 'Text.Megaparsec.Time.Char.dayParser'.+--+-- @since 0.3.0.0+dayParser+ :: MonadParsec e s m+ => Token s ~ Word8+ => m DayResult+dayParser = mkDayParser char' fromChar digitChar toChar++-- | Parse a 'NominalDiffTime' using strings like @1h23m45s@, with all+-- components being optional as long as one is present.+--+-- @since 0.3.0.0+durationParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m NominalDiffTime+durationParser = mkDurationParser space hoursParser minutesParser secondsParser++-- | Parse a Gregorian 'Day' from a @%F@ or @%d\/%m\/%Y@ format.+--+-- @since 0.3.0.0+gregorianDayParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m Day+gregorianDayParser = mkGregorianDayParser toChar++-- | Parse a 'NominalDiffTime' from a number of hours from a string like @1h@.+--+-- @since 0.3.0.0+hoursParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m NominalDiffTime+hoursParser = secondsToNominalDiffTime . (* 3600) <$> posNumParser <* char (fromChar 'h')++-- | Parse a 'NominalDiffTime' from a number of minutes from a string like @1m@.+--+-- @since 0.3.0.0+minutesParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m NominalDiffTime+minutesParser = secondsToNominalDiffTime . (* 60) <$> posNumParser <* char (fromChar 'm')++-- | Parse a 'NominalDiffTime' from a number of seconds from a string like @1s@.+--+-- @since 0.3.0.0+secondsParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m NominalDiffTime+secondsParser = secondsToNominalDiffTime <$> posNumParser <* optional (char (fromChar 's'))++-- | Parse a 'TimeOfDay' from a string like @01:23@.+--+-- @since 0.3.0.0+timeParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Word8+ => m TimeOfDay+timeParser = mkTimeParser digitChar (char (fromChar ':')) toChar
+ src/Text/Megaparsec/Time/Char.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-- |+-- Module : Text.Megaparsec.Time.Char+-- Description : Time parsers for streams of 'Char'.+-- Copyright : (c) drlkf, 2024+-- License : GPL-3+-- Maintainer : drlkf@drlkf.net+-- Stability : experimental+--+-- Parsers for types related to time for streams of 'Char' ('String', 'Text',+-- lazy 'Text').+module Text.Megaparsec.Time.Char (+ -- * Simple parsers+ hoursParser,+ minutesParser,+ secondsParser,+ timeParser,+ dayParser,+ gregorianDayParser,++ -- * Composite parsers+ dateParser,+ durationParser,+) where++import Control.Applicative (optional)+import Data.Time (Day, NominalDiffTime, TimeOfDay, secondsToNominalDiffTime)+import Text.Megaparsec (MonadParsec, Token)+import Text.Megaparsec.Char (char, char', digitChar, space, space1)+import Text.Megaparsec.Time.Common (+ DayResult,+ mkDateParser,+ mkDayParser,+ mkDurationParser,+ mkGregorianDayParser,+ mkTimeParser,+ )+import Text.Megaparsec.Utils.Char (posNumParser)++-- | Parse a tuple containing a day or not, and a 'TimeOfDay', separated with a+-- space.+--+-- @since 0.3.0.0+dateParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m (Maybe DayResult, TimeOfDay)+dateParser = mkDateParser space1 dayParser timeParser++-- | Parse a day using one of the following, all case-insensitive:+--+-- * a short (3-letters) or long day name e.g @mon@ or @monday@+-- * @yesterday@ or @tomorrow@+-- * a day number relative to the current day i.e @+2@ is two days from today+-- * an absolute number for a 'Data.Time.DayOfWeek', refer to its 'Num' instance for more information.+--+-- @since 0.3.0.0+dayParser+ :: MonadParsec e s m+ => Token s ~ Char+ => m DayResult+dayParser = mkDayParser char' id digitChar id++-- | Parse a 'NominalDiffTime' using strings like @1h23m45s@, with all+-- components being optional as long as one is present.+--+-- @since 0.3.0.0+durationParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m NominalDiffTime+durationParser = mkDurationParser space hoursParser minutesParser secondsParser++-- | Parse a Gregorian 'Day' from a @%F@ or @%d\/%m\/%Y@ format.+--+-- @since 0.3.0.0+gregorianDayParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m Day+gregorianDayParser = mkGregorianDayParser id++-- | Parse a 'NominalDiffTime' from a number of hours from a string like @1h@.+--+-- @since 0.3.0.0+hoursParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m NominalDiffTime+hoursParser = secondsToNominalDiffTime . (* 3600) <$> posNumParser <* char 'h'++-- | Parse a 'NominalDiffTime' from a number of minutes from a string like @1m@.+--+-- @since 0.3.0.0+minutesParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m NominalDiffTime+minutesParser = secondsToNominalDiffTime . (* 60) <$> posNumParser <* char 'm'++-- | Parse a 'NominalDiffTime' from a number of seconds from a string like @1s@.+--+-- @since 0.3.0.0+secondsParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m NominalDiffTime+secondsParser = secondsToNominalDiffTime <$> posNumParser <* optional (char 's')++-- | Parse a 'TimeOfDay' from a string like @01:23@.+--+-- @since 0.3.0.0+timeParser+ :: MonadFail m+ => MonadParsec e s m+ => Token s ~ Char+ => m TimeOfDay+timeParser = mkTimeParser digitChar (char ':') id
+ src/Text/Megaparsec/Time/Common.hs view
@@ -0,0 +1,163 @@+{-# LANGUAGE FlexibleContexts #-}++-- |+-- Module : Text.Megaparsec.Time.Common+-- Description : Stream-agnostic time parser builders.+-- Copyright : (c) drlkf, 2024+-- License : GPL-3+-- Maintainer : drlkf@drlkf.net+-- Stability : experimental+--+-- Builders shared by 'Text.Megaparsec.Time.Char' and 'Text.Megaparsec.Time.Byte'.+module Text.Megaparsec.Time.Common (+ -- * Types+ DayResult,++ -- * Shared parser builders+ mkDateParser,+ mkDayParser,+ mkDurationParser,+ mkGregorianDayParser,+ mkTimeParser,+) where++import Control.Applicative (many, optional, (<|>))+import Control.Monad (replicateM)+import Control.Monad.Combinators (choice, some)+import Data.Foldable (traverse_)+import Data.Functor (($>))+import Data.Maybe (fromMaybe)+import Data.Time (+ Day,+ DayOfWeek (..),+ NominalDiffTime,+ TimeOfDay (..),+ defaultTimeLocale,+ makeTimeOfDayValid,+ parseTimeM,+ )+import Text.Megaparsec (MonadParsec, Token, anySingle, try)+import Text.Printf (printf)++-- | Representation of a parser result with either a number of days relative to+-- the current day, or a 'DayOfWeek'.+--+-- @since 0.3.0.0+type DayResult = Either Int DayOfWeek++-- | Build a parser for a tuple containing a day or not, and a 'TimeOfDay',+-- separated with a space.+--+-- @since 0.3.0.0+mkDateParser+ :: MonadParsec e s m+ => m ()+ -- ^ Non-empty space parser.+ -> m DayResult+ -- ^ Day parser.+ -> m TimeOfDay+ -- ^ Time parser.+ -> m (Maybe DayResult, TimeOfDay)+mkDateParser space1 day time = (,) <$> optional (try (day <* space1)) <*> time++-- | Build a case-insensitive day parser, see 'Text.Megaparsec.Time.Char.dayParser'.+--+-- @since 0.3.0.0+mkDayParser+ :: MonadParsec e s m+ => (Token s -> m (Token s))+ -- ^ Case-insensitive single-token parser.+ -> (Char -> Token s)+ -- ^ Convert a literal 'Char' to a token.+ -> m (Token s)+ -- ^ Digit parser.+ -> (Token s -> Char)+ -- ^ Convert a token to a 'Char'.+ -> m DayResult+mkDayParser char' fromChar digitChar toChar =+ choice+ [ Right <$> longDay+ , Right <$> shortDay+ , Left <$> (str "yesterday" $> -1)+ , Left <$> (str "tomorrow" $> 1)+ , Right <$> absoluteDay+ , Left <$> relativeDay+ ]+ where+ str = try . traverse_ (char' . fromChar)+ shortDay = choice $ map (dayString (take 3 . show)) weekDays+ longDay = choice $ map (dayString show) weekDays+ dayString f d = str (f d) $> d+ weekDays = [Monday .. Friday]+ sign = (str "-" $> negate) <|> (str "+" $> id)+ digits = map toChar <$> some digitChar+ absoluteDay = toEnum . read <$> digits+ relativeDay = ($) <$> sign <*> (read <$> digits)++-- | Build a 'NominalDiffTime' parser from hours, minutes and seconds parsers,+-- with all components being optional as long as one is present.+--+-- @since 0.3.0.0+mkDurationParser+ :: MonadParsec e s m+ => m ()+ -- ^ Space parser.+ -> m NominalDiffTime+ -- ^ Hours parser.+ -> m NominalDiffTime+ -- ^ Minutes parser.+ -> m NominalDiffTime+ -- ^ Seconds parser.+ -> m NominalDiffTime+mkDurationParser space hoursParser minutesParser secondsParser =+ try hours <|> try minutes <|> secondsParser+ where+ hours = do+ h <- hoursParser <* space+ m <- fromMaybe 0 <$> optional (try minutes)+ s <- fromMaybe 0 <$> optional secondsParser++ return (h + m + s)++ minutes = do+ m <- minutesParser <* space+ s <- fromMaybe 0 <$> optional secondsParser++ return (m + s)++-- | Build a Gregorian 'Day' parser from a @%F@ or @%d\/%m\/%Y@ format.+--+-- @since 0.3.0.0+mkGregorianDayParser+ :: MonadFail m+ => MonadParsec e s m+ => (Token s -> Char)+ -- ^ Convert a token to a 'Char'.+ -> m Day+mkGregorianDayParser toChar = do+ s <- map toChar <$> many anySingle++ let parseTime = flip (parseTimeM False defaultTimeLocale) s++ parseTime "%F" <|> parseTime "%d/%m/%Y"++-- | Build a 'TimeOfDay' parser from a string like @01:23@.+--+-- @since 0.3.0.0+mkTimeParser+ :: MonadFail m+ => m t+ -- ^ Digit parser.+ -> m t+ -- ^ Colon parser.+ -> (t -> Char)+ -- ^ Convert a token to a 'Char'.+ -> m TimeOfDay+mkTimeParser digitChar colon toChar = do+ h <- read . map toChar <$> replicateM 2 digitChar <* colon+ m <- read . map toChar <$> replicateM 2 digitChar++ maybe+ (fail (printf "invalid hours or minutes %02d:%02d" h m))+ pure+ (makeTimeOfDayValid h m 0)
+ test/Text/Megaparsec/TimeStreamSpec.hs view
@@ -0,0 +1,51 @@+module Text.Megaparsec.TimeStreamSpec (+ spec,+) where++import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as B+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time (DayOfWeek (..), TimeOfDay (..), fromGregorian)+import Data.Void (Void)+import Test.Hspec (Spec, describe, it, shouldBe)+import Text.Megaparsec (ParseErrorBundle, Parsec, runParser)+import qualified Text.Megaparsec.Time.Byte as Byte+import qualified Text.Megaparsec.Time.Char as Char++parseText :: Parsec Void Text a -> Text -> Either (ParseErrorBundle Text Void) a+parseText p = runParser p "test"++parseBytes+ :: Parsec Void ByteString a+ -> ByteString+ -> Either (ParseErrorBundle ByteString Void) a+parseBytes p = runParser p "test"++spec :: Spec+spec = do+ describe "Char parsers on Text" $ do+ it "dateParser" $+ parseText Char.dateParser (T.pack "Tue 11:03")+ `shouldBe` Right (Just (Right Tuesday), TimeOfDay 11 3 0)++ it "durationParser" $+ parseText Char.durationParser (T.pack "1h 2m 3s") `shouldBe` Right 3723++ it "gregorianDayParser" $+ parseText Char.gregorianDayParser (T.pack "25/10/2026")+ `shouldBe` Right (fromGregorian 2026 10 25)++ describe "Byte parsers on ByteString" $ do+ it "dateParser" $ do+ parseBytes Byte.dateParser (B.pack "Tue 11:03")+ `shouldBe` Right (Just (Right Tuesday), TimeOfDay 11 3 0)+ parseBytes Byte.dateParser (B.pack "-2 11:03")+ `shouldBe` Right (Just (Left (-2)), TimeOfDay 11 3 0)++ it "durationParser" $+ parseBytes Byte.durationParser (B.pack "1h 2m 3s") `shouldBe` Right 3723++ it "gregorianDayParser" $+ parseBytes Byte.gregorianDayParser (B.pack "2026-10-25")+ `shouldBe` Right (fromGregorian 2026 10 25)