3

It seems that you can't use strotime if you need reliable and accurate date manipulation. For example, if the month has 31 days, then it appears that strtotime simply minuses 30 days, not a whole month.

So, for example, if $event["EndDate"] is equal to "2013-10-31 00:00:01", the following code:

echo date("Y/n/j", strtotime('-1 month', strtotime($event["EndDate"]));

Ouputs: 2013/10/1 instead of 2013/09/30.

QUESTION: Now how I know how NOT to do it, is there another, more accurate, way to make PHP subtract (or add) exactly a whole month, and not just 30 days?

vascowhite
  • 18,120
  • 9
  • 61
  • 77
Chuck Le Butt
  • 47,570
  • 62
  • 203
  • 289
  • 5
    If you're working with Events with dates and times, then make life easier for yourself and use DateTime objects – Mark Baker May 08 '13 at 17:18
  • 2
    I think the real problem is that it's subtracting one month from Oct.31, returning Sept.31, which doesn't exist and is converted into Oct.1 instead. If I take your code and feed it Oct.29, I get Sept.29 as expected. – Blazemonger May 08 '13 at 17:20
  • http://stackoverflow.com/questions/9087462/php-strtotime-is-not-returning-the-correct-month – Lawson May 08 '13 at 17:20
  • @Blazemonger that would be severely broken though. I can't believe that `strtotime()` (which isn't really a part of PHP, it's an underlying universal C library) has a flaw like that – Pekka May 08 '13 at 17:21
  • strtotime is handy and sometimes magical, but really... you should NOT use it for date manipulations. there's a very nice DateTime class for that sort of thing, with much more predictable/reliable relative date operations. – Marc B May 08 '13 at 17:22
  • It's not a flaw, it's a feature. There is no Sept.31, which you asked for, so it gives you one day after Sept.30 instead -- Oct.1. – Blazemonger May 08 '13 at 17:22
  • My point is, the function is working perfectly. It's not doing what you want because you're giving the function bad data -- GIGO. – Blazemonger May 08 '13 at 17:24
  • Damn. Why must I continue to learn everything the hard way! I guess the answer is "no", then. I'll look into DateTime objects. – Chuck Le Butt May 08 '13 at 17:24
  • Incidentally, this isn't a dupe. The other question is asking WHY something occurs, I was asking how I might move past it. – Chuck Le Butt May 08 '13 at 17:28
  • @MarkBaker http://3v4l.org/dHYLc `DateTime` has same issue – Baba May 08 '13 at 17:36
  • 2
    As 2013/09/31 does not exist ask yourself what you would expect to be returned. Clearly 31st September would not be an acceptable result from any date/time function in any language. I don't think you could argue that 30th September is any more (in)correct than 1st October in this case. What is important is that the behaviour is consistent and predictable, which I believe it is, although I haven't tested it, although I may do when I get some time as this has piqued my curiosity. – vascowhite May 08 '13 at 17:38
  • I ran those tests, see my answer below. – vascowhite May 08 '13 at 18:31
  • @Baba - I didn't say that DateTime objects would eliminate this problem, that's done by using them correctly; DateTime objects simplify a lot of working with dates and times – Mark Baker May 08 '13 at 19:55

2 Answers2

3

The main issue is that 2013/09/31 does not exist so a better approach would be to use first day or last day of the previous month.

$date = new DateTime("2013-10-31 00:00:01");
$date->modify("last day last month");
echo $date->format("Y/n/j"); // 2013/9/30

When date is 2013-10-15

$date = new DateTime("2013-10-15 00:00:01");
$day = $date->format("d");
$year = $date->format("Y");

$date->modify("last day last month");
$month = $date->format("m");

if (checkdate($month, $day, $year)) {
    $date->setDate($year, $month, $day);
}

echo $date->format("Y/n/j"); // 2013-9-15
Baba
  • 94,024
  • 28
  • 166
  • 217
1

You say:-

It seems that you can't use strotime if you need reliable and accurate date manipulation

You can, you just need to know how it behaves. Your question prompted me to run a couple of tests.


PHP does not merely subtract 30 days from the date when subtracting a month, although it appears that it does from the single case you are looking at. In fact my test here suggests that it adds 31 days to the start of the previous month (the result of 3rd March suggests this to me) in this case.

$thirtyOnedayMonths = array(
    1, 3, 5, 7, 8, 10, 12
);

$oneMonth = new \DateInterval('P1M');
$format = 'Y-m-d';

foreach($thirtyOnedayMonths as $month){
    $date = new \DateTime("2013-{$month}-31 00:00:01");
    var_dump($date->format($format));
    $date->sub($oneMonth);
    var_dump($date->format($format));
}

There are 31 days between 2013-11-12 and 2013-10-12 and PHP calculates the month subtraction correctly as can be seen here.

$date = new \DateTime('2013-11-12 00:00:01');
var_dump($date);
$interval = new DateInterval('P1M');
$date->sub($interval);
var_dump($date);

In your particular case 2013-10-31 - 1 month is 2013-09-31 which does not exist. Any date/time function needs to return a valid date, which 2013-09-31 is not.

In this case PHP, as I stated above, seems to add 31 days to the start of the previous month to arrive at a valid date.

Once you know the expected behaviour, you can program accordingly. If the current behaviour does not fit your use case then you could extend the DateTime class to provide behaviour that works for you.

I have assumed that strtotime and DateTime::sub() behave the same, this test suggests they do.

$thirtyOnedayMonths = array(
    1, 3, 5, 7, 8, 10, 12
);

$format = 'Y-m-d';

foreach($thirtyOnedayMonths as $month){
    $date = date($format, strtotime('-1 month', strtotime("2013-{$month}-31 00:00:01")));
    var_dump($date);
}
vascowhite
  • 18,120
  • 9
  • 61
  • 77