4
>> $start_dt = new DateTime()
DateTime::__set_state(array(
   'date' => '2012-04-11 08:34:01',
   'timezone_type' => 3,
   'timezone' => 'America/Los_Angeles',
))
>> $end_dt = new DateTime()
DateTime::__set_state(array(
   'date' => '2012-04-11 08:34:06',
   'timezone_type' => 3,
   'timezone' => 'America/Los_Angeles',
))
>> $start_dt->setTimestamp(strtotime('31-Jan-2012'))
DateTime::__set_state(array(
   'date' => '2012-01-31 00:00:00',
   'timezone_type' => 3,
   'timezone' => 'America/Los_Angeles',
))
>> $end_dt->setTimestamp(strtotime('1-Mar-2012'))
DateTime::__set_state(array(
   'date' => '2012-03-01 00:00:00',
   'timezone_type' => 3,
   'timezone' => 'America/Los_Angeles',
))
>> $interval = $start_dt->diff($end_dt)
DateInterval::__set_state(array(
   'y' => 0,
   'm' => 0,
   'd' => 30,
   'h' => 0,
   'i' => 0,
   's' => 0,
   'invert' => 0,
   'days' => 30,
))
>> $interval->format('%mm %dd')
'0m 30d'

i.e., 31-Jan-2012 to 1-Mar-2012 yields less than a month! I'd expect the output to be 1 month, 1 day. It shouldn't matter the number of days in February; that's the point of using a time library -- it's supposed to handle these things. WolframAlpha agrees.

Should I file a bug to PHP? Is there a hack/fix/workaround to get months to work as expected?

mpen
  • 272,448
  • 266
  • 850
  • 1,236

2 Answers2

6

Updated answer

This behavior of DateTime::diff is certainly unexpected, but it's not a bug. In a nutshell, diff returns years, months, days etc such that if you did

$end_ts = strtotime('+$y years +$m months +$d days' /* etc */, $start_ts);

you would get back the timestamp that corresponds to end original end date.

These additions are performed "blindly" and then date correction applies (e.g. Jan 31 + 1 month would be Feb 31, corrected to Mar 2 or Mar 3 depending on the year). In this specific example you cannot add even one month as salathe also explains.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • 1st December shows `10` months in the interval ([example](http://codepad.viper-7.com/8wgJI2)). – salathe Apr 11 '12 at 16:00
  • 1
    I'd argue that months and years don't have a set number of days. If you *ask* it for months, it should count the number of *calendar* months betweens between the two dates; same for years. *That* is the advantage of using a library; if I wanted an "objective" (read: consistent) number of days, I'd just take my two timestamps and divide by [2629743.83](https://www.google.ca/search?sourceid=chrome&ie=UTF-8&q=number+of+seconds+in+a+month). – mpen Apr 11 '12 at 16:01
  • I see what you're saying now though...makes more sense how it came up with that. Bummer. I guess I have to come up with a different solution to my problem; perhaps I'll just take the month number and subtract it out. – mpen Apr 11 '12 at 16:09
5

Should I file a bug to PHP?

No.

The "month" part of the interval means that the month part of the start date can be incremented by that many months. The behaviour in PHP, taking your start date of 31-Jan-2012 and incrementing the month (literally, 31-Feb-2012) and then correcting for a valid date (PHP does this for you) would give 02-Mar-2012 which is later than the target date that you are working with.

To demonstrate this, take your start date and add n months for a few months to see the behaviour.

31-Jan-2012 (Interval)
02-Mar-2012 (P1M)
31-Mar-2012 (P2M)
01-May-2012 (P3M)
31-May-2012 (P4M)
01-Jul-2012 (P5M)

You can see that the month is being incremented, then adjusted to make a valid date.

salathe
  • 51,324
  • 12
  • 104
  • 132
  • +1 because this is the correct answer -- I had come across this behavior in the past and due to less-than-stellar documentation did not find out about this. Thanks for shining the light. :) – Jon Apr 11 '12 at 16:01