attoparsec-iso8601 1.0.2.1 → 1.1.0.0
raw patch · 3 files changed
+101/−25 lines, 3 filesdep ~base-compat-batteriesPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base-compat-batteries
API changes (from Hackage documentation)
+ Data.Attoparsec.Time: year :: Parser Year
Files
- attoparsec-iso8601.cabal +3/−21
- changelog.md +19/−0
- src/Data/Attoparsec/Time.hs +79/−4
attoparsec-iso8601.cabal view
@@ -1,5 +1,5 @@ name: attoparsec-iso8601-version: 1.0.2.1+version: 1.1.0.0 synopsis: Parsing of ISO 8601 dates, originally from aeson description: Parsing of ISO 8601 dates, originally from aeson. license: BSD3@@ -14,19 +14,10 @@ homepage: https://github.com/haskell/aeson bug-reports: https://github.com/haskell/aeson/issues build-type: Simple-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+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.7, GHC == 9.0.2, GHC ==9.2.3, GHC==9.4.1 extra-source-files: README.md--flag developer- description: operate in developer mode- default: False- manual: True--flag fast- description: compile without optimizations- default: False- manual: True+ changelog.md library hs-source-dirs: src@@ -42,15 +33,6 @@ time-compat >= 1.9.4 && < 1.10, 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- else- ghc-options: -O2-- if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all source-repository head type: git
+ changelog.md view
@@ -0,0 +1,19 @@+For the latest version of this document, please see [https://github.com/bos/aeson/blob/master/attoparsec-iso8601/changelog.md](https://github.com/bos/aeson/blob/master/attoparsec-iso8601/changelog.md).++### 1.1.0.0++- Change parsers of types with year (`Day`, `UTCTime`) to require years with at least 4 digits.+- Remove `fast` and `developer` package flags++### 1.0.2.1++* Code (re)organization.+* Avoid wildcard imports++### 1.0.2.0++* Add `month :: Parser Month` and `quarter :: Parser Quarter`++### 1.0.1.0++* Fixes handling of `UTCTime` wrt. leap seconds , thanks to Adam Schønemann
src/Data/Attoparsec/Time.hs view
@@ -19,6 +19,7 @@ , timeZone , utcTime , zonedTime+ , year , month , quarter ) where@@ -27,7 +28,7 @@ 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.Text (Parser, char, digit, option, anyChar, peekChar, peekChar', takeWhile1, satisfy) import Data.Attoparsec.Time.Internal (toPico) import Data.Bits ((.&.)) import Data.Char (isDigit, ord)@@ -35,6 +36,7 @@ import Data.Int (Int64) import Data.Maybe (fromMaybe) import Data.Time.Calendar (Day, fromGregorianValid)+import Data.Time.Calendar.Compat (Year) import Data.Time.Calendar.Quarter.Compat (Quarter, QuarterOfYear (..), fromYearQuarter) import Data.Time.Calendar.Month.Compat (Month, fromYearMonthValid) import Data.Time.Clock (UTCTime(..))@@ -42,27 +44,37 @@ import qualified Data.Time.LocalTime as Local -- | Parse a date of the form @[+,-]YYYY-MM-DD@.+--+-- The year must contain at least 4 digits, to avoid the Y2K problem:+-- a two-digit year @YY@ may mean @YY@, @19YY@, or @20YY@, and we make it+-- an error to prevent the ambiguity.+-- Years from @0000@ to @0999@ must thus be zero-padded.+-- The year may have more than 4 digits. day :: Parser Day day = do absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id- y <- (decimal <* char '-') <|> fail "date must be of form [+,-]YYYY-MM-DD"+ y <- (year <* 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@.+--+-- See also 'day' for details about the year format. month :: Parser Month month = do absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id- y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"+ y <- (year <* 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@.+--+-- See also 'day' for details about the year format. quarter :: Parser Quarter quarter = do absOrNeg <- negate <$ char '-' <|> id <$ char '+' <|> pure id- y <- (decimal <* char '-') <|> fail "month must be of form [+,-]YYYY-MM"+ y <- (year <* char '-') <|> fail "month must be of form [+,-]YYYY-MM" _ <- char 'q' <|> char 'Q' q <- parseQ return $! fromYearQuarter (absOrNeg y) q@@ -72,6 +84,19 @@ <|> Q3 <$ char '3' <|> Q4 <$ char '4' +-- | Parse a year @YYYY@, with at least 4 digits. Does not include any sign.+--+-- Note: 'Year' is a type synonym for 'Integer'.+--+-- @since 1.1.0.0+year :: Parser Year+year = do+ ds <- takeWhile1 isDigit+ if T.length ds < 4 then+ fail "expected year with at least 4 digits"+ else+ pure (txtToInteger ds)+ -- | Parse a two-digit integer (e.g. day of month, hour). twoDigits :: Parser Int twoDigits = do@@ -172,3 +197,53 @@ utc :: Local.TimeZone utc = Local.TimeZone 0 False ""++------------------ Copy-pasted and adapted from base ------------------------++txtToInteger :: T.Text -> Integer+txtToInteger bs+ | l > 40 = valInteger 10 l [ fromIntegral (ord w - 48) | w <- T.unpack bs ]+ | otherwise = txtToIntegerSimple bs+ where+ l = T.length bs++txtToIntegerSimple :: T.Text -> Integer+txtToIntegerSimple = T.foldl' step 0 where+ step a b = a * 10 + fromIntegral (ord b - 48) -- 48 = '0'++-- A sub-quadratic algorithm for Integer. Pairs of adjacent radix b+-- digits are combined into a single radix b^2 digit. This process is+-- repeated until we are left with a single digit. This algorithm+-- performs well only on large inputs, so we use the simple algorithm+-- for smaller inputs.+valInteger :: Integer -> Int -> [Integer] -> Integer+valInteger = go+ where+ go :: Integer -> Int -> [Integer] -> Integer+ go _ _ [] = 0+ go _ _ [d] = d+ go b l ds+ | l > 40 = b' `seq` go b' l' (combine b ds')+ | otherwise = valSimple b ds+ where+ -- ensure that we have an even number of digits+ -- before we call combine:+ ds' = if even l then ds else 0 : ds+ b' = b * b+ l' = (l + 1) `quot` 2++ combine b (d1 : d2 : ds) = d `seq` (d : combine b ds)+ where+ d = d1 * b + d2+ combine _ [] = []+ combine _ [_] = errorWithoutStackTrace "this should not happen"++-- The following algorithm is only linear for types whose Num operations+-- are in constant time.+valSimple :: Integer -> [Integer] -> Integer+valSimple base = go 0+ where+ go r [] = r+ go r (d : ds) = r' `seq` go r' ds+ where+ r' = r * base + fromIntegral d