packages feed

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 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)