imp: smartdates: Simplify and generalise the SmartDate constructor and
parsers to allow for arbitrary numbers of periods in relative dates. We now accept smart dates like “in 5 days, 5 weeks ahead, in -6 months, 2 quarters ago”.
This commit is contained in:
parent
5c9436a3ed
commit
f13ac2812f
@ -12,7 +12,8 @@ Date parsing and utilities for hledger.
|
||||
For date and time values, we use the standard Day and UTCTime types.
|
||||
|
||||
A 'SmartDate' is a date which may be partially-specified or relative.
|
||||
Eg 2008\/12\/31, but also 2008\/12, 12\/31, tomorrow, last week, next year.
|
||||
Eg 2008\/12\/31, but also 2008\/12, 12\/31, tomorrow, last week, next year,
|
||||
in 5 days, in -3 quarters.
|
||||
We represent these as a triple of strings like (\"2008\",\"12\",\"\"),
|
||||
(\"\",\"\",\"tomorrow\"), (\"\",\"last\",\"week\").
|
||||
|
||||
@ -104,7 +105,7 @@ import Data.Time.LocalTime (getZonedTime, localDay, zonedTimeToLocalTime)
|
||||
import Safe (headMay, lastMay, maximumMay, minimumMay)
|
||||
import Text.Megaparsec
|
||||
import Text.Megaparsec.Char (char, char', digitChar, string, string')
|
||||
import Text.Megaparsec.Char.Lexer (decimal)
|
||||
import Text.Megaparsec.Char.Lexer (decimal, signed)
|
||||
import Text.Megaparsec.Custom (customErrorBundlePretty)
|
||||
import Text.Printf (printf)
|
||||
|
||||
@ -396,21 +397,11 @@ spanFromSmartDate refdate sdate = DateSpan (Just b) (Just e)
|
||||
(ry,rm,_) = toGregorian refdate
|
||||
(b,e) = span sdate
|
||||
span :: SmartDate -> (Day,Day)
|
||||
span (SmartRelative This Day) = (refdate, nextday refdate)
|
||||
span (SmartRelative Last Day) = (prevday refdate, refdate)
|
||||
span (SmartRelative Next Day) = (nextday refdate, addDays 2 refdate)
|
||||
span (SmartRelative This Week) = (thisweek refdate, nextweek refdate)
|
||||
span (SmartRelative Last Week) = (prevweek refdate, thisweek refdate)
|
||||
span (SmartRelative Next Week) = (nextweek refdate, startofweek $ addDays 14 refdate)
|
||||
span (SmartRelative This Month) = (thismonth refdate, nextmonth refdate)
|
||||
span (SmartRelative Last Month) = (prevmonth refdate, thismonth refdate)
|
||||
span (SmartRelative Next Month) = (nextmonth refdate, startofmonth $ addGregorianMonthsClip 2 refdate)
|
||||
span (SmartRelative This Quarter) = (thisquarter refdate, nextquarter refdate)
|
||||
span (SmartRelative Last Quarter) = (prevquarter refdate, thisquarter refdate)
|
||||
span (SmartRelative Next Quarter) = (nextquarter refdate, startofquarter $ addGregorianMonthsClip 6 refdate)
|
||||
span (SmartRelative This Year) = (thisyear refdate, nextyear refdate)
|
||||
span (SmartRelative Last Year) = (prevyear refdate, thisyear refdate)
|
||||
span (SmartRelative Next Year) = (nextyear refdate, startofyear $ addGregorianYearsClip 2 refdate)
|
||||
span (SmartRelative n Day) = (addDays n refdate, addDays (n+1) refdate)
|
||||
span (SmartRelative n Week) = let d = thisweek refdate in (addDays (7*n) d, addDays (7*n+7) d)
|
||||
span (SmartRelative n Month) = let d = thismonth refdate in (addGregorianMonthsClip n d, addGregorianMonthsClip (n+1) d)
|
||||
span (SmartRelative n Quarter) = let d = thisquarter refdate in (addGregorianMonthsClip (3*n) d, addGregorianMonthsClip (3*n+3) d)
|
||||
span (SmartRelative n Year) = let d = thisyear refdate in (addGregorianYearsClip n d, addGregorianYearsClip (n+1) d)
|
||||
span (SmartAssumeStart y Nothing) = (startofyear day, nextyear day) where day = fromGregorian y 1 1
|
||||
span (SmartAssumeStart y (Just (m, Nothing))) = (startofmonth day, nextmonth day) where day = fromGregorian y m 1
|
||||
span (SmartAssumeStart y (Just (m, Just d))) = (day, nextday day) where day = fromGregorian y m d
|
||||
@ -510,28 +501,28 @@ fixSmartDateStrEither' d s = case parsewith smartdateonly (T.toLower s) of
|
||||
-- t "next january"
|
||||
-- "2009-01-01"
|
||||
--
|
||||
-- >>> t "in 5 days"
|
||||
-- "2008-12-01"
|
||||
-- >>> t "in 7 months"
|
||||
-- "2009-06-01"
|
||||
-- >>> t "in -2 weeks"
|
||||
-- "2008-11-10"
|
||||
-- >>> t "1 quarter ago"
|
||||
-- "2008-07-01"
|
||||
-- >>> t "1 week ahead"
|
||||
-- "2008-12-01"
|
||||
fixSmartDate :: Day -> SmartDate -> Day
|
||||
fixSmartDate refdate = fix
|
||||
where
|
||||
fix :: SmartDate -> Day
|
||||
fix (SmartRelative This Day) = refdate
|
||||
fix (SmartRelative Last Day) = prevday refdate
|
||||
fix (SmartRelative Next Day) = nextday refdate
|
||||
fix (SmartRelative This Week) = thisweek refdate
|
||||
fix (SmartRelative Last Week) = prevweek refdate
|
||||
fix (SmartRelative Next Week) = nextweek refdate
|
||||
fix (SmartRelative This Month) = thismonth refdate
|
||||
fix (SmartRelative Last Month) = prevmonth refdate
|
||||
fix (SmartRelative Next Month) = nextmonth refdate
|
||||
fix (SmartRelative This Quarter) = thisquarter refdate
|
||||
fix (SmartRelative Last Quarter) = prevquarter refdate
|
||||
fix (SmartRelative Next Quarter) = nextquarter refdate
|
||||
fix (SmartRelative This Year) = thisyear refdate
|
||||
fix (SmartRelative Last Year) = prevyear refdate
|
||||
fix (SmartRelative Next Year) = nextyear refdate
|
||||
fix (SmartAssumeStart y md) = fromGregorian y (maybe 1 fst md) (fromMaybe 1 $ snd =<< md)
|
||||
fix (SmartFromReference m d) = fromGregorian ry (fromMaybe rm m) d
|
||||
fix (SmartMonth m) = fromGregorian ry m 1
|
||||
fix (SmartRelative n Day) = addDays n refdate
|
||||
fix (SmartRelative n Week) = addDays (7*n) $ thisweek refdate
|
||||
fix (SmartRelative n Month) = addGregorianMonthsClip n $ thismonth refdate
|
||||
fix (SmartRelative n Quarter) = addGregorianMonthsClip (3*n) $ thisquarter refdate
|
||||
fix (SmartRelative n Year) = addGregorianYearsClip n $ thisyear refdate
|
||||
fix (SmartAssumeStart y md) = fromGregorian y (maybe 1 fst md) (fromMaybe 1 $ snd =<< md)
|
||||
fix (SmartFromReference m d) = fromGregorian ry (fromMaybe rm m) d
|
||||
fix (SmartMonth m) = fromGregorian ry m 1
|
||||
(ry, rm, _) = toGregorian refdate
|
||||
|
||||
prevday :: Day -> Day
|
||||
@ -554,7 +545,6 @@ startofmonth day = fromGregorian y m 1 where (y,m,_) = toGregorian day
|
||||
nthdayofmonth d day = fromGregorian y m d where (y,m,_) = toGregorian day
|
||||
|
||||
thisquarter = startofquarter
|
||||
prevquarter = startofquarter . addGregorianMonthsClip (-3)
|
||||
nextquarter = startofquarter . addGregorianMonthsClip 3
|
||||
startofquarter day = fromGregorian y (firstmonthofquarter m) 1
|
||||
where
|
||||
@ -726,6 +716,8 @@ Examples:
|
||||
> october, oct (start of month in current year)
|
||||
> yesterday, today, tomorrow (-1, 0, 1 days from today)
|
||||
> last/this/next day/week/month/quarter/year (-1, 0, 1 periods from the current period)
|
||||
> in n days/weeks/months/quarters/years (n periods from the current period)
|
||||
> n days/weeks/months/quarters/years ago (-n periods from the current period)
|
||||
> 20181201 (8 digit YYYYMMDD with valid year month and day)
|
||||
> 201812 (6 digit YYYYMM with valid year and month)
|
||||
|
||||
@ -763,19 +755,26 @@ Right (SmartAssumeStart 201813012 Nothing)
|
||||
smartdate :: TextParser m SmartDate
|
||||
smartdate = choice'
|
||||
-- XXX maybe obscures date errors ? see ledgerdate
|
||||
[ yyyymmdd, ymd
|
||||
[ relativeP
|
||||
, yyyymmdd, ymd
|
||||
, (\(m,d) -> SmartFromReference (Just m) d) <$> md
|
||||
, failIfInvalidDate . SmartFromReference Nothing =<< decimal
|
||||
, SmartMonth <$> (month <|> mon)
|
||||
, SmartRelative This Day <$ string' "today"
|
||||
, SmartRelative Last Day <$ string' "yesterday"
|
||||
, SmartRelative Next Day <$ string' "tomorrow"
|
||||
, liftA2 SmartRelative (seqP <* skipNonNewlineSpaces) intervalP
|
||||
, SmartRelative 0 Day <$ string' "today"
|
||||
, SmartRelative (-1) Day <$ string' "yesterday"
|
||||
, SmartRelative 1 Day <$ string' "tomorrow"
|
||||
]
|
||||
where
|
||||
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"]
|
||||
relativeP = do
|
||||
optional $ string' "in" <* skipNonNewlineSpaces
|
||||
num <- seqP <* skipNonNewlineSpaces
|
||||
interval <- intervalP <* skipNonNewlineSpaces
|
||||
sign <- choice [negate <$ string' "ago", id <$ string' "ahead", pure id]
|
||||
return $ SmartRelative (sign num) interval
|
||||
|
||||
seqP = choice [ 0 <$ string' "this", -1 <$ string' "last", 1 <$ string' "next", signed skipNonNewlineSpaces decimal ]
|
||||
intervalP = choice [ Day <$ string' "day", Week <$ string' "week", Month <$ string' "month"
|
||||
, Quarter <$ string' "quarter", Year <$ string' "year" ] <* optional (char' 's')
|
||||
|
||||
-- | Like smartdate, but there must be nothing other than whitespace after the date.
|
||||
smartdateonly :: TextParser m SmartDate
|
||||
|
||||
@ -263,7 +263,7 @@ checkPeriodicTransactionStartDate i s periodexpr =
|
||||
_ -> Nothing
|
||||
where
|
||||
checkStart d x =
|
||||
let firstDate = fixSmartDate d $ SmartRelative This x
|
||||
let firstDate = fixSmartDate d $ SmartRelative 0 x
|
||||
in
|
||||
if d == firstDate
|
||||
then Nothing
|
||||
|
||||
@ -84,10 +84,9 @@ data SmartDate
|
||||
= SmartAssumeStart Year (Maybe (Month, Maybe MonthDay))
|
||||
| SmartFromReference (Maybe Month) MonthDay
|
||||
| SmartMonth Month
|
||||
| SmartRelative SmartSequence SmartInterval
|
||||
| SmartRelative Integer SmartInterval
|
||||
deriving (Show)
|
||||
|
||||
data SmartSequence = Last | This | Next deriving (Show)
|
||||
data SmartInterval = Day | Week | Month | Quarter | Year deriving (Show)
|
||||
|
||||
data WhichDate = PrimaryDate | SecondaryDate deriving (Eq,Show)
|
||||
|
||||
@ -461,6 +461,9 @@ Examples:
|
||||
| `october, oct` | start of month in current year |
|
||||
| `yesterday, today, tomorrow` | -1, 0, 1 days from today |
|
||||
| `last/this/next day/week/month/quarter/year` | -1, 0, 1 periods from the current period |
|
||||
| `in n days/weeks/months/quarters/years` | n periods from the current period |
|
||||
| `n days/weeks/months/quarters/years ahead` | n periods from the current period |
|
||||
| `n days/weeks/months/quarters/years ago` | -n periods from the current period |
|
||||
| `20181201` | 8 digit YYYYMMDD with valid year month and day |
|
||||
| `201812` | 6 digit YYYYMM with valid year and month |
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user