lib: datep now requires years to be at least four digits.

This commit is contained in:
Stephen Morgan 2020-07-31 11:17:23 +10:00 committed by Simon Michael
parent ffb5cf0773
commit dc41cee2b0
2 changed files with 55 additions and 48 deletions

View File

@ -12,20 +12,20 @@ Some of these might belong in Hledger.Read.JournalReader or Hledger.Read.
-} -}
--- ** language --- ** language
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoMonoLocalBinds #-} {-# LANGUAGE NoMonoLocalBinds #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}
{-# LANGUAGE Rank2Types #-} {-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
--- ** exports --- ** exports
module Hledger.Read.Common ( module Hledger.Read.Common (
@ -446,45 +446,44 @@ datep = do
datep' :: Maybe Year -> TextParser m Day datep' :: Maybe Year -> TextParser m Day
datep' mYear = do datep' mYear = do
startOffset <- getOffset startOffset <- getOffset
d1 <- decimal <?> "year or month" d1 <- yearorintp <?> "year or month"
sep <- satisfy isDateSepChar <?> "date separator" sep <- datesepchar <?> "date separator"
d2 <- decimal <?> "month or day" d2 <- decimal <?> "month or day"
fullDate startOffset d1 sep d2 <|> partialDate startOffset mYear d1 sep d2 case d1 of
<?> "full or partial date" Left y -> fullDate startOffset y sep d2
Right m -> partialDate startOffset mYear m sep d2
<?> "full or partial date"
where where
fullDate :: Int -> Year -> Char -> Month -> TextParser m Day
fullDate startOffset year sep1 month = do
sep2 <- satisfy isDateSepChar <?> "date separator"
day <- decimal <?> "day"
endOffset <- getOffset
let dateStr = show year ++ [sep1] ++ show month ++ [sep2] ++ show day
fullDate :: Int -> Integer -> Char -> Int -> TextParser m Day when (sep1 /= sep2) $ customFailure $ parseErrorAtRegion startOffset endOffset $
fullDate startOffset year sep1 month = do "invalid date (mixing date separators is not allowed): " ++ dateStr
sep2 <- satisfy isDateSepChar <?> "date separator"
day <- decimal <?> "day"
endOffset <- getOffset
let dateStr = show year ++ [sep1] ++ show month ++ [sep2] ++ show day
when (sep1 /= sep2) $ customFailure $ parseErrorAtRegion startOffset endOffset $ case fromGregorianValid year month day of
"invalid date (mixing date separators is not allowed): " ++ dateStr Nothing -> customFailure $ parseErrorAtRegion startOffset endOffset $
"well-formed but invalid date: " ++ dateStr
Just date -> pure $! date
case fromGregorianValid year month day of partialDate :: Int -> Maybe Year -> Month -> Char -> MonthDay -> TextParser m Day
Nothing -> customFailure $ parseErrorAtRegion startOffset endOffset $ partialDate startOffset mYear month sep day = do
"well-formed but invalid date: " ++ dateStr endOffset <- getOffset
Just date -> pure $! date case mYear of
Just year ->
case fromGregorianValid year month day of
Nothing -> customFailure $ parseErrorAtRegion startOffset endOffset $
"well-formed but invalid date: " ++ dateStr
Just date -> pure $! date
where dateStr = show year ++ [sep] ++ show month ++ [sep] ++ show day
partialDate Nothing -> customFailure $ parseErrorAtRegion startOffset endOffset $
:: Int -> Maybe Year -> Integer -> Char -> Int -> TextParser m Day "partial date "++dateStr++" found, but the current year is unknown"
partialDate startOffset mYear month sep day = do where dateStr = show month ++ [sep] ++ show day
endOffset <- getOffset
case mYear of
Just year ->
case fromGregorianValid year (fromIntegral month) day of
Nothing -> customFailure $ parseErrorAtRegion startOffset endOffset $
"well-formed but invalid date: " ++ dateStr
Just date -> pure $! date
where dateStr = show year ++ [sep] ++ show month ++ [sep] ++ show day
Nothing -> customFailure $ parseErrorAtRegion startOffset endOffset $
"partial date "++dateStr++" found, but the current year is unknown"
where dateStr = show month ++ [sep] ++ show day
{-# INLINABLE datep' #-} {-# INLINABLE datep' #-}
@ -551,6 +550,14 @@ secondarydatep :: Day -> TextParser m Day
secondarydatep primaryDate = char '=' *> datep' (Just primaryYear) secondarydatep primaryDate = char '=' *> datep' (Just primaryYear)
where primaryYear = first3 $ toGregorian primaryDate where primaryYear = first3 $ toGregorian primaryDate
-- | Parse a year number or an Int. Years must contain at least four
-- digits.
yearorintp :: TextParser m (Either Year Int)
yearorintp = do
yearOrMonth <- takeWhile1P (Just "digit") isDigit
let n = readDecimal yearOrMonth
return $ if T.length yearOrMonth >= 4 then Left n else Right (fromInteger n)
--- *** account names --- *** account names
-- | Parse an account name (plus one following space if present), -- | Parse an account name (plus one following space if present),

View File

@ -50,5 +50,5 @@ end comment
2000/1/2 2000/1/2
b 0 ; [1/1=1/2/3/4] bad second date, should error b 0 ; [1/1=1/2/3/4] bad second date, should error
>>>2 /9:23/ >>>2 /-:9:21/
>>>=1 >>>=1