Display all dates on models in the user’s timezone

2020-07-17 06:40发布

问题:

I have the users timezone stored (there is timezone column in the users DB table) and I want to display all $dates attributes on all models in the user’s timezone, if authenticated.

I'm trying to find an elegant way to do this... Ideally, when there is something like this in Blade views:

{{ $post->created_at }}

OR

{{ $post->created_at->format('h:i:s A') }}

... for authenticated users it would be automatically in their timezones.

How would you handle this?


I'm thinking about creating one trait (for example, app/Traits/UserTimezoneAware.php) and place there accessors that will simply return Carbon::createFromFormat('Y-m-d H:i:s', $value)->timezone(auth()->user()->timezone) if the current user is authenticated. For example:

<?php

namespace App\Traits;

use Carbon\Carbon;

trait UserTimezoneAware
{
    /**
     * Get the created_at in the user's timezone.
     *
     * @param $value
     * @return mixed
     */
    public function getCreatedAtAttribute($value)
    {
        if (auth()->check()) {
            return Carbon::createFromFormat('Y-m-d H:i:s', $value)->timezone(auth()->user()->timezone);
        }

        return Carbon::createFromFormat('Y-m-d H:i:s', $value);
    }

    /**
     * Get the updated_at in the user's timezone.
     *
     * @param $value
     * @return mixed
     */
    public function getUpdatedAtAttribute($value) { ... }
}

But I'm not sure if this is good or bad to do (to create these accessros for the Laravel's $datesattributes)?

Also, models will have different attributes specified in $dates array: for example, User model can have:

/**
 * The attributes that should be mutated to dates.
 *
 * @var array
 */
protected $dates = [
    'created_at',
    'updated_at',
    'last_login_at'
];

and Post model can have:

protected $dates = [
    'created_at',
    'updated_at',
    'approved_at',
    'deleted_at'
];

Is it possible to dynamically create accessors in trait, based on the attirbutes specified in the $dates array of a model that uses that trait?

Or maybe there is a better way to handle this, without accessors?

回答1:

One way (without accessors) is to use this trait:

<?php

namespace App\Traits;

use DateTimeInterface;
use Illuminate\Support\Carbon;

trait UserTimezoneAware
{
    /**
     * Return a timestamp as DateTime object.
     *
     * @param  mixed  $value
     * @return \Illuminate\Support\Carbon
     */
    protected function asDateTime($value)
    {
        $timezone = auth()->check() ? auth()->user()->timezone : config('app.timezone');

        if ($value instanceof Carbon) {
            return $value->timezone($timezone);
        }

        if ($value instanceof DateTimeInterface) {
            return new Carbon(
                $value->format('Y-m-d H:i:s.u'), $timezone
            );
        }

        if (is_numeric($value)) {
            return Carbon::createFromTimestamp($value)->timezone($timezone);
        }

        if ($this->isStandardDateFormat($value)) {
            return Carbon::createFromFormat('Y-m-d', $value)->startOfDay()->timezone($timezone);
        }

        return Carbon::createFromFormat(
            str_replace('.v', '.u', $this->getDateFormat()), $value
        )->timezone($timezone);
    }
}

When using this trait, we are overriding asDateTime($value) defined in Concerns\HasAttributes trait (which is used in Illuminate\Database\Eloquent\Model).

This seems to work OK, I have not yet encountered any problems.

But I'm not sure if there are any risks or potential problems when doing this (when using this trait that overrides asDateTime method).



回答2:

Documentation https://laravel.com/docs/5.5/eloquent-mutators#date-mutators

class Flight extends Model
{
    /**
     * The storage format of the model's date columns.
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

or see https://laravel.com/docs/5.5/eloquent-mutators#attribute-casting

class User extends Model
{
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
       'created_at' => 'datetime:Y-m-d' // or other format
    ];
}

for make it dinamically changed format your model add this method

public function setCast($attribute, $format) 
{
    $this->casts[$attribute] = $format;    
}