I’ve just realized and thought it might be interesting for other community members as well that for the date function there is no invalid day parameter because the function handles overflow in the following way:
daml> date 2019 Feb 29
2019-03-01
daml> date 2020 Aug 32
2020-09-01
I came across this behavior when I set out to implement a function which adds years to a date preserving month and day and started to think about leap year handling.
The simple solution for this is that you don’t care about leap years:
addYearsToDate : Int -> Date -> Date
addYearsToDate yearsToAdd initDate =
date resultYear initMonth initDay
where
(initYear, initMonth, initDay) = toGregorian initDate
resultYear = initYear + yearsToAdd
yearsToAddTest = scenario do
assert $ isLeapYear 2020
assert $ addYearsToDate 4 (date 2020 Feb 29) == date 2024 Feb 29
assert $ addYearsToDate 5 (date 2020 Feb 29) == date 2025 Feb 29
assert $ addYearsToDate 5 (date 2020 Feb 29) == date 2025 Mar 1
If I add 5 years to 2020 Feb 29, the result is 2025 Mar 1.
I wonder whether these are standard algorithms, or are adapted from some other Standard Library. Does anyone here know the source of all the magic numbers?
It turns out that according to Hungarian law, the simple leap year handling cited above is not OK, I had to implement a more complex function for this:
addYearsToDate : Int -> Date -> Date
addYearsToDate yearsToAdd initDate =
if (not . isLeapYear) resultYear &&
initMonth == Feb &&
initDay == 29
then date resultYear Feb 28
else date resultYear initMonth initDay
where
(initYear, initMonth, initDay) = toGregorian initDate
resultYear = initYear + yearsToAdd
addingYearsTest = scenario do
assert $ isLeapYear 2020
assert $ addYearsToDate 4 (date 2020 Feb 29) == date 2024 Feb 29
assert $ addYearsToDate 5 (date 2020 Feb 29) == date 2025 Feb 28
-- Given the three values (year, month, day), constructs a Date
-- value. date (y, m, d) turns the year y, month m, and day d
-- into a Date value.
date: Int -> Month -> Int -> Date
does not mention any kind of “overflow” behaviour, and I would be surprised by it. I would much prefer if it threw an error on invalid inputs.
@bernhard I think we should update either the documentation to mention the overflow behaviour, or the implementation to throw on invalid inputs. Happy to do either.
Yes, makes sense. I guess the easiest way is to convert to check that calling toGregorian on the result of date gives the original inputs. Quite wasteful, though. Mind opening a ticket for discussion?