diff --git a/hledger-lib/Hledger/Data/Types.hs b/hledger-lib/Hledger/Data/Types.hs index 8d41ec9cd..1521ac8da 100644 --- a/hledger-lib/Hledger/Data/Types.hs +++ b/hledger-lib/Hledger/Data/Types.hs @@ -43,6 +43,9 @@ import Text.Printf import Hledger.Utils.Regex +-- | A possibly incomplete date, whose missing parts will be filled from a reference date. +-- A numeric year, month, and day of month, or the empty string for any of these. +-- See the smartdate parser. type SmartDate = (String,String,String) data WhichDate = PrimaryDate | SecondaryDate deriving (Eq,Show) diff --git a/hledger-lib/Hledger/Read/JournalReader.hs b/hledger-lib/Hledger/Read/JournalReader.hs index 4d87779fa..37b034869 100644 --- a/hledger-lib/Hledger/Read/JournalReader.hs +++ b/hledger-lib/Hledger/Read/JournalReader.hs @@ -465,6 +465,14 @@ transactionmodifierp = do return $ TransactionModifier querytxt postings -- | Parse a periodic transaction +-- +-- This reuses periodexprp which parses period expressions on the command line. +-- This is awkward because periodexprp supports relative and partial dates, +-- which we don't really need here, and it doesn't support the notion of a +-- default year set by a Y directive, which we do need to consider here. +-- We resolve it as follows: in periodic transactions' period expressions, +-- if there is a default year Y in effect, partial/relative dates are calculated +-- relative to Y/1/1. If not, they are calculated related to today as usual. periodictransactionp :: MonadIO m => JournalParser m PeriodicTransaction periodictransactionp = do @@ -473,8 +481,16 @@ periodictransactionp = do lift $ skipMany spacenonewline -- a period expression off <- getOffset - d <- liftIO getCurrentDay - (periodtxt, (interval, span)) <- lift $ first T.strip <$> match (periodexprp d) + pos <- getPosition + + -- if there's a default year in effect, use Y/1/1 as base for partial/relative dates + today <- liftIO getCurrentDay + mdefaultyear <- getYear + let refdate = case mdefaultyear of + Nothing -> today + Just y -> fromGregorian y 1 1 + (periodtxt, (interval, span)) <- lift $ first T.strip <$> match (periodexprp refdate) + -- In periodic transactions, the period expression has an additional constraint: case checkPeriodicTransactionStartDate interval span periodtxt of Just e -> customFailure $ parseErrorAt off e @@ -493,8 +509,8 @@ periodictransactionp = do return (s,c,desc,(cmt,ts)) ) - -- next lines - postings <- postingsp (Just $ first3 $ toGregorian d) + -- next lines; use same year determined above + postings <- postingsp (Just $ first3 $ toGregorian refdate) return $ nullperiodictransaction{ ptperiodexpr=periodtxt diff --git a/hledger-lib/hledger_journal.m4.md b/hledger-lib/hledger_journal.m4.md index 413a48ad5..56531a2d3 100644 --- a/hledger-lib/hledger_journal.m4.md +++ b/hledger-lib/hledger_journal.m4.md @@ -1019,6 +1019,10 @@ There is an additional constraint on the period expression: the start date must fall on a natural boundary of the interval. Eg `monthly from 2018/1/1` is valid, but `monthly from 2018/1/15` is not. +Partial or relative dates (M/D, D, tomorrow, last week) in the period expression +can work (useful or not). They will be relative to today's date, unless +a Y default year directive is in effect, in which case they will be relative to Y/1/1. + If you write a transaction description or same-line comment, it must be separated from the period expression by **two or more spaces**. Eg: diff --git a/tests/budget/forecast.test b/tests/budget/forecast.test index f97e3af64..8fa78d5dc 100644 --- a/tests/budget/forecast.test +++ b/tests/budget/forecast.test @@ -105,21 +105,54 @@ hledger register -b 2015-12 -e 2017-02 -f - assets:cash --forecast >>>2 >>>=0 -# TODO -# 5. Y should affect the partial date in this periodic transaction. -# Also the recur tag's value ? -#hledger -f - print --forecast desc:forecast -#<<< -#Y 2000 -# -#~ 2/1 forecast -# -#; a real transaction to set --forecast's start date -#2000/1/1 real -# -#>>> -#2000/02/01 forecast -# ; recur: 2000/2/1 -# -#>>>2 -#>>>=0 +# 5. Y affects M/D partial dates in periodic transactions. +# The recur tag shows the original period expression and is not modified. +hledger -f - print --forecast desc:forecast +<<< +Y 2000 + +~ 2/1 forecast + +; a real transaction to set the start of the forecast window +2000/1/1 real + +>>> +2000/02/01 forecast + ; recur: 2/1 + +>>>2 +>>>=0 + +# 6. Y also sets the month to 1, affecting D dates: +hledger -f - print --forecast desc:forecast +<<< +Y 2000 + +~ 15 forecast + +; a real transaction to set the start of the forecast window +2000/1/1 real + +>>> +2000/01/15 forecast + ; recur: 15 + +>>>2 +>>>=0 + +# 7. Y also sets the day to 1, affecting relative dates: +hledger -f - print --forecast desc:forecast +<<< +Y 2000 + +~ next month forecast + +; a real transaction to set the start of the forecast window +2000/1/1 real + +>>> +2000/02/01 forecast + ; recur: next month + +>>>2 +>>>=0