lib: Ensure parsed years have at least 4 digits.

This commit is contained in:
Stephen Morgan 2020-07-28 23:00:25 +10:00 committed by Simon Michael
parent 7b9f9ae49c
commit ffb5cf0773
3 changed files with 27 additions and 17 deletions

View File

@ -71,6 +71,7 @@ module Hledger.Data.Dates (
fixSmartDateStr,
fixSmartDateStrEither,
fixSmartDateStrEither',
yearp,
daysInSpan,
maybePeriod,
mkdatespan,
@ -84,6 +85,7 @@ import Control.Applicative (liftA2)
import Control.Applicative.Permutations
import Control.Monad (guard, unless)
import "base-compat-batteries" Data.List.Compat
import Data.Char (isDigit)
import Data.Default
import Data.Foldable (asum)
import Data.Function (on)
@ -763,12 +765,7 @@ Right (SmartYMD (Just 201813012) Nothing Nothing)
smartdate :: TextParser m SmartDate
smartdate = choice'
-- XXX maybe obscures date errors ? see ledgerdate
[ yyyymmdd
, md
, ymd
, yd
, month
, mon
[ yyyymmdd, md, ymd, smartYear, smartDay, month, mon
, SmartRel This Day <$ string' "today"
, SmartRel Last Day <$ string' "yesterday"
, SmartRel Next Day <$ string' "tomorrow"
@ -778,6 +775,11 @@ smartdate = choice'
seqP = choice [This <$ string' "this", Last <$ string' "last", Next <$ string' "next"]
intervalP = choice [Day <$ string' "day", Week <$ string' "week", Month <$ string' "month",
Quarter <$ string' "quarter", Year <$ string' "year"]
smartYear = (\y -> SmartYMD (Just y) Nothing Nothing) <$> yearp
smartDay = do
d <- SmartYMD Nothing Nothing . Just <$> decimal
failIfInvalidDate d
return d
-- | Like smartdate, but there must be nothing other than whitespace after the date.
smartdateonly :: TextParser m SmartDate
@ -812,7 +814,7 @@ yyyymmdd = do
ymd :: TextParser m SmartDate
ymd = do
y <- decimal
y <- yearp
sep <- datesepchar
m <- decimal
d <- optional $ char sep *> decimal
@ -829,12 +831,13 @@ md = do
failIfInvalidDate date
return date
yd :: TextParser m SmartDate
yd = do
n <- decimal
if n >= 1 && n <= 31
then return $ SmartYMD Nothing Nothing (Just $ fromInteger n)
else return $ SmartYMD (Just n) Nothing Nothing
-- | Parse a year number from a Text, making sure that at least four digits are
-- used.
yearp :: TextParser m Integer
yearp = do
year <- takeWhile1P (Just "year") isDigit
unless (T.length year >= 4) . Fail.fail $ "Year must contain at least 4 digits: " <> T.unpack year
return $ readDecimal year
-- These are compared case insensitively, and should all be kept lower case.
months = ["january","february","march","april","may","june",

View File

@ -98,7 +98,6 @@ import Data.Time.LocalTime
import Safe
import Text.Megaparsec hiding (parse)
import Text.Megaparsec.Char
import Text.Megaparsec.Char.Lexer (decimal)
import Text.Megaparsec.Custom
import Text.Printf
import System.FilePath
@ -553,8 +552,7 @@ defaultyeardirectivep :: JournalParser m ()
defaultyeardirectivep = do
char 'Y' <?> "default year"
lift skipNonNewlineSpaces
y <- decimal
setYear y
setYear =<< lift yearp
defaultcommoditydirectivep :: JournalParser m ()
defaultcommoditydirectivep = do

View File

@ -50,12 +50,14 @@ module Hledger.Utils.Text
-- fitStringMulti,
textPadLeftWide,
textPadRightWide,
-- -- * Reading
readDecimal,
-- -- * tests
tests_Text
)
where
-- import Data.Char
import Data.Char (digitToInt)
import Data.List
#if !(MIN_VERSION_base(4,11,0))
import Data.Monoid
@ -400,6 +402,13 @@ textWidth s = maximum $ map (T.foldr (\a b -> charWidth a + b) 0) $ T.lines s
-- | otherwise -> 1
-- | Read a decimal number from a Text. Assumes the input consists only of digit
-- characters.
readDecimal :: Integral a => Text -> a
readDecimal = foldl' step 0 . T.unpack
where step a c = a * 10 + fromIntegral (digitToInt c)
tests_Text = tests "Text" [
test "quoteIfSpaced" $ do
quoteIfSpaced "a'a" @?= "a'a"