2

I looked at this answer already, and it's quite close to what I have.

Here is my PHP code:

$start = new DateTime('0:00 first day of previous month', new DateTimeZone('UTC'));
/*
if (isset($_GET['year']) && isset($_GET['month']) && checkdate($_GET['month'], 1, $_GET['year'])) {
    $start = DateTime::createFromFormat('Y-m-d', $_GET['year'] . '-' . $_GET['month'] . '-1');
}*/
$middle = DateTime::createFromFormat('U', strtotime('first day of last month', $start->format('U')));
$middle->setTimezone(new DateTimeZone('UTC'));
$end = DateTime::createFromFormat('U', strtotime('first day of 2 months ago', $start->format('U')));
$end->setTimezone(new DateTimeZone('UTC'));

var_dump($start);
var_dump($middle);
var_dump($end);

Today is August 27th, so I would expect July 1, June 1, and May 1. Here's what the actual output is:

object(DateTime)[1]
  public 'date' => string '2013-07-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)

object(DateTime)[2]
  public 'date' => string '2013-05-02 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)

object(DateTime)[3]
  public 'date' => string '2013-04-02 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)

Why is it returning the second day of the months for me?

I've also tried it without the new DateTimeZone('GMT') as the second parameter of the constructor for the initial DateTime but it still gives me the same result, just with different times.

Community
  • 1
  • 1
rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156

2 Answers2

6

this part irrelevant - question was edited

Because of the timezone difference. $start is calculated in the 'Rainy River timezone', while $middle and $end are in UTC time. The 'Rainy River timezone has a -06:00 hour offset from UTC (exactly the difference in hours between the first with the second and third results).

update 1 - solution

It seems the problem lies somewhere around strtotime. For some reason it yields a result with an offset of one day (further explanation needed). A simple solution, is to subtract one second from that date and it will produce the correct result.

$timezone = new DateTimeZone('UTC');
$start = new DateTime('0:00 first day of previous month', $timezone );
$middle = DateTime::createFromFormat('U', strtotime('first day of last month',($start  ->format('U'))-1),$timezone);
echo $middle->format('Y-m-d')."\n";

Result:

2013-05-01

update 2 - reason for problem

Eventually I find out that the problem originates from the instantiation of the fisrt date object. Here is an illustration.

This will give a correct result:

$original = new DateTime('2013-05-01');
echo $original->format('Y-m-d')."\n";

$previous= DateTime::createFromFormat('U', strtotime('first day of last month',($original->format('U'))),new DateTimeZone('UTC'));
echo $previous->format('Y-m-d')."\n";

Result (OK):

2013-05-01
2013-04-01   <--- OK

However, this will not (only first line different, as in the original code):

$original = new DateTime('0:00 first day of previous month', new DateTimeZone('UTC'));
echo $original->format('Y-m-d')."\n";

$previous= DateTime::createFromFormat('U', strtotime('first day of last month',($original->format('U'))),new DateTimeZone('UTC'));
echo $previous->format('Y-m-d')."\n";

Result:

 2013-07-01
 2013-05-02  <--- BAD
ılǝ
  • 3,440
  • 2
  • 33
  • 47
  • I thought that was the problem but I also tried setting all of the times to UTC... see edited post. – rink.attendant.6 Aug 28 '13 at 00:46
  • @rink.attendant.6 is there a reason you are instantiating the dates differently? Why not $start= new DateTime("first day of previous month"); $middle = new DateTime("first day of last month"); $end = new DateTime("first day of 2 months ago"); – ılǝ Aug 28 '13 at 00:58
  • Because as you can see in my commented code, `$start` might be passed through `$_GET` (specifically dropdown boxes), so the `$middle` and `$end` need to be relative to `$start`. – rink.attendant.6 Aug 28 '13 at 00:59
  • +1 for the explanation... gotta learn something new every day! :) – rink.attendant.6 Aug 28 '13 at 01:45
  • 1
    That's why we're here! :) I would still appreciate an input by a person more experienced in php on how/why the date objects behave like this. It seems they are creating some references, but nothing in the documentation or other online discussions... – ılǝ Aug 28 '13 at 01:52
4

After reading the answer here, I had a better idea:

$start = new DateTime('0:00 first day of previous month');
/*
if (isset($_GET['year']) && isset($_GET['month']) && checkdate($_GET['month'], 1, $_GET['year'])) {
    $start = DateTime::createFromFormat('Y-m-d', $_GET['year'] . '-' . $_GET['month'] . '-1');
}*/
$middle = clone $start;
$middle->modify('first day of last month');
$end = clone $start;
$end->modify('first day of 2 months ago');

var_dump($start);
var_dump($middle);
var_dump($end);

Output:

object(DateTime)[1]
  public 'date' => string '2013-07-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'America/Rainy_River' (length=19)

object(DateTime)[2]
  public 'date' => string '2013-06-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'America/Rainy_River' (length=19)

object(DateTime)[3]
  public 'date' => string '2013-05-01 00:00:00' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'America/Rainy_River' (length=19)

Also, I realize that a DateTimeImmutable would be a better choice for the $start instance (so that I don't have to clone the other two), but I don't have access to PHP 5.5 yet.

Community
  • 1
  • 1
rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
  • This is a much cleaner way, thumbs up. However - part of original question was why that particular piece of code didn't work correctly, on which I tried to elaborate. – ılǝ Aug 28 '13 at 01:44
  • Yeah, so for DateTimeImmutable just change the DateTime > DateTimeImmutable, and then you can do: $middle = $start->modify etcetc. – reinierkors Aug 31 '16 at 12:06