Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

georgecoffey's avatar

Carbon datetime is Ignoring and shifting timezone

I'm trying to figure out this issue I am having. I have a model called "booking" and it has a datetime called "start". It's defined in the object like this:

protected $casts = ['start' => 'datetime'];

When I try to set it with postdata inside a controller, like this:

$this->booking->start = Carbon::createFromFormat('Y-m-d H:i', $postdata['date'].' '.$postdata['time'], 'America/Los_Angeles');

It sets the time as UTC, so instead of 8pm Los Angeles time, 8pm UTC is saved in the database (and dumped in the Carbon object) If I do it by itself:

$test = Carbon::createFromFormat('Y-m-d H:i', $postdata['date'].' '.$postdata['time'], 'America/Los_Angeles');

Then the dumped Carbon object shows the correct time, set as Los Angeles time. But if I then try to assign that to the start time:

$this->booking->start = $test;

Then the "start" value is changed to UTC. So even though the Carbon object "$test" showed 8pm Los Angeles time, the "start" value is now a Carbon object with a value of 8pm UTC

If I do a shiftTimezone on the "start" object nothing happens

shiftTimezone('America/Los_Angeles')

I was able to work around it for now by doing this:

$start = Carbon::createFromFormat('Y-m-d H:i', $postdata['date'].' '.$postdata['time'], 'America/Los_Angeles');
		$start->setTimezone('UTC');
		$this->booking->start = $start;

But that seems unnecessary, why is the Carbon object locked? What is going on? My app config is set to UTC EDIT: Forgot to mention it's Laravel Framework 10.7.1

0 likes
10 replies
LaryAI's avatar
Level 58

The issue is that the Laravel application timezone is set to UTC, so when you set the Carbon object to a specific timezone, it gets converted to UTC when it's saved to the database. To fix this, you can set the application timezone to the desired timezone in the config/app.php file.

Alternatively, you can use the setTimezone method on the Carbon object to convert it to the desired timezone before saving it to the database.

Here's an example of how to set the application timezone to 'America/Los_Angeles':

// config/app.php
'timezone' => 'America/Los_Angeles',

And here's an example of how to use the setTimezone method:

$this->booking->start = Carbon::createFromFormat('Y-m-d H:i', $postdata['date'].' '.$postdata['time'], 'America/Los_Angeles')
    ->setTimezone('UTC');
georgecoffey's avatar

The A.I. suggestion did work, of adding a "->setTimezone('UTC')" to the end of the carbon create, but that doesn't explain why this is necessary? Do Eloquent models claim to use Carbon but actually only store the hours and minutes and re-create the Carbon object every time it's referred to?

I also had tried:

$this->booking->start->sub('hour', 8);

and that also did not work, nothing would effect the "start" attribute once it was set

aarontharker's avatar

@georgecoffey Yes you are correct. It is stored in the database as a normal timestamp string. If you want your date or dateTime field on your model to always be a Caebon instance you have to set a cast attribute for it on your model.

protected $casts = [
    'booking' => 'datetime',
]
georgecoffey's avatar

@aarontharker it is cast as a 'datetime' in the model. The issue seems to be that when you fill a model attribute with a carbon object, the datetime is extracted and used, regardless of what the timezone is. So even though Laravel is using carbon objects for datetimes, it seems to only use that functionality for making output of dates easier, while inputting dates only gives you the functionality of "strtotime"

Snapey's avatar

If your app timezone is UTC then datetimes will be converted from whatever timezone they are in to the equivalent UTC time. This is what is stored. Your timezone is not stored.

When retrieving the date time, it will be a carbon instance with UTC timezone.

If you need to display this to a user, then you should apply the user's timezone before formatting it. The timezone could of course come from some other model such as the timezone of a hotel in the country in which it is based.

If you need to apply the timezone independently of the user (for instance) then you should save the timezone as a column on the model eg booking->timezone.

georgecoffey's avatar

@Snapey I know this is quite late, but now having re-written everything and having it all working I just wanted to clarify this point for anyone seeing it in the future. I get what you are saying, everything is stored in UTC and comes back out of the DB as UTC, but that's not what I am talking about. The issue is this:

Let's say your project used UTC, and you set an eloquent model attribute from a Carbon instance. If that Carbon instance has a different timezone (for example America/Los_Angeles), instead of laravel covering that time to UTC, or copying the Carbon instance into the attribute, it discards any timezone information and creates a fresh carbon instance using only the date and time.

For example:

$time = Carbon::now();
$time->setTimezone('America/Los_Angeles');
$time->setHour(10);
$time2 = $time->copy();
$model->updated_at = $time;

In this code, "$time2" and "$model->updated_at" were both set to the "$time" Carbon Instance, and both are themselves carbon instances, however if you look at the output, the model's Carbon Instance has ignored the timezone information and created a fresh carbon instance without it, while the "$time2" carbon instance retains the timezone information.

georgecoffey's avatar

@Snapey Let me try again, keep in mind this is all before anything is saved in the database, this (for me) is all happening in a controller before anything is saved.

If you set the time using a carbon instance with a non-UTC timezone so:

$model->timeField = $carbonInstance;

Then sure enough "$model->timeField" will be a carbon instance. This made me think that "$model->timeField" would be a copy of your original carbon instance, the same way you can do:

$anotherCarbonInstance = $carbonInstance->copy();

and have "$anotherCarbonInstance" be a copy or your original.

However, when assigning to a model, you don't end up with a copy of your original carbon instance, but instead a brand new carbon instance that only took the date-time from your original, and ignored the timezone.

So nothing is being converted here, the timezone is being ignored EVEN WHEN SETTING, let alone when saving to the database

Snapey's avatar

@georgecoffey entirely depends on your casts

However, when assigning to a model, you don't end up with a copy of your original carbon instance, but instead a brand new carbon instance

How did you read back the value from the model? It probably read the plain string of date time and converted it to an instance of carbon via its getter.

georgecoffey's avatar

@Snapey I used dd() to compare them. I think you are correct, the getter is reading the plain datetime from the carbon and creating it's own instance. This just seems like the incorrect behavior to me. The Laravel documentation states:

When a column is cast as a date, you may set the corresponding model attribute value to a UNIX timestamp, date string (Y-m-d), date-time string, or a DateTime / Carbon instance. The date's value will be correctly converted and stored in your database.

Encouraging you to set it with a carbon instance and saying that it will be "correctly converted" implies that it would convert the timestamp to local time when setting.

I do at least know what's going on now if I want to file a bug, so thank you for your responses.

Please or to participate in this conversation.