Strange (to me) behaviour of the CalendarLib


I started using the CalendarLib library and while it will definitely make do for my needs, I discovered something strange, which I want to discuss here. Maybe there is something I don’t understand, maybe this behaviour is wrong.

I discovered that if I use Date.add to add one month to a date, if my initial date is at the end of a long month, I may end up not in the next month, but one month after that, for example

utop # let start = Date.make 2018 1 31 in
Date.add start (Date.Period.month 1) |> Printer.Date.sprint "%F";;
- : string = "2018-03-03"

Because I need a slightly different behavior, I implemented this for my own use:

module DateUtil = struct
  open CalendarLib

  let to_string date = Printer.Date.sprint "%F" date

  let first_of_month date =
    Date.(make (year date) (int_of_month (month date)) 1)

  (* Makes sure to stay within the boundaries  of the next month.
     So if called on 2018-01-31, it will return 2018-02-28 *)
  let plus_one_month date =
    let open Date in
    let one_month = Period.month 1 in
    let first_of_this_month = first_of_month date in
    let first_of_next_month = add first_of_this_month one_month in
    let last_of_next_month =
      make (year first_of_next_month)
        (int_of_month (month first_of_next_month))
        (days_in_month first_of_next_month)
    let after_one_month = add date one_month in
    if compare after_one_month last_of_next_month > 0 then last_of_next_month
    else after_one_month

When called with 2018-01-31 it works like this:

utop # DateUtil.to_string (DateUtil.plus_one_month (CalendarLib.Date.make 2018 1 31));;
- : string = "2018-02-28"

But then I noticed the odd thing… CalendarLib doesn’t behave like above when called from the beginning of the month:

utop # let start = Date.make 2018 2 1 in
Date.add start (Date.Period.month 1) |> Printer.Date.sprint "%F";;
- : string = "2018-03-01"

If it were consistent, I would expect to get something like “2018-03-04”…
So, what’s going on here? Are there some rules about date arithmetic that I’m not aware of?


I ran into this a few years ago. The rules used by the library are described in the intro section here:


Oh wow, so then it always converts to the same day of the next month and coerces if needed.