imp: fully support quarter syntax in smart dates and period expressions
This commit is contained in:
parent
afd18a10bf
commit
714b346fdd
@ -89,7 +89,7 @@ import Control.Applicative (Applicative(..))
|
|||||||
import Control.Applicative.Permutations
|
import Control.Applicative.Permutations
|
||||||
import Control.Monad (guard, unless)
|
import Control.Monad (guard, unless)
|
||||||
import qualified Control.Monad.Fail as Fail (MonadFail, fail)
|
import qualified Control.Monad.Fail as Fail (MonadFail, fail)
|
||||||
import Data.Char (digitToInt, isDigit, ord)
|
import Data.Char (digitToInt, isDigit)
|
||||||
import Data.Default (def)
|
import Data.Default (def)
|
||||||
import Data.Foldable (asum)
|
import Data.Foldable (asum)
|
||||||
import Data.Function (on)
|
import Data.Function (on)
|
||||||
@ -910,7 +910,8 @@ smartdate :: TextParser m SmartDate
|
|||||||
smartdate = choice'
|
smartdate = choice'
|
||||||
-- XXX maybe obscures date errors ? see ledgerdate
|
-- XXX maybe obscures date errors ? see ledgerdate
|
||||||
[ relativeP
|
[ relativeP
|
||||||
, yyyymmdd, ymd
|
, yyyymmdd
|
||||||
|
, ymd
|
||||||
, (\(m,d) -> SmartFromReference (Just m) d) <$> md
|
, (\(m,d) -> SmartFromReference (Just m) d) <$> md
|
||||||
, failIfInvalidDate . SmartFromReference Nothing =<< decimal
|
, failIfInvalidDate . SmartFromReference Nothing =<< decimal
|
||||||
, SmartMonth <$> (month <|> mon)
|
, SmartMonth <$> (month <|> mon)
|
||||||
@ -1141,12 +1142,11 @@ reportingintervalp = choice'
|
|||||||
]
|
]
|
||||||
|
|
||||||
periodexprdatespanp :: Day -> TextParser m DateSpan
|
periodexprdatespanp :: Day -> TextParser m DateSpan
|
||||||
periodexprdatespanp rdate = choice $ map try [
|
periodexprdatespanp rdate = choice' [
|
||||||
doubledatespanp rdate,
|
doubledatespanp rdate,
|
||||||
quarterdatespanp rdate,
|
|
||||||
fromdatespanp rdate,
|
fromdatespanp rdate,
|
||||||
todatespanp rdate,
|
todatespanp rdate,
|
||||||
justdatespanp rdate
|
indatespanp rdate
|
||||||
]
|
]
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
@ -1162,45 +1162,95 @@ periodexprdatespanp rdate = choice $ map try [
|
|||||||
-- Right DateSpan 2017
|
-- Right DateSpan 2017
|
||||||
doubledatespanp :: Day -> TextParser m DateSpan
|
doubledatespanp :: Day -> TextParser m DateSpan
|
||||||
doubledatespanp rdate = liftA2 fromToSpan
|
doubledatespanp rdate = liftA2 fromToSpan
|
||||||
(optional ((string' "from" <|> string' "since") *> skipNonNewlineSpaces) *> smartdate)
|
(optional ((string' "from" <|> string' "since") *> skipNonNewlineSpaces) *> smartdateorquarterstartp rdate)
|
||||||
(skipNonNewlineSpaces *> choice [string' "to", string "..", string "-"]
|
(skipNonNewlineSpaces *> choice [string' "to", string "..", string "-"]
|
||||||
*> skipNonNewlineSpaces *> smartdate)
|
*> skipNonNewlineSpaces *> smartdateorquarterstartp rdate)
|
||||||
where
|
where
|
||||||
fromToSpan = DateSpan `on` (Just . fixSmartDate rdate)
|
fromToSpan = DateSpan `on` (Just . fixSmartDate rdate)
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
-- >>> parsewith (quarterdatespanp (fromGregorian 2018 01 01) <* eof) "q1"
|
-- >>> let p = parsewith (fromdatespanp (fromGregorian 2024 02 02) <* eof)
|
||||||
-- Right DateSpan 2018Q1
|
-- >>> p "2025-01-01.."
|
||||||
-- >>> parsewith (quarterdatespanp (fromGregorian 2018 01 01) <* eof) "Q1"
|
-- Right DateSpan 2025-01-01..
|
||||||
-- Right DateSpan 2018Q1
|
-- >>> p "2025Q1.."
|
||||||
-- >>> parsewith (quarterdatespanp (fromGregorian 2018 01 01) <* eof) "2020q4"
|
-- Right DateSpan 2025-01-01..
|
||||||
-- Right DateSpan 2020Q4
|
-- >>> p "from q2"
|
||||||
quarterdatespanp :: Day -> TextParser m DateSpan
|
-- Right DateSpan 2024-04-01..
|
||||||
quarterdatespanp rdate = do
|
|
||||||
y <- yearp <|> pure (first3 $ toGregorian rdate)
|
|
||||||
q <- char' 'q' *> satisfy is4Digit
|
|
||||||
return . periodAsDateSpan $ QuarterPeriod y (digitToInt q)
|
|
||||||
where
|
|
||||||
is4Digit c = (fromIntegral (ord c - ord '1') :: Word) <= 3
|
|
||||||
|
|
||||||
fromdatespanp :: Day -> TextParser m DateSpan
|
fromdatespanp :: Day -> TextParser m DateSpan
|
||||||
fromdatespanp rdate = fromSpan <$> choice
|
fromdatespanp rdate = fromSpan <$> choice
|
||||||
[ (string' "from" <|> string' "since") *> skipNonNewlineSpaces *> smartdate
|
[ (string' "from" <|> string' "since") *> skipNonNewlineSpaces *> smartdateorquarterstartp rdate
|
||||||
, smartdate <* choice [string "..", string "-"]
|
, smartdateorquarterstartp rdate <* choice [string "..", string "-"]
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
fromSpan b = DateSpan (Just $ fixSmartDate rdate b) Nothing
|
fromSpan b = DateSpan (Just $ fixSmartDate rdate b) Nothing
|
||||||
|
|
||||||
|
-- |
|
||||||
|
-- >>> let p = parsewith (todatespanp (fromGregorian 2024 02 02) <* eof)
|
||||||
|
-- >>> p "..2025-01-01"
|
||||||
|
-- Right DateSpan ..2024-12-31
|
||||||
|
-- >>> p "..2025Q1"
|
||||||
|
-- Right DateSpan ..2024-12-31
|
||||||
|
-- >>> p "to q2"
|
||||||
|
-- Right DateSpan ..2024-03-31
|
||||||
todatespanp :: Day -> TextParser m DateSpan
|
todatespanp :: Day -> TextParser m DateSpan
|
||||||
todatespanp rdate =
|
todatespanp rdate =
|
||||||
choice [string' "to", string' "until", string "..", string "-"]
|
choice [string' "to", string' "until", string "..", string "-"]
|
||||||
*> skipNonNewlineSpaces
|
*> skipNonNewlineSpaces
|
||||||
*> (DateSpan Nothing . Just . fixSmartDate rdate <$> smartdate)
|
*> (DateSpan Nothing . Just . fixSmartDate rdate <$> smartdateorquarterstartp rdate)
|
||||||
|
|
||||||
justdatespanp :: Day -> TextParser m DateSpan
|
-- |j
|
||||||
justdatespanp rdate =
|
-- >>> let p = parsewith (indatespanp (fromGregorian 2024 02 02) <* eof)
|
||||||
optional (string' "in" *> skipNonNewlineSpaces)
|
-- >>> p "2025-01-01"
|
||||||
*> (spanFromSmartDate rdate <$> smartdate)
|
-- Right DateSpan 2025-01-01
|
||||||
|
-- >>> p "2025q1"
|
||||||
|
-- Right DateSpan 2025Q1
|
||||||
|
-- >>> p "in Q2"
|
||||||
|
-- Right DateSpan 2024Q2
|
||||||
|
indatespanp :: Day -> TextParser m DateSpan
|
||||||
|
indatespanp rdate =
|
||||||
|
optional (string' "in" *> skipNonNewlineSpaces)
|
||||||
|
*> choice' [
|
||||||
|
quarterspanp rdate,
|
||||||
|
spanFromSmartDate rdate <$> smartdate
|
||||||
|
]
|
||||||
|
|
||||||
|
-- Helper: parse a quarter number, optionally preceded by a year.
|
||||||
|
quarterp :: Day -> TextParser m (Year, Int)
|
||||||
|
quarterp rdate = do
|
||||||
|
y <- yearp <|> pure (first3 $ toGregorian rdate)
|
||||||
|
n <- char' 'q' *> satisfy (`elem` ['1' .. '4']) >>= return . digitToInt
|
||||||
|
return (y, n)
|
||||||
|
|
||||||
|
-- | Parse a single quarter (YYYYqN or qN, case insensitive q) as a date span.
|
||||||
|
--
|
||||||
|
-- >>> parsewith (quarterspanp (fromGregorian 2018 01 01) <* eof) "q1"
|
||||||
|
-- Right DateSpan 2018Q1
|
||||||
|
-- >>> parsewith (quarterspanp (fromGregorian 2018 01 01) <* eof) "Q1"
|
||||||
|
-- Right DateSpan 2018Q1
|
||||||
|
-- >>> parsewith (quarterspanp (fromGregorian 2018 01 01) <* eof) "2020q4"
|
||||||
|
-- Right DateSpan 2020Q4
|
||||||
|
quarterspanp :: Day -> TextParser m DateSpan
|
||||||
|
quarterspanp rdate = do
|
||||||
|
(y,q) <- quarterp rdate
|
||||||
|
return . periodAsDateSpan $ QuarterPeriod y q
|
||||||
|
|
||||||
|
-- | Parse a quarter (YYYYqN or qN, case insensitive q) as its start date.
|
||||||
|
--
|
||||||
|
-- >>> parsewith (quarterstartp (fromGregorian 2025 02 02) <* eof) "q1"
|
||||||
|
-- Right 2025-01-01
|
||||||
|
-- >>> parsewith (quarterstartp (fromGregorian 2025 02 02) <* eof) "Q2"
|
||||||
|
-- Right 2025-04-01
|
||||||
|
-- >>> parsewith (quarterstartp (fromGregorian 2025 02 02) <* eof) "2025q4"
|
||||||
|
-- Right 2025-10-01
|
||||||
|
quarterstartp :: Day -> TextParser m Day
|
||||||
|
quarterstartp rdate = do
|
||||||
|
(y,q) <- quarterp rdate
|
||||||
|
return $
|
||||||
|
fromMaybe (error' "Hledger.Data.Dates.quarterstartp: invalid date found") $ -- PARTIAL, shouldn't happen
|
||||||
|
periodStart $ QuarterPeriod y q
|
||||||
|
|
||||||
|
smartdateorquarterstartp :: Day -> TextParser m SmartDate
|
||||||
|
smartdateorquarterstartp rdate = choice' [SmartCompleteDate <$> quarterstartp rdate, smartdate]
|
||||||
|
|
||||||
nulldatespan :: DateSpan
|
nulldatespan :: DateSpan
|
||||||
nulldatespan = DateSpan Nothing Nothing
|
nulldatespan = DateSpan Nothing Nothing
|
||||||
|
|||||||
@ -4954,17 +4954,24 @@ during January 2020 (the smallest common period, with the -p overriding -b and -
|
|||||||
In hledger's user interfaces (though not in the journal file), you can optionally use "smart date" syntax.
|
In hledger's user interfaces (though not in the journal file), you can optionally use "smart date" syntax.
|
||||||
Smart dates can be written with english words, can be relative, and can have parts omitted.
|
Smart dates can be written with english words, can be relative, and can have parts omitted.
|
||||||
Missing parts are inferred as 1, when needed.
|
Missing parts are inferred as 1, when needed.
|
||||||
Smart dates can be interpreted as dates or periods depending on context.
|
Smart dates can be interpreted as dates or periods depending on the context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
`2004-01-01`, `2004/10/1`, `2004.9.1`, `20240504`
|
`2004-01-01`, `2004/10/1`, `2004.9.1`, `20240504`, `2024Q1`
|
||||||
:\
|
:\
|
||||||
Exact dates. The year must have at least four digits, the month must be 1-12, the day must be 1-31, the separator can be `-` or `/` or `.` or nothing.
|
Exact dates. The year must have at least four digits, the month must be 1-12, the day must be 1-31, the separator can be `-` or `/` or `.` or nothing.
|
||||||
|
The q can be upper or lower case and the quarter number must be 1-4.
|
||||||
|
|
||||||
`2004-10`
|
`2004-10`
|
||||||
: start of month
|
: start of month
|
||||||
|
|
||||||
|
`2004q3`
|
||||||
|
: start of third quarter of 2004
|
||||||
|
|
||||||
|
v`q3`
|
||||||
|
: start of third quarter of current year
|
||||||
|
|
||||||
`2004`
|
`2004`
|
||||||
: start of year
|
: start of year
|
||||||
|
|
||||||
@ -5047,7 +5054,7 @@ For example, if the journal's last transaction is on february 20th,
|
|||||||
|
|
||||||
- `hledger register` will end the report on february 20th.
|
- `hledger register` will end the report on february 20th.
|
||||||
- `hledger register --monthly` will end the report at the end of february.
|
- `hledger register --monthly` will end the report at the end of february.
|
||||||
- `hledger register --monthly --end 2/14` also will end the report at the end of february.
|
- `hledger register --monthly --end 2/14` also will end the report at the end of february (overriding the requested end date).
|
||||||
- `hledger register --monthly --begin 1/5 --end 2/14` will end the report on march 4th [1].
|
- `hledger register --monthly --begin 1/5 --end 2/14` will end the report on march 4th [1].
|
||||||
|
|
||||||
[1] Since hledger 1.29.
|
[1] Since hledger 1.29.
|
||||||
|
|||||||
@ -247,3 +247,57 @@ $ hledger -f- reg mondays --forecast=2024
|
|||||||
2024-01-08 (mondays) 1 1
|
2024-01-08 (mondays) 1 1
|
||||||
2024-02-12 (mondays) 1 2
|
2024-02-12 (mondays) 1 2
|
||||||
|
|
||||||
|
# ** 20. using quarters as start/end dates
|
||||||
|
<
|
||||||
|
2025-06-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2025-07-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2025-08-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2025-09-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2025-10-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2025-11-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2025-12-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2026-01-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2026-02-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2026-03-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2026-04-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2026-05-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
2026-06-01
|
||||||
|
(a) 1
|
||||||
|
|
||||||
|
$ hledger -f - reg --today=2025 -p q4-2026q2
|
||||||
|
2025-10-01 (a) 1 1
|
||||||
|
2025-11-01 (a) 1 2
|
||||||
|
2025-12-01 (a) 1 3
|
||||||
|
2026-01-01 (a) 1 4
|
||||||
|
2026-02-01 (a) 1 5
|
||||||
|
2026-03-01 (a) 1 6
|
||||||
|
|
||||||
|
# ** 21. using a quarter as the period
|
||||||
|
$ hledger -f - --forecast=2025 reg --today=2025 -p q4
|
||||||
|
2025-10-01 (a) 1 1
|
||||||
|
2025-11-01 (a) 1 2
|
||||||
|
2025-12-01 (a) 1 3
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user