Mon, 29 Aug 2016
# Save all dates in UTC for multi timezone apps
Table of Contents
So when dealing with dates which must be presented to a user in his local time zone it's better to store those in UTC (default) time zone.
In case for a Laravel app you should set app's timezone to UTC (it will be used for database connections). This is actually a default value for that config option:
<?php
// in config/app.php
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
// ...
// AND in config/database.php
'connections' => [
'mysql' => [
//....
'timezone' => '+00:00', // set to UTC
],
// ...
So the next thing you should do is to set your Eloquent model's date fields. You can do that using $casts
 variable:
<?php
class YourModel extends BaseModel
{
// ...
protected $casts = [
'start_at' => 'date',
'end_at' => 'date',
];
// ...
So when you save models - you pass Carbon instances as date values. And each of that instance can have it's own timezone. See this example:
<?php
// Somewhere in your code you create dates and manage them in user's local timezone
$date_in_local_timezone = Carbon::now('GMT+4');
// and then you just assign Carbon instances to the Model's date fields
$model->some_date = $date_in_local_timezone;
$model->save();
The thing is that by default Laravel (as of Laravel 5.3.4) does not respect Carbon instance's timezone. See this example:
<?php
// We create a Carbon instance with timezone set to "GMT+4"
$date_in_local_timezone = Carbon::now('GMT+4');
// Let's see the date we had initially
dump($date->toIso8601String())
// We then save it as "date" field
$model->some_date = $date_in_local_timezone;
$model->save();
// And then read it back from the model
dump($model->fresh()->some_date->toIso8601String());
//
// RESULTS
//
// "2015-01-01T12:00:00+0300"
// "2015-01-01T12:00:00+0000"
See how Laravel just discarded the timezone from the date? This is what it does right now. To change that - we need to force Laravel (actually Eloquent Model) to cast any Carbon instance to UTC timezone before saving. To do that I reload a method asDateTime(). See how I made it:
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* Class BaseModel
* Base model for any inner models
*
* @package App\Models
* @mixin \Eloquent
*/
class BaseModel extends Model
{
/**
* If Carbon - then move to UTC timezone before saving
* Assuming all dates are saved in UTC only
*
* @param mixed $value
*
* @return \Carbon\Carbon|mixed
*/
protected function asDateTime($value)
{
//
// any Carbon instance is going to be translated to UTC before saving
//
if($value instanceof Carbon) {
$value->timezone('UTC');
}
return parent::asDateTime($value); // TODO: Change the autogenerated stub
}
// ....
}
I don't know why Laravel does not respect the timezone, but I believe that this will be fixed in the future. Probably a good idea for a PR to Laravel?
Issue was discussed here:Â https://github.com/laravel/framework/issues/15122Â