lib/Data/Time/Format/Parse.hs

    {-# OPTIONS -fno-warn-orphans #-}
    module Data.Time.Format.Parse
        (
        -- * UNIX-style parsing
        parseTimeM, parseTimeOrError, readSTime, readPTime,
        parseTime, readTime, readsTime,
        ParseTime(),
        -- * Locale
        module Data.Time.Format.Locale
        ) where

    import Data.Proxy
    #if MIN_VERSION_base(4,9,0)
    import Control.Monad.Fail
    import Prelude hiding (fail)
    #endif
    import Data.Char
    import Data.Time.Format.Locale
    import Text.ParserCombinators.ReadP hiding (char, string)
    import Data.Time.Clock.Internal.UniversalTime
    import Data.Time.Clock.Internal.UTCTime
    import Data.Time.Calendar.Days
    import Data.Time.LocalTime.Internal.TimeZone
    import Data.Time.LocalTime.Internal.TimeOfDay
    import Data.Time.LocalTime.Internal.LocalTime
    import Data.Time.LocalTime.Internal.ZonedTime
    import Data.Time.Format.Parse.Class
    import Data.Time.Format.Parse.Instances()

    -- | Parses a time value given a format string.
    -- Supports the same %-codes as 'formatTime', including @%-@, @%_@ and @%0@ modifiers, however padding widths are not supported.
    -- Case is not significant in the input string.
    -- Some variations in the input are accepted:
    --
    -- [@%z@] accepts any of @±HHMM@ or @±HH:MM@.
    --
    -- [@%Z@] accepts any string of letters, or any of the formats accepted by @%z@.
    --
    -- [@%0Y@] accepts exactly four digits.
    --
    -- [@%0G@] accepts exactly four digits.
    --
    -- [@%0C@] accepts exactly two digits.
    --
    -- [@%0f@] accepts exactly two digits.
    --
    -- For example, to parse a date in YYYY-MM-DD format, while allowing the month
    -- and date to have optional leading zeros (notice the @-@ modifier used for @%m@
    -- and @%d@):
    --
    -- > Prelude Data.Time> parseTimeM True defaultTimeLocale "%Y-%-m-%-d" "2010-3-04" :: Maybe Day
    -- > Just 2010-03-04
    --
    parseTimeM :: (
    #if MIN_VERSION_base(4,9,0)
        MonadFail m
    #else
        Monad m
    #endif
        ,ParseTime t) =>
                 Bool       -- ^ Accept leading and trailing whitespace?
              -> TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string.
              -> String     -- ^ Input string.
              -> m t    -- ^ Return the time value, or fail if the input could
                            -- not be parsed using the given format.
    parseTimeM acceptWS l fmt s = case parseTimeList acceptWS l fmt s of
        [t] -> return t
        []  -> fail $ "parseTimeM: no parse of " ++ show s
        _   -> fail $ "parseTimeM: multiple parses of " ++ show s

    -- | Parse a time value given a format string. Fails if the input could
    -- not be parsed using the given format. See 'parseTimeM' for details.
    parseTimeOrError :: ParseTime t =>
                 Bool       -- ^ Accept leading and trailing whitespace?
              -> TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string.
              -> String     -- ^ Input string.
              -> t          -- ^ The time value.
    parseTimeOrError acceptWS l fmt s = case parseTimeList acceptWS l fmt s of
        [t] -> t
        []  -> error $ "parseTimeOrError: no parse of " ++ show s
        _   -> error $ "parseTimeOrError: multiple parses of " ++ show s

    parseTimeList :: ParseTime t =>
                 Bool       -- ^ Accept leading and trailing whitespace?
              -> TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string
              -> String     -- ^ Input string.
              -> [t]
    parseTimeList False l fmt s = [t | (t,"") <- readSTime False l fmt s]
    parseTimeList True l fmt s = [t | (t,r) <- readSTime True l fmt s, all isSpace r]

    -- | Parse a time value given a format string.  See 'parseTimeM' for details.
    readSTime :: ParseTime t =>
                 Bool       -- ^ Accept leading whitespace?
              -> TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string
              -> ReadS t
    readSTime acceptWS l f = readP_to_S (readPTime acceptWS l f)

    -- | Parse a time value given a format string.  See 'parseTimeM' for details.
    readPTime :: ParseTime t =>
                 Bool       -- ^ Accept leading whitespace?
              -> TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string
              -> ReadP t
    readPTime False l f = readPOnlyTime l f
    readPTime True l f = (skipSpaces >> readPOnlyTime l f) <++ readPOnlyTime l f

    readPOnlyTime' :: ParseTime t => proxy t -> TimeLocale -> String -> ReadP t
    readPOnlyTime' pt l f = do
        pairs <- parseSpecifiers pt l f
        case buildTime l pairs of
            Just t -> return t
            Nothing -> pfail

    -- | Parse a time value given a format string (without allowing leading whitespace).  See 'parseTimeM' for details.
    readPOnlyTime :: ParseTime t =>
                 TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string
              -> ReadP t
    readPOnlyTime = readPOnlyTime' Proxy

    {-# DEPRECATED parseTime "use \"parseTimeM True\" instead" #-}
    parseTime :: ParseTime t =>
                 TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string.
              -> String     -- ^ Input string.
              -> Maybe t    -- ^ The time value, or 'Nothing' if the input could
                            -- not be parsed using the given format.
    parseTime = parseTimeM True

    {-# DEPRECATED readTime "use \"parseTimeOrError True\" instead" #-}
    readTime :: ParseTime t =>
                TimeLocale -- ^ Time locale.
             -> String     -- ^ Format string.
             -> String     -- ^ Input string.
             -> t          -- ^ The time value.
    readTime = parseTimeOrError True

    {-# DEPRECATED readsTime "use \"readSTime True\" instead" #-}
    readsTime :: ParseTime t =>
                 TimeLocale -- ^ Time locale.
              -> String     -- ^ Format string
              -> ReadS t
    readsTime = readSTime True

    -- * Read instances for time package types

    instance Read Day where
        readsPrec _ = readParen False $ readSTime True defaultTimeLocale "%Y-%m-%d"

    instance Read TimeOfDay where
        readsPrec _ = readParen False $ readSTime True defaultTimeLocale "%H:%M:%S%Q"

    instance Read LocalTime where
        readsPrec _ = readParen False $ readSTime True defaultTimeLocale "%Y-%m-%d %H:%M:%S%Q"

    -- | This only works for @±HHMM@ format,
    -- single-letter military time-zones,
    -- and these time-zones: \"UTC\", \"UT\", \"GMT\", \"EST\", \"EDT\", \"CST\", \"CDT\", \"MST\", \"MDT\", \"PST\", \"PDT\".
    instance Read TimeZone where
        readsPrec _ = readParen False $ readSTime True defaultTimeLocale "%Z"

    -- | This only works for a 'zonedTimeZone' in @±HHMM@ format,
    -- single-letter military time-zones,
    -- and these time-zones: \"UTC\", \"UT\", \"GMT\", \"EST\", \"EDT\", \"CST\", \"CDT\", \"MST\", \"MDT\", \"PST\", \"PDT\".
    instance Read ZonedTime where
        readsPrec n = readParen False $ \s ->
            [(ZonedTime t z, r2) | (t,r1) <- readsPrec n s, (z,r2) <- readsPrec n r1]

    instance Read UTCTime where
        readsPrec n s = [ (zonedTimeToUTC t, r) | (t,r) <- readsPrec n s ]

    instance Read UniversalTime where
        readsPrec n s = [ (localTimeToUT1 0 t, r) | (t,r) <- readsPrec n s ]