attoparsec-iso8601 1.0.2.0 → 1.0.2.1
raw patch · 5 files changed
+232/−232 lines, 5 filesdep ~attoparsecdep ~basedep ~textnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: attoparsec, base, text, time
API changes (from Hackage documentation)
Files
- Data/Attoparsec/Time.hs +0/−174
- Data/Attoparsec/Time/Internal.hs +0/−49
- attoparsec-iso8601.cabal +9/−9
- src/Data/Attoparsec/Time.hs +174/−0
- src/Data/Attoparsec/Time/Internal.hs +49/−0
− Data/Attoparsec/Time.hs
@@ -1,174 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE ScopedTypeVariables #-}--- |--- Module: Data.Aeson.Parser.Time--- Copyright: (c) 2015-2016 Bryan O'Sullivan--- License: BSD3--- Maintainer: Bryan O'Sullivan <bos@serpentine.com>--- Stability: experimental--- Portability: portable------ Parsers for parsing dates and times.--module Data.Attoparsec.Time- (- day- , localTime- , timeOfDay- , timeZone- , utcTime- , zonedTime- , month- , quarter- ) where--import Prelude.Compat--import Control.Applicative ((<|>))-import Control.Monad (void, when)-import Data.Attoparsec.Text as A-import Data.Attoparsec.Time.Internal (toPico)-import Data.Bits ((.&.))-import Data.Char (isDigit, ord)-import Data.Fixed (Pico)-import Data.Int (Int64)-import Data.Maybe (fromMaybe)-import Data.Time.Calendar (Day, fromGregorianValid)-import Data.Time.Calendar.Quarter.Compat (Quarter, QuarterOfYear (..), fromYearQuarter)-import Data.Time.Calendar.Month.Compat (Month, fromYearMonthValid)-import Data.Time.Clock (UTCTime(..))-import qualified Data.Text as T-import qualified Data.Time.LocalTime as Local---- | Parse a date of the form @[+,-]YYYY-MM-DD@.-day :: Parser Day-day = do- absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id- y <- (decimal <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"- m <- (twoDigits <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"- d <- twoDigits <|> fail "date must be of form [+,-]YYYY-MM-DD"- maybe (fail "invalid date") return (fromGregorianValid (absOrNeg y) m d)---- | Parse a month of the form @[+,-]YYYY-MM@.-month :: Parser Month-month = do- absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id- y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"- m <- twoDigits <|> fail "month must be of form [+,-]YYYY-MM"- maybe (fail "invalid month") return (fromYearMonthValid (absOrNeg y) m)---- | Parse a quarter of the form @[+,-]YYYY-QN@.-quarter :: Parser Quarter-quarter = do- absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id- y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"- _ <- char 'q' <|> char 'Q'- q <- parseQ- return $! fromYearQuarter (absOrNeg y) q- where- parseQ = Q1 <$ char '1'- <|> Q2 <$ char '2'- <|> Q3 <$ char '3'- <|> Q4 <$ char '4'---- | Parse a two-digit integer (e.g. day of month, hour).-twoDigits :: Parser Int-twoDigits = do- a <- digit- b <- digit- let c2d c = ord c .&. 15- return $! c2d a * 10 + c2d b---- | Parse a time of the form @HH:MM[:SS[.SSS]]@.-timeOfDay :: Parser Local.TimeOfDay-timeOfDay = do- h <- twoDigits- m <- char ':' *> twoDigits- s <- option 0 (char ':' *> seconds)- if h < 24 && m < 60 && s < 61- then return (Local.TimeOfDay h m s)- else fail "invalid time"--data T = T {-# UNPACK #-} !Int {-# UNPACK #-} !Int64---- | Parse a count of seconds, with the integer part being two digits--- long.-seconds :: Parser Pico-seconds = do- real <- twoDigits- mc <- peekChar- case mc of- Just '.' -> do- t <- anyChar *> takeWhile1 isDigit- return $! parsePicos real t- _ -> return $! fromIntegral real- where- parsePicos a0 t = toPico (fromIntegral (t' * 10^n))- where T n t' = T.foldl' step (T 12 (fromIntegral a0)) t- step ma@(T m a) c- | m <= 0 = ma- | otherwise = T (m-1) (10 * a + fromIntegral (ord c) .&. 15)---- | Parse a time zone, and return 'Nothing' if the offset from UTC is--- zero. (This makes some speedups possible.)-timeZone :: Parser (Maybe Local.TimeZone)-timeZone = do- let maybeSkip c = do ch <- peekChar'; when (ch == c) (void anyChar)- maybeSkip ' '- ch <- satisfy $ \c -> c == 'Z' || c == '+' || c == '-'- if ch == 'Z'- then return Nothing- else do- h <- twoDigits- mm <- peekChar- m <- case mm of- Just ':' -> anyChar *> twoDigits- Just d | isDigit d -> twoDigits- _ -> return 0- let off | ch == '-' = negate off0- | otherwise = off0- off0 = h * 60 + m- case undefined of- _ | off == 0 ->- return Nothing- | off < -720 || off > 840 || m > 59 ->- fail "invalid time zone offset"- | otherwise ->- let !tz = Local.minutesToTimeZone off- in return (Just tz)---- | Parse a date and time, of the form @YYYY-MM-DD HH:MM[:SS[.SSS]]@.--- The space may be replaced with a @T@. The number of seconds is optional--- and may be followed by a fractional component.-localTime :: Parser Local.LocalTime-localTime = Local.LocalTime <$> day <* daySep <*> timeOfDay- where daySep = satisfy (\c -> c == 'T' || c == ' ')---- | Behaves as 'zonedTime', but converts any time zone offset into a--- UTC time.-utcTime :: Parser UTCTime-utcTime = do- lt@(Local.LocalTime d t) <- localTime- mtz <- timeZone- case mtz of- Nothing -> let !tt = Local.timeOfDayToTime t- in return (UTCTime d tt)- Just tz -> return $! Local.localTimeToUTC tz lt---- | Parse a date with time zone info. Acceptable formats:------ @YYYY-MM-DD HH:MM Z@--- @YYYY-MM-DD HH:MM:SS Z@--- @YYYY-MM-DD HH:MM:SS.SSS Z@------ The first space may instead be a @T@, and the second space is--- optional. The @Z@ represents UTC. The @Z@ may be replaced with a--- time zone offset of the form @+0000@ or @-08:00@, where the first--- two digits are hours, the @:@ is optional and the second two digits--- (also optional) are minutes.-zonedTime :: Parser Local.ZonedTime-zonedTime = Local.ZonedTime <$> localTime <*> (fromMaybe utc <$> timeZone)--utc :: Local.TimeZone-utc = Local.TimeZone 0 False ""
− Data/Attoparsec/Time/Internal.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE NoImplicitPrelude #-}--- |--- Module: Data.Aeson.Internal.Time--- Copyright: (c) 2015-2016 Bryan O'Sullivan--- License: BSD3--- Maintainer: Bryan O'Sullivan <bos@serpentine.com>--- Stability: experimental--- Portability: portable--module Data.Attoparsec.Time.Internal- (- TimeOfDay64(..)- , fromPico- , toPico- , diffTimeOfDay64- , toTimeOfDay64- ) where--import Prelude.Compat--import Data.Fixed (Fixed(MkFixed), Pico)-import Data.Int (Int64)-import Data.Time (TimeOfDay(..))-import Data.Time.Clock.Compat--toPico :: Integer -> Pico-toPico = MkFixed--fromPico :: Pico -> Integer-fromPico (MkFixed i) = i---- | Like TimeOfDay, but using a fixed-width integer for seconds.-data TimeOfDay64 = TOD {-# UNPACK #-} !Int- {-# UNPACK #-} !Int- {-# UNPACK #-} !Int64--posixDayLength :: DiffTime-posixDayLength = 86400--diffTimeOfDay64 :: DiffTime -> TimeOfDay64-diffTimeOfDay64 t- | t >= posixDayLength = TOD 23 59 (60000000000000 + pico (t - posixDayLength))- | otherwise = TOD (fromIntegral h) (fromIntegral m) s- where (h,mp) = pico t `quotRem` 3600000000000000- (m,s) = mp `quotRem` 60000000000000- pico = fromIntegral . diffTimeToPicoseconds--toTimeOfDay64 :: TimeOfDay -> TimeOfDay64-toTimeOfDay64 (TimeOfDay h m s) = TOD h m (fromIntegral (fromPico s))
attoparsec-iso8601.cabal view
@@ -1,6 +1,6 @@ name: attoparsec-iso8601-version: 1.0.2.0-synopsis: Parsing of ISO 8601 dates, originally from aeson.+version: 1.0.2.1+synopsis: Parsing of ISO 8601 dates, originally from aeson description: Parsing of ISO 8601 dates, originally from aeson. license: BSD3 license-file: LICENSE@@ -14,7 +14,7 @@ homepage: https://github.com/haskell/aeson bug-reports: https://github.com/haskell/aeson/issues build-type: Simple-tested-with: GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.3, GHC == 8.10.1+tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0.1 extra-source-files: README.md @@ -29,19 +29,19 @@ manual: True library- hs-source-dirs: .+ hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall exposed-modules: Data.Attoparsec.Time.Internal Data.Attoparsec.Time build-depends:- attoparsec >= 0.13.0.1 && < 0.14.0.0,- base >= 4.7 && < 5,- base-compat-batteries >= 0.10.0 && < 0.12,+ attoparsec >= 0.14.2 && < 0.15,+ base >= 4.9 && < 5,+ base-compat-batteries >= 0.10.0 && < 0.13, time-compat >= 1.9.4 && < 1.10,- text >= 1.1.1.0 && < 1.3.0.0,- time >= 1.1.1.4 && < 1.12+ text >= 1.2.3.0 && < 1.3.0.0 || >= 2.0 && <2.1,+ time >= 1.6.0.1 && < 1.13 if flag(fast) ghc-options: -O0
+ src/Data/Attoparsec/Time.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module: Data.Aeson.Parser.Time+-- Copyright: (c) 2015-2016 Bryan O'Sullivan+-- License: BSD3+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable+--+-- Parsers for parsing dates and times.++module Data.Attoparsec.Time+ (+ day+ , localTime+ , timeOfDay+ , timeZone+ , utcTime+ , zonedTime+ , month+ , quarter+ ) where++import Prelude.Compat++import Control.Applicative ((<|>))+import Control.Monad (void, when)+import Data.Attoparsec.Text (Parser, char, decimal, digit, option, anyChar, peekChar, peekChar', takeWhile1, satisfy)+import Data.Attoparsec.Time.Internal (toPico)+import Data.Bits ((.&.))+import Data.Char (isDigit, ord)+import Data.Fixed (Pico)+import Data.Int (Int64)+import Data.Maybe (fromMaybe)+import Data.Time.Calendar (Day, fromGregorianValid)+import Data.Time.Calendar.Quarter.Compat (Quarter, QuarterOfYear (..), fromYearQuarter)+import Data.Time.Calendar.Month.Compat (Month, fromYearMonthValid)+import Data.Time.Clock (UTCTime(..))+import qualified Data.Text as T+import qualified Data.Time.LocalTime as Local++-- | Parse a date of the form @[+,-]YYYY-MM-DD@.+day :: Parser Day+day = do+ absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id+ y <- (decimal <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"+ m <- (twoDigits <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"+ d <- twoDigits <|> fail "date must be of form [+,-]YYYY-MM-DD"+ maybe (fail "invalid date") return (fromGregorianValid (absOrNeg y) m d)++-- | Parse a month of the form @[+,-]YYYY-MM@.+month :: Parser Month+month = do+ absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id+ y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"+ m <- twoDigits <|> fail "month must be of form [+,-]YYYY-MM"+ maybe (fail "invalid month") return (fromYearMonthValid (absOrNeg y) m)++-- | Parse a quarter of the form @[+,-]YYYY-QN@.+quarter :: Parser Quarter+quarter = do+ absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id+ y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"+ _ <- char 'q' <|> char 'Q'+ q <- parseQ+ return $! fromYearQuarter (absOrNeg y) q+ where+ parseQ = Q1 <$ char '1'+ <|> Q2 <$ char '2'+ <|> Q3 <$ char '3'+ <|> Q4 <$ char '4'++-- | Parse a two-digit integer (e.g. day of month, hour).+twoDigits :: Parser Int+twoDigits = do+ a <- digit+ b <- digit+ let c2d c = ord c .&. 15+ return $! c2d a * 10 + c2d b++-- | Parse a time of the form @HH:MM[:SS[.SSS]]@.+timeOfDay :: Parser Local.TimeOfDay+timeOfDay = do+ h <- twoDigits+ m <- char ':' *> twoDigits+ s <- option 0 (char ':' *> seconds)+ if h < 24 && m < 60 && s < 61+ then return (Local.TimeOfDay h m s)+ else fail "invalid time"++data T = T {-# UNPACK #-} !Int {-# UNPACK #-} !Int64++-- | Parse a count of seconds, with the integer part being two digits+-- long.+seconds :: Parser Pico+seconds = do+ real <- twoDigits+ mc <- peekChar+ case mc of+ Just '.' -> do+ t <- anyChar *> takeWhile1 isDigit+ return $! parsePicos real t+ _ -> return $! fromIntegral real+ where+ parsePicos a0 t = toPico (fromIntegral (t' * 10^n))+ where T n t' = T.foldl' step (T 12 (fromIntegral a0)) t+ step ma@(T m a) c+ | m <= 0 = ma+ | otherwise = T (m-1) (10 * a + fromIntegral (ord c) .&. 15)++-- | Parse a time zone, and return 'Nothing' if the offset from UTC is+-- zero. (This makes some speedups possible.)+timeZone :: Parser (Maybe Local.TimeZone)+timeZone = do+ let maybeSkip c = do ch <- peekChar'; when (ch == c) (void anyChar)+ maybeSkip ' '+ ch <- satisfy $ \c -> c == 'Z' || c == '+' || c == '-'+ if ch == 'Z'+ then return Nothing+ else do+ h <- twoDigits+ mm <- peekChar+ m <- case mm of+ Just ':' -> anyChar *> twoDigits+ Just d | isDigit d -> twoDigits+ _ -> return 0+ let off | ch == '-' = negate off0+ | otherwise = off0+ off0 = h * 60 + m+ case undefined of+ _ | off == 0 ->+ return Nothing+ | off < -720 || off > 840 || m > 59 ->+ fail "invalid time zone offset"+ | otherwise ->+ let !tz = Local.minutesToTimeZone off+ in return (Just tz)++-- | Parse a date and time, of the form @YYYY-MM-DD HH:MM[:SS[.SSS]]@.+-- The space may be replaced with a @T@. The number of seconds is optional+-- and may be followed by a fractional component.+localTime :: Parser Local.LocalTime+localTime = Local.LocalTime <$> day <* daySep <*> timeOfDay+ where daySep = satisfy (\c -> c == 'T' || c == ' ')++-- | Behaves as 'zonedTime', but converts any time zone offset into a+-- UTC time.+utcTime :: Parser UTCTime+utcTime = do+ lt@(Local.LocalTime d t) <- localTime+ mtz <- timeZone+ case mtz of+ Nothing -> let !tt = Local.timeOfDayToTime t+ in return (UTCTime d tt)+ Just tz -> return $! Local.localTimeToUTC tz lt++-- | Parse a date with time zone info. Acceptable formats:+--+-- @YYYY-MM-DD HH:MM Z@+-- @YYYY-MM-DD HH:MM:SS Z@+-- @YYYY-MM-DD HH:MM:SS.SSS Z@+--+-- The first space may instead be a @T@, and the second space is+-- optional. The @Z@ represents UTC. The @Z@ may be replaced with a+-- time zone offset of the form @+0000@ or @-08:00@, where the first+-- two digits are hours, the @:@ is optional and the second two digits+-- (also optional) are minutes.+zonedTime :: Parser Local.ZonedTime+zonedTime = Local.ZonedTime <$> localTime <*> (fromMaybe utc <$> timeZone)++utc :: Local.TimeZone+utc = Local.TimeZone 0 False ""
+ src/Data/Attoparsec/Time/Internal.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE NoImplicitPrelude #-}+-- |+-- Module: Data.Aeson.Internal.Time+-- Copyright: (c) 2015-2016 Bryan O'Sullivan+-- License: BSD3+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>+-- Stability: experimental+-- Portability: portable++module Data.Attoparsec.Time.Internal+ (+ TimeOfDay64(..)+ , fromPico+ , toPico+ , diffTimeOfDay64+ , toTimeOfDay64+ ) where++import Prelude.Compat++import Data.Fixed (Fixed(MkFixed), Pico)+import Data.Int (Int64)+import Data.Time (TimeOfDay(..))+import Data.Time.Clock.Compat (DiffTime, diffTimeToPicoseconds)++toPico :: Integer -> Pico+toPico = MkFixed++fromPico :: Pico -> Integer+fromPico (MkFixed i) = i++-- | Like TimeOfDay, but using a fixed-width integer for seconds.+data TimeOfDay64 = TOD {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int+ {-# UNPACK #-} !Int64++posixDayLength :: DiffTime+posixDayLength = 86400++diffTimeOfDay64 :: DiffTime -> TimeOfDay64+diffTimeOfDay64 t+ | t >= posixDayLength = TOD 23 59 (60000000000000 + pico (t - posixDayLength))+ | otherwise = TOD (fromIntegral h) (fromIntegral m) s+ where (h,mp) = pico t `quotRem` 3600000000000000+ (m,s) = mp `quotRem` 60000000000000+ pico = fromIntegral . diffTimeToPicoseconds++toTimeOfDay64 :: TimeOfDay -> TimeOfDay64+toTimeOfDay64 (TimeOfDay h m s) = TOD h m (fromIntegral (fromPico s))