🇷🇺|🇷🇸|🇬🇪 Dmitriy Lezhnev
Software Developer
PHP/Go practitioner
Zend Certified Engineer
Clean Architecture advocate


PHP version 7+ Nginx web-server MySQL Linux Ubuntu Jet Brains Docker DuckDB Clickhouse
Remote developer

Find me on the Internet






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 







    ATOM feed | Website source code