PHP DateTime()

Like so?

	$march = new DateTime('2012-03-15');
	echo $march->format('Y-m-d') . "<br />"; // 2012-03-15
	$february = $march->modify('-1 month');
	echo $february->format('Y-m-d') . "<br />"; // 2012-02-15
	$january = $february->modify('-1 month');
	echo $january->format('Y-m-d') . "<br />"; // 2012-01-15
	$december = $january->modify('-1 month');
	echo $december->format('Y-m-d') . "<br />"; // 2011-12-15

This will continue to work so long as you don’t need to use 29, 30, or 31. If you need to use any of those dates, then you are better off choosing the last day like my prior examples showed.

except he doesnt want the last day. He wants a string literal that can be a non-existant date. (Why, i’m not sure, but what the hell)

So lets tweak this slightly.


$day = array_pop(explode('-',$suppliedDate));
$curr = new DateTime('2012-03-15');
$prev = $curr->modify('-1 month');
echo $prev->format('Y-m-').$day;

EDIT: Except that also wont work. Okay, lets go archaic.


$date = explode('-',$suppliedDate);
$date[1]--;
if($date[1] == 0) {
 $date[0]--;
 $date[1] = 12;
}
$date = implode('-',$date);

That’ll work as long as you dont try and input year 0.

That probably would work, as so long as you don’t initialize your DateTime with 29, 30 or 31 as the day you are fine, you can still output the $day of 29, 30 or 31 appended to the Y and m output. Using 1-28 hard coded for the day for calculation purposes guarantees the -1 month will equate to what he wants and he just has to append the correct day.

$dateparts = explode('-',$suppliedDate);
$day = array_pop($dateparts);
$curr = new DateTime(implode('-', $dateparts) . '-15');
$prev = $curr->modify('-1 month');
echo $prev->format('Y-m-').$day; 

The only issue I see is this is only useful if you can use the dates to retrieve data, and 2012-02-30 or 2012-02-31 will not work in ANY database nor will 2012-04-31, etc (which is why I made the last day argument for days that need be 29, 30, or 31).

Yeah, you can never insert those dates into a type-checked field, but if you’re doing simple comparisons (SELECT X where date BETWEEN Y and Z) it… should work…

I think a lot of these troubles stem from a flaw in the requirements. Most notably:

“they still need to roll all the way back to what would be exactly 1 month ago.”

This is flawed because, of course, there’s no such thing as exactly 1 month, and trying to pretend otherwise is forcing you to attempt computing from or to some invalid dates.

Since this is supposed to match a billing cycle, it’s probably worthwhile to ask your billing department how they handle those edge cases. Ask them if Jan 31 +1 month should be Feb 28 or Mar 3? Or if Feb 28 -1 month should be Jan 28 or Jan 31? Etc.

It’s a little interesting because, based on results you’ve posted, .NET behavior will match common sense but break mathematics. For example:

2012-03-31 -1 month returns 2012-02-29, and 2012-02-29 +1 month returns 2012-03-29. In other words, x + 1 -1 != x.

Yeah, I know. I had to run it three times to verify I didn’t flub it up with some stupid mistake, but it does indeed do that. sigh, not as infallible as I was led to believe, but, I do appreciate the fact that like PHP, it does give you tools to get better validity when you need it (such as getting the last day in a given month consistently).

Off Topic:

Stupid calendar makers.

Fer realz.

What I’m doing is running a daily batch of usage per billing cycle. A customer’s billing cycle can land on any day (1-31). Daily I need to calculate their usage for their bill cycle. So today, the 25th, I’d be calculating for everyone whos bill cycle is on the 25th (Jan - Dec). On the 28th, I will run 4 seperate batches for the 28th, 29th, 30th and 31st to handle months that do not have certain days. This is why I need to be able to run on fictional dates…

I’m going to use literal strings in this function to avoid any unwanted side effects, and handle the year by hand. I’ll post the code when I have completed.

Ugly but whatever:


//bill cycle date to run for
$day = 25;
$month = 1;
$year = 2013;
if($month == 1) {
    $startDate = ($year-1) . '-12-' . $day;
    $endDate = $year . '-' . $month . '-' . $day;
} else {
    $startDate = $year . '-' . ($month-1) . '-' . $day;
    $endDate = $year . '-' . $month . '-' . $day;
}