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.
-
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