如何处理多个时区的Laravel应用程序

时间:2018-08-13 14:30:28

标签: laravel laravel-5 eloquent

当在多个时区使用Laravel应用程序时,我一直在学习处理本地化时间的最佳方法。

我的理解是,应用程序时区应保持默认设置,即UTC。

这意味着所有日期时间/时间戳均以其UTC值记录在数据库中(在我的情况下为MySQL),换句话说,始终如一。

要使雄辩的模型具有正确的(本地化)日期/时间值,必须遵守用户的时区。在这一点上,我对如何进行尚不十分清楚-具体而言,在以下方面:

  • 如何最好地获取用户的时区
  • 如何在Eloquent中以透明的方式使用该时区,以便
    • 所有型号日期均以当地时间输出
    • 所有日期都正确记录在数据库中(作为UTC)

修改 我应该提到我的应用程序同时支持匿名用户和经过身份验证的用户,所以我不想强迫用户明确选择其时区。

2 个答案:

答案 0 :(得分:0)

我喜欢为此添加辅助功能。我将应用程序保留在UTC中,因此所有日期都以相同的方式存储...然后,我将它们通过控制器或刀片模板中的帮助器传递。

public static function date($date, $format = 'n/j/Y g:i a T'){
    $timezone = empty(Auth::user()->timezone) ? 'America/New_York' : Auth::user()->timezone;
    if( empty($date) ){
        return '--';
    }
    return Carbon::parse($date)->timezone($timezone)->format($format);
}

这使用已添加到用户模型中的时区字段-但如果他们是来宾,则默认为东部时间。 (之所以如此东部,是因为这是生意,而且大多数客户都在这里。)

答案 1 :(得分:0)

我最终用自己的模型特征实现了这一目标,主要是因为我需要以透明的方式实现这一目标。

首先,我创建了自己的getAttribute()方法,以检索存储的值(存储为应用程序的默认时区-可能是UTC),然后应用当前时区。

特质还更改了模型的create()update()方法,以支持在用户设置了模型的dates属性中的字段后将其存储为应用程序的时区在当前有效时区。

在我的情况下,特征中的self::getLocale()静态方法由应用程序中的另一个特征提供,尽管可以根据您自己的应用程序调整此逻辑。

trait LocalTime
{
    /**
     * Override create() to save user supplied dates as app timezone
     * 
     * @param array      $attributes
     * @param bool|mixed $allow_empty_translations
     */
    public static function create(array $attributes = [], $allow_empty_translations=false)
    {
        // get empty model so we can access properties (like table name and fillable fields) that really should be static!
        // https://github.com/laravel/framework/issues/1436
        $emptyModel = new static;

        // ensure dates are stored with the app's timezone
        foreach ($attributes as $attribute_name => $attribute_value) {
            // do we have date value, that isn't Carbon instance? (assumption with Carbon is timezone value will be correct)
            if (!empty($attribute_value) && !$attribute_value instanceof Carbon && in_array($attribute_name, $emptyModel->dates)) {
                // update attribute to Carbon instance, created with current timezone and converted to app timezone
                $attributes[$attribute_name] = Carbon::parse($attribute_value, self::getLocale()->timezone)->setTimezone(config('app.timezone'));
            }
        }

        // https://github.com/laravel/framework/issues/17876#issuecomment-279026028
        $model = static::query()->create($attributes);

        return $model;
    }

    /**
     * Override update(), to save user supplied dates as app timezone
     *
     * @param array $attributes
     * @param array $options
     */
    public function update(array $attributes = [], array $options = [])
    {
        // ensure dates are stored with the app's timezone
        foreach ($attributes as $attribute_name => $attribute_value) {
            // do we have date value, that isn't Carbon instance? (assumption with Carbon is timezone value will be correct)
            if (!empty($attribute_value) && !$attribute_value instanceof Carbon && in_array($attribute_name, $this->dates)) {
                // update attribute to Carbon instance, created with current timezone and converted to app timezone
                $attributes[$attribute_name] = Carbon::parse($attribute_value, self::getLocale()->timezone)->setTimezone(config('app.timezone'));
            }
        }

        // update model
        return parent::update($attributes, $options);
    }

    /**
     * Override getAttribute() to get times in local time
     *
     * @param mixed $key
     */
    public function getAttribute($key)
    {
        $attribute = parent::getAttribute($key);

        // we apply current timezone to any timestamp / datetime columns (these are Carbon objects)
        if ($attribute instanceof Carbon) {
            $attribute->tz(self::getLocale()->timezone);
        }

        return $attribute;
    }
}

我对上述方法的反馈很感兴趣。