Ben Sinclair
Ben Sinclair

Reputation: 3986

Carbon timezone issues

I am having issues with Carbon and timezones when the timezone set by date_default_timezone_set() differs from the timezone Carbon is using.

In the example below, I have a while loop that adds a month and reverts to the start of that month until the $end_date is greater than $current_date:

date_default_timezone_set('Australia/Brisbane');

$tz = new DateTimeZone('Australia/Brisbane');

$start_date = \Carbon\Carbon::instance(new DateTime('2019-03-01 00:00:00', $tz));
$end_date = \Carbon\Carbon::instance(new DateTime('2021-03-21 23:59:00', $tz));

$current_date = $start_date->copy();

while ($end_date->gte($current_date)) {
   echo $current_date->toDateTimeString() . "\n";
   $current_date->addMonth()->startOfMonth();
}

As you can see the output is correct.

2019-03-01 00:00:00
2019-04-01 00:00:00
2019-05-01 00:00:00
2019-06-01 00:00:00
2019-07-01 00:00:00
2019-08-01 00:00:00
2019-09-01 00:00:00
2019-10-01 00:00:00
2019-11-01 00:00:00
2019-12-01 00:00:00
2020-01-01 00:00:00

As soon as I change the default timezone to UTC I get an infinite loop. For the sake of this example, I've adjusted the code to stop after 10 loops:

date_default_timezone_set('UTC'); // <---- Changed to UTC

$tz = new DateTimeZone('Australia/Brisbane');

$start_date = \Carbon\Carbon::instance(new DateTime('2019-03-01 00:00:00', $tz));
$end_date = \Carbon\Carbon::instance(new DateTime('2021-03-21 23:59:00', $tz));

$current_date = $start_date->copy();

$x = 0;
while ($end_date->gte($current_date)) {
   echo $current_date->toDateTimeString() . "\n";
   $current_date->addMonth()->startOfMonth();
   $x++;
   if ($x === 10)
       break;
}

And here is the output.

2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00
2019-03-01 00:00:00

My expectation is that because I am passing Australia/Brisbane as the timezone for $start_date and $end_date there shouldn't be any issue here.

Finally, if I rebuild my code to use DateTime instead of Carbon, I have no problem.

date_default_timezone_set('UTC');

$tz = new DateTimeZone('Australia/Brisbane');

$start_date = new DateTime('2019-03-01 00:00:00', $tz);
$end_date = new DateTime('2020-03-21 23:59:00', $tz);

$current_date = clone $start_date;

while ($current_date->getTimestamp() < $end_date->getTimestamp()) {
    echo $current_date->format('Y-m-d H:i:s') . "\n";
    $current_date->add(new DateInterval('P1M'));
    $current_date->modify('first day of this month');
}

Have I missed something vital to how Carbon handles timezones?

Upvotes: 0

Views: 1550

Answers (1)

hassan
hassan

Reputation: 8288

if you want to keep Carbon structure , you may use add function directly as follows :

$x = 0;
while ($end_date->gte($current_date)) {
   echo $current_date->toDateTimeString() . "\n";
   $current_date->add(new \DateTimeinterval('P1M'))->startOfMonth();
   $x++;
   if ($x === 10)
       break;
}

Update

The following method is the method that's addMonth method uses to increments the current date by one month

/**
 * Consider the timezone when modifying the instance.
 *
 * @param string $modify
 *
 * @return static
 */
public function modify($modify)
{
    if ($this->local) {
        return parent::modify($modify);
    }

    $timezone = $this->getTimezone();
    $this->setTimezone('UTC');
    $instance = parent::modify($modify);
    $this->setTimezone($timezone);

    return $instance;
}

As you can see, this function reset so to speak the timezone to UTC before modifying your date then modify , and finally convert it into the given timezone -in this context it is a Australia/Brisbane-

Actually It's not clear enough why Carbon authors resets the timezone to UTC in that function but this is the cause of your issue.

Upvotes: 1

Related Questions