brick / date-time

Date and time library for PHP
MIT License
335 stars 30 forks source link

`LocalDate::plusMonths` not consistent with `LocalDate::until(LocalDate)` #41

Closed bendavies closed 2 years ago

bendavies commented 2 years ago

Hi there!

I am trying to determine the Period between 2 LocalDates, where I know I have added a x months. I can't seem to get the result I want This is best explained with an example:

$start = LocalDate::of(2022, 01, 31);
$end = $start->plusMonths(3);
echo $start, PHP_EOL, $end, PHP_EOL;
echo $period = $start->until($end), PHP_EOL;
2022-01-31
2022-04-30
P2M30D

I want a period of P3M.

This works fine if the $start date is not on a complicated end of month day :)

$start = LocalDate::of(2022, 01, 1);
$end = $start->plusMonths(3);
echo $start, PHP_EOL, $end, PHP_EOL;
echo $period = $start->until($end), PHP_EOL;
2022-01-01
2022-04-01
P3M

So,

  1. are my results expected, or a bug?
  2. if not a bug, is there a way to get the results I'm after?

Many thanks!

bendavies commented 2 years ago

another simplified example, where the date changes after adding and then removing the same period.

$period = Period::ofMonths(3);
$start = LocalDate::of(2022, 01, 31);
echo $start, PHP_EOL, $start->plusPeriod($period)->minusPeriod($period);
2022-01-31
2022-01-30
BenMorel commented 2 years ago

Hi, this is due to the fact that when adding months, you may end up with a date that does not exist (2022-04-31), so the behaviour in this case is to "resolve" the date to the last day of the month. This is the same behaviour as in the upstream Java library: https://onlinegdb.com/hywm6RVlg.

(For the record, I actually wondered if this was the correct behaviour to adopt when writing brick/date-time; I hesitated with adding the "missing" days, such as 2022-04-31 => 2022-05-01, but as someone pointed out, in this case you would now be adding 4 months and not 3, so this did not make any sense either.)

So it is normal that after the resolution to the last day of the month occurs, you end up with P2M30D, and that applying the operation backwards does not give you the original date. I don't know if this can be fixed at all, especially if the behaviour is the same for the Java library.

BenMorel commented 2 years ago

Java result for your original example: https://www.onlinegdb.com/IaDzOB08F

import java.time.LocalDate;

public class Main {
    public static void main(String args[]) {
      LocalDate start = LocalDate.parse("2022-01-31");
      LocalDate end = start.plusMonths(3);

      System.out.println(start.until(end));
    }
}

P2M30D

bendavies commented 2 years ago

hi @BenMorel , thanks for the reply!

Hi, this is due to the fact that when adding months, you may end up with a date that does not exist (2022-04-31), so the behaviour in this case is to "resolve" the date to the last day of the month.

Yep was aware of this, but wasn't sure of the correct behavior when "reversing" from end back to start. Thanks for pointing out that the behavior here matches java.

This example also matches, where the start date doesn't match after adding and subtracting 3 months: https://www.onlinegdb.com/OwlxydgR8

Guess we can close here since we can't really argue that matching java's logic is wrong!

Thanks again.