mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-27 12:24:43 +03:00
parsing: date parsing overhaul, don't exit on bad dates
- get rid of undefined parse errors due to guard - don't call error from date parsers; let add re-prompt on bad dates - years now must always have at least four digits - clearer date parse errors
This commit is contained in:
parent
a90d32ae78
commit
b19b02962a
@ -597,7 +597,7 @@ tests = TestList [
|
|||||||
"1999/3/2" `gives` "1999/03/02"
|
"1999/3/2" `gives` "1999/03/02"
|
||||||
"19990302" `gives` "1999/03/02"
|
"19990302" `gives` "1999/03/02"
|
||||||
"2008/2" `gives` "2008/02/01"
|
"2008/2" `gives` "2008/02/01"
|
||||||
"20/2" `gives` "0020/02/01"
|
"0020/2" `gives` "0020/02/01"
|
||||||
"1000" `gives` "1000/01/01"
|
"1000" `gives` "1000/01/01"
|
||||||
"4/2" `gives` "2008/04/02"
|
"4/2" `gives` "2008/04/02"
|
||||||
"2" `gives` "2008/11/02"
|
"2" `gives` "2008/11/02"
|
||||||
|
@ -23,6 +23,7 @@ where
|
|||||||
|
|
||||||
import Data.Time.Format
|
import Data.Time.Format
|
||||||
import Data.Time.Calendar.OrdinalDate
|
import Data.Time.Calendar.OrdinalDate
|
||||||
|
import Safe (readMay)
|
||||||
import System.Locale (defaultTimeLocale)
|
import System.Locale (defaultTimeLocale)
|
||||||
import Text.ParserCombinators.Parsec
|
import Text.ParserCombinators.Parsec
|
||||||
import Hledger.Data.Types
|
import Hledger.Data.Types
|
||||||
@ -261,54 +262,65 @@ smartdateonly = do
|
|||||||
|
|
||||||
datesepchar = oneOf "/-."
|
datesepchar = oneOf "/-."
|
||||||
|
|
||||||
|
validYear, validMonth, validDay :: String -> Bool
|
||||||
|
validYear s = length s >= 4 && isJust (readMay s :: Maybe Int)
|
||||||
|
validMonth s = maybe False (\n -> n>=1 && n<=12) $ readMay s
|
||||||
|
validDay s = maybe False (\n -> n>=1 && n<=31) $ readMay s
|
||||||
|
|
||||||
|
-- failIfInvalidYear, failIfInvalidMonth, failIfInvalidDay :: a
|
||||||
|
failIfInvalidYear s = unless (validYear s) $ fail $ "bad year number: " ++ s
|
||||||
|
failIfInvalidMonth s = unless (validMonth s) $ fail $ "bad month number: " ++ s
|
||||||
|
failIfInvalidDay s = unless (validDay s) $ fail $ "bad day number: " ++ s
|
||||||
|
|
||||||
yyyymmdd :: GenParser Char st SmartDate
|
yyyymmdd :: GenParser Char st SmartDate
|
||||||
yyyymmdd = do
|
yyyymmdd = do
|
||||||
y <- count 4 digit
|
y <- count 4 digit
|
||||||
m <- count 2 digit
|
m <- count 2 digit
|
||||||
guard (read m <= 12)
|
failIfInvalidMonth m
|
||||||
d <- count 2 digit
|
d <- count 2 digit
|
||||||
guard (read d <= 31)
|
failIfInvalidDay d
|
||||||
return (y,m,d)
|
return (y,m,d)
|
||||||
|
|
||||||
ymd :: GenParser Char st SmartDate
|
ymd :: GenParser Char st SmartDate
|
||||||
ymd = do
|
ymd = do
|
||||||
y <- many1 digit
|
y <- many1 digit
|
||||||
|
failIfInvalidYear y
|
||||||
datesepchar
|
datesepchar
|
||||||
m <- try (count 2 digit) <|> count 1 digit
|
m <- many1 digit
|
||||||
when (read m < 1 || (read m > 12)) $ error $ "bad month number: " ++ m
|
failIfInvalidMonth m
|
||||||
datesepchar
|
datesepchar
|
||||||
d <- try (count 2 digit) <|> count 1 digit
|
d <- many1 digit
|
||||||
when (read d < 1 || (read d > 31)) $ error $ "bad day number: " ++ d
|
failIfInvalidDay d
|
||||||
return $ (y,m,d)
|
return $ (y,m,d)
|
||||||
|
|
||||||
ym :: GenParser Char st SmartDate
|
ym :: GenParser Char st SmartDate
|
||||||
ym = do
|
ym = do
|
||||||
y <- many1 digit
|
y <- many1 digit
|
||||||
guard (read y > 12)
|
failIfInvalidYear y
|
||||||
datesepchar
|
datesepchar
|
||||||
m <- try (count 2 digit) <|> count 1 digit
|
m <- many1 digit
|
||||||
guard (read m >= 1 && (read m <= 12))
|
failIfInvalidMonth m
|
||||||
return (y,m,"")
|
return (y,m,"")
|
||||||
|
|
||||||
y :: GenParser Char st SmartDate
|
y :: GenParser Char st SmartDate
|
||||||
y = do
|
y = do
|
||||||
y <- many1 digit
|
y <- many1 digit
|
||||||
guard (read y >= 1000)
|
failIfInvalidYear y
|
||||||
return (y,"","")
|
return (y,"","")
|
||||||
|
|
||||||
d :: GenParser Char st SmartDate
|
d :: GenParser Char st SmartDate
|
||||||
d = do
|
d = do
|
||||||
d <- many1 digit
|
d <- many1 digit
|
||||||
guard (read d <= 31)
|
failIfInvalidDay d
|
||||||
return ("","",d)
|
return ("","",d)
|
||||||
|
|
||||||
md :: GenParser Char st SmartDate
|
md :: GenParser Char st SmartDate
|
||||||
md = do
|
md = do
|
||||||
m <- try (count 2 digit) <|> count 1 digit
|
m <- many1 digit
|
||||||
guard (read m >= 1 && (read m <= 12))
|
failIfInvalidMonth m
|
||||||
datesepchar
|
datesepchar
|
||||||
d <- try (count 2 digit) <|> count 1 digit
|
d <- many1 digit
|
||||||
when (read d < 1 || (read d > 31)) $ fail "bad day number specified"
|
failIfInvalidDay d
|
||||||
return ("",m,d)
|
return ("",m,d)
|
||||||
|
|
||||||
months = ["january","february","march","april","may","june",
|
months = ["january","february","march","april","may","june",
|
||||||
|
@ -380,7 +380,7 @@ ledgerDefaultYear = do
|
|||||||
many spacenonewline
|
many spacenonewline
|
||||||
y <- many1 digit
|
y <- many1 digit
|
||||||
let y' = read y
|
let y' = read y
|
||||||
guard (y' >= 1000)
|
failIfInvalidYear y
|
||||||
setYear y'
|
setYear y'
|
||||||
return $ return id
|
return $ return id
|
||||||
|
|
||||||
@ -403,7 +403,7 @@ ledgerTransaction = do
|
|||||||
Left err -> fail err
|
Left err -> fail err
|
||||||
|
|
||||||
ledgerdate :: GenParser Char LedgerFileCtx Day
|
ledgerdate :: GenParser Char LedgerFileCtx Day
|
||||||
ledgerdate = (try ledgerfulldate <|> ledgerpartialdate) <?> "full or partial date"
|
ledgerdate = choice' [ledgerfulldate, ledgerpartialdate] <?> "full or partial date"
|
||||||
|
|
||||||
ledgerfulldate :: GenParser Char LedgerFileCtx Day
|
ledgerfulldate :: GenParser Char LedgerFileCtx Day
|
||||||
ledgerfulldate = do
|
ledgerfulldate = do
|
||||||
@ -416,7 +416,7 @@ ledgerpartialdate :: GenParser Char LedgerFileCtx Day
|
|||||||
ledgerpartialdate = do
|
ledgerpartialdate = do
|
||||||
(_,m,d) <- md
|
(_,m,d) <- md
|
||||||
y <- getYear
|
y <- getYear
|
||||||
when (y==Nothing) $ fail "partial date found, but no default year specified"
|
when (isNothing y) $ fail "partial date found, but no default year specified"
|
||||||
return $ fromGregorian (fromJust y) (read m) (read d)
|
return $ fromGregorian (fromJust y) (read m) (read d)
|
||||||
|
|
||||||
ledgerdatetime :: GenParser Char LedgerFileCtx LocalTime
|
ledgerdatetime :: GenParser Char LedgerFileCtx LocalTime
|
||||||
@ -458,7 +458,7 @@ ledgerpostings = do
|
|||||||
let parses p = isRight . parseWithCtx ctx p
|
let parses p = isRight . parseWithCtx ctx p
|
||||||
ls <- many1 $ try linebeginningwithspaces
|
ls <- many1 $ try linebeginningwithspaces
|
||||||
let ls' = filter (not . (ledgercommentline `parses`)) ls
|
let ls' = filter (not . (ledgercommentline `parses`)) ls
|
||||||
guard (not $ null ls')
|
when (null ls') $ fail "no postings"
|
||||||
return $ map (fromparse . parseWithCtx ctx ledgerposting) ls'
|
return $ map (fromparse . parseWithCtx ctx ledgerposting) ls'
|
||||||
<?> "postings"
|
<?> "postings"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user