Replies: 13 comments 12 replies
-
Hi. protected $attributes = [
'updated',
]; This is useless. Attributes are not a list but a map. Then the model has default value for updated at : /**
* The name of the "updated at" column.
*
* @var string|null
*/
const UPDATED_AT = 'updated_at'; To pin point the issue, please try with: /**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false; and then with const UPDATED_AT = 'updated'; UPDATE protected function addDateAttributesToArray(array $attributes)
{
foreach ($this->getDates() as $key) {
if (! isset($attributes[$key])) {
continue;
}
$attributes[$key] = $this->serializeDate(
$this->asDateTime($attributes[$key])
);
}
return $attributes;
} As the default timezone for laravel is UTC, when a date is converted in the above function, 09 hour is considered Berlin so the UTC is 08 h. Please confirm by testing. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
The main issue is that $m->toArray()['updated_at'] is different from (string) $m->updated_at . |
Beta Was this translation helpful? Give feedback.
-
@thbley We think this is not an issue but a normal behavior. If you set in your model /**
* The name of the "updated at" column.
*
* @var string|null
*/
const UPDATED_AT = 'updated_at_'; It will work. We bypassed issues like this by setting timestamps to false and then in the model we set them as we wanted. See example https://github.com/macropay-solutions/laravel-crud-wizard-free/blob/71f80f9895aeb8201c07a27c8d515ce3a1e01e36/src/Models/BaseModel.php#L161 |
Beta Was this translation helpful? Give feedback.
-
You can also try to set laravel timezone to Berlin in config and test. See also db saved values in this case. |
Beta Was this translation helpful? Give feedback.
-
I have app.timezone set to Europe/Berlin and that's why I created the ticket. In general: if I write X to the database, I want to read X later and not Y. Everything else is not normal behavior. $m->toArray()['updated_at'] should always be the same as (string) $m->updated_at |
Beta Was this translation helpful? Give feedback.
-
I want automatic population of updated_at, with the option to override automatic. |
Beta Was this translation helpful? Give feedback.
-
A possible fix for this problem would be this (but I'm not sure if this works for all use cases):
|
Beta Was this translation helpful? Give feedback.
-
public $timestamps = false;
public static function boot(): void
{
parent::boot();
static::creating(function (BaseModel $baseModel): void {
if ($baseModel->isDirty($c = $baseModel->getCreatedAtColumn()) && null !== $baseModel->getAttribute($c)) { // instead of null check you could validate the format
// if you set it already do nothing
return;
}
$baseModel->setCreatedAt(Carbon::now()->format($baseModel::CREATED_AT_FORMAT));
});
static::updating(function (BaseModel $baseModel): void {
$updatedAtColumn = $baseModel->getUpdatedAtColumn();
if ('' === $baseModel->getAttribute($updatedAtColumn)) { // feature to not set updatedAt (datetime or null) on request
$baseModel->setUpdatedAt($baseModel->getOriginal($updatedAtColumn));
return;
}
if ($baseModel->isDirty($updatedAtColumn) && null !== $baseModel->getAttribute($updatedAtColumn)) { // instead of null check you could validate the format
// if you set it already do nothing
return;
}
$baseModel->setUpdatedAt(Carbon::now()->format($baseModel::UPDATED_AT_FORMAT));
});
} |
Beta Was this translation helpful? Give feedback.
-
Where did you set On your On a fresh installation of Laravel 12, it works as expected: <?php // ./routes/console.php
use App\Models\User;
use Illuminate\Support\Facades\Artisan;
Artisan::command('app:test', function () {
$this->info(config('app.timezone'));
// on insert
$user = User::factory()->create();
$this->info($user->updated_at->toDateTimeString());
// force update to a different date
\sleep(1);
$user->touch();
// after update
$user = User::query()->find($user->getKey());
$this->info($user->updated_at->toDateTimeString());
// force update to a different date
\sleep(1);
$user->touch();
// after update, reading raw data from DB
$pdo = User::query()->getConnection()->getPdo();
$statement = $pdo->query('SELECT updated_at FROM users ORDER BY id DESC LIMIT 1');
$updatedAt = $statement->fetchColumn();
$this->info($updatedAt);
}); Output: $ TZ="Europe/Berlin" date --iso-8601=seconds
2025-02-24T23:52:03+01:00
$ php artisan app:test
Europe/Berlin
2025-02-24 23:52:04
2025-02-24 23:52:05
2025-02-24 23:52:06 You don't need to call Mind that the If you were using that environment variable to set your app's timezone, you need to either:
|
Beta Was this translation helpful? Give feedback.
-
Please check my example:
It's clearly showing that $m->toArray()['updated_at'] gets converted to the wrong timezone. app.timezone is used to populate date_default_timezone_set(), so to make the example easier, I call date_default_timezone_set() directly. The example is working correctly with date_default_timezone_set('UTC'), but it should be also working correctly with any other timezone. |
Beta Was this translation helpful? Give feedback.
-
Looking at HasAttributes::serializeDate(), it's clear that toJSON() removes the timezone information whereas toISOString(true) is preserving the timezone information. vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
|
Beta Was this translation helpful? Give feedback.
-
@crynobone |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Laravel Version
v11.44.0, v12.0.1
PHP Version
8.4.3
Database Driver & Version
No response
Description
Expected output:
Actual output:
Steps To Reproduce (updated)
Beta Was this translation helpful? Give feedback.
All reactions