PHP DateTime()

Im sick, and it’s been a long day, so if this is painfully obvious which Im sure it is, I’m sorry.

This echos nothing:


$cycleDate = new DateTime('2012-12-30');
//var_dump($cycleDate);
echo $cycleDate->date;

This echos both the var dump, annnndd $cycleDate->date.


$cycleDate = new DateTime('2012-12-30');
var_dump($cycleDate);
echo $cycleDate->date;

Why cant I access $cycleDate->date without a var dump being done first?

Furthermore Iwould like to express my dissatisfaction for PHPs DateTime classes. I’m finding it most difficult just to manipulate the object to remove 1 month from the date.

Not quite sure why it pumps stuff out when you do the vardump, but I’m pretty sure you’re supposed to use: $datetime->format(); to get the date:

eg:


        $cycleDate = new DateTime('2012-12-30');
        echo $cycleDate->format('Y-m-d H:i:s');
//will echo: 2012-12-30 00:00:00

You need to use the DateInterval class: http://php.net/manual/en/dateinterval.format.php

Eg:


        $cycleDate = new DateTime('2012-12-30');
        echo $cycleDate->format('Y-m-d H:i:s'); //will output 2012-12-30 00:00:00

        $monthInterval = new DateInterval('P1M');

        $cycleDate->sub($monthInterval);
        echo $cycleDate->format('Y-m-d H:i:s'); //will output 2012-11-30 00:00:00

*Edit: the “P” in the DateInterval constructor relates to “period” ie “Period 1 Month”.

Ya so the problem with this is Feb:

$cycleDate = new DateTime(‘2012-02-30’);

While I know 2/30 does not exist, I still need it to return 2012-01-30 as the -1 month value

Then I guess the logic you are looking for is slightly different? I’m not sure what your use-case is here? Can you explain what you want a bit more?

Obviously, there is no 30th Feb, so when you try to set that date, php sets it to the 1st of March, meaning you’d end up with the 1st Feb in your calculations. I don’t think it’s php that’s wrong here though - it’s just that your logic is different to actually subtracting a month from another month, for whatever reason.

Can you explain your use-case in more detail?

No PHPs treating it correctly in most cases. I’m sure I’d be way past this if the flu didn’t have me down. I’m writing a script for billing cycle statistics. What happened during their previous billing cycle. So on the 28th of every month, I’ll be kicking off this calculation for the 28th, 29th, 30th and 31st because not every month has those, however they still need to roll all the way back to what would be exactly 1 month ago according to their bill cycle.

Personally, I like the following approach, it got me the best results:

<?php
	$march = new DateTime('2012-03-31');
	echo $march->format('Y-m-d') . "<br />";
	$february = $march->modify('last day of previous month');
	echo $february->format('Y-m-d') . "<br />";
	$january = $february->modify('last day of previous month');
	echo $january->format('Y-m-d') . "<br />";
?>

Whereas, this was just wrong!

<?php
	$oneMonth = new DateInterval('P1M');
	$march = new DateTime('2012-03-31');
	echo $march->format('Y-m-d') . "<br />";
	$february = $march->sub($oneMonth);
	echo $february->format('Y-m-d') . "<br />";
	$january = $february->sub($oneMonth);
	echo $january->format('Y-m-d') . "<br />";
?>

It’s crazy that the above works - I had no idea you could type a string like that in the datetime class. It’s quite cool I guess…

Yeah, I remember first finding that out, and I was like COOL! but why can’t I just use a DateInterval, so I tried DateInterval and went sigh that’s why…

Oh! and before I forget to mention this because it truly is a pain. Both ->sub and ->modify return an instance of DateTime and MODIFY the existing object!

So if you did this after my above examples:

	echo "<br />";
	echo $march->format('Y-m-d') . "<br />";
	echo $february->format('Y-m-d') . "<br />";
	echo $january->format('Y-m-d') . "<br />";

They will ALL output the same date! Keep that in mind.

Someone recommended this to me recently: https://github.com/briannesbitt/Carbon

Thought it might be worth sharing :slight_smile:

Neat! I’ll play with that tomorrow to see how well it handles edge cases.

Drats! I had hoped it may have inherited DateTime to fix some of the idiosyncrasies, but alas it doesn’t, it just adds additional methods. Not really worth the footprint (in my opinion, but neat nonetheless). This is where I wish PHP took a lesson from the .NET framework. Subtracting one month from 2012-03-31 produces 2012-02-29 appropriately in .NET, PHP instead calculates 2012-02-31 and then pushes that to 2012-03-02, which is annoying.

I wound up just passing $day and $month into a function. Thanks for the go at it cp, though it doesn’t do what I need it to. (ex. Feb 15th should return Jan 15th.) This was pretty much just me complaining about my inability to work through anything on a flu :slight_smile:

Except 2012-02-29 isnt one month back from 2012-03-31… so…

I disagree wholeheartedly. When you subtract a month, you expect to be in the previous month, not still in the current month. Saying 2012-03-31 minus a month is 2012-03-02 is misleading at best. You should be subtracting the 31 days of March to get to February 29th, as that is the month you are trying to lose. When you are 2012-02-29 and you subtract a month, you should remove the 29 days of February to get to January 31st.

The 29th of February would be 1 month prior to the end of March. Remember a Month is not a fixed interval, the size of a month differs by month and by year (in case of leap year).

You may be able to use $date->modify(‘15 of previous month’); but I haven’t tested that to be sure.

So by your math, A month before May 30 is April 29th. Uhm… what? (31 days in May)
Think you meant you need to subtract the number of days in the -preceding- month. Which is how you end up with March 2nd.

I see your point, so my basis is flawed to. PHP is currently subtracting the number of days in the preceding month, thus the March 31st minus a month is March 2nd, which is still wrong… So obviously it is a bit more complex than that too.

This is why I agree with .NET:
[highlight=c#]var march = new DateTime(2012, 03, 31);
var may = new DateTime(2012, 05, 31);
Console.WriteLine(may.ToString(“yyyy-MM-dd”));
var april = may.AddMonths(-1);
Console.WriteLine(april.ToString(“yyyy-MM-dd”));

var march = new DateTime(2012, 03, 31);
Console.WriteLine(march.ToString(“yyyy-MM-dd”));
var february = march.AddMonths(-1);
Console.WriteLine(february.ToString(“yyyy-MM-dd”));
var january = february.AddMonths(-1);
Console.WriteLine(january.ToString(“yyyy-MM-dd”));
Console.ReadKey();
/*
Output:
2012-05-31
2012-04-30
2012-03-31
2012-02-29
2012-01-29 // this one irks me a bit, but it is understandable.
*/



I have to give it to the MS folks, they really made it nice and maybe I just expect other languages to do it as well.

Bah, I’m back to the drawing board on this one. My solution doesn’t have handling for new years. So my requirements again:

$suppliedDate = ‘2013-02-25’; //yyyy-mm-dd
$minus1month === ‘2013-01-25’; //essentially, the day column must never be altered, no matter if any scripting language thinks it makes sense or not.


$suppliedDate = ‘2013-03-29’;
$minus1month === ‘2013-02-29’;


$suppliedDate = ‘2013-01-15’;
$minus1month === ‘2012-12-15’;

I started to replacing the month area by hand in a string, but then I realized it’s not taking care of the year scenario. Should I just write in a case statement, if my starting month is January, subtract from the year?