Laravel/Carbon中的多时区设置

5

我想知道是否有可能,假设我有一个这样的模型:

MyModel
   SomeDate - Carbon

现在,我还为当前用户设置了时区,代码如下:
User
   MyTimezone

数据库中存储的时区始终以UTC格式存储(以确保一致性),输出的日期应始终格式化为特定时区(但每个用户的时区不同),例如User1的时区为America/Chicago,而User2的时区为America/Denver。
是否有一种方法可以在输出之前自动将每个Carbon实例的时区格式化为给定的时区,还是我必须循环遍历集合并相应地设置每个实例?
设置app.timezone无效,因为它还会导致Carbon实例以app.timezone时区保存到数据库中,而数据库中的所有日期都应该是UTC,因此我失去了一致性。
我当前将app.timezone设置为App配置中的UTC,但我也被迫在输出之前将所有Carbon实例转换为正确的时区。是否有更好的方法,例如在Carbon转换为字符串之前捕获执行并在那里进行操作?
编辑:
我尝试过的事情:
覆盖setAttribute和getAttribute:
public function setAttribute($property, $value) {
    if ($value instanceof Carbon) {
        $value->timezone = 'UTC';
    }

    parent::setAttribute($property, $value);
}

public function getAttribute($key) {
    $stuff = parent::getAttribute($key);

    if ($stuff instanceof Carbon) {
        $stuff->timezone = Helper::fetchUserTimezone();
    }

    return $stuff;
}

覆盖 asDateTime 方法:

protected function asDateTime($value)
{
    // If this value is an integer, we will assume it is a UNIX timestamp's value
    // and format a Carbon object from this timestamp. This allows flexibility
    // when defining your date fields as they might be UNIX timestamps here.
    $timezone = Helper::fetchUserTimezone();

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

    // If the value is in simply year, month, day format, we will instantiate the
    // Carbon instances from that format. Again, this provides for simple date
    // fields on the database, while still supporting Carbonized conversion.
    elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value))
    {
        return Carbon::createFromFormat('Y-m-d', $value, $timezone)->startOfDay();
    }

    // Finally, we will just assume this date is in the format used by default on
    // the database connection and use that format to create the Carbon object
    // that is returned back out to the developers after we convert it here.
    elseif ( ! $value instanceof DateTime)
    {
        $format = $this->getDateFormat();

        return Carbon::createFromFormat($format, $value, $timezone);
    }

    return Carbon::instance($value);
}

只是一个想法,但将时间数据传递到JS中,让JS为您进行计算。https://dev59.com/S3NA5IYBdhLWcg3wGJ0V - clonerworks
2个回答

5

我的应用程序也遇到了类似问题,远程网站会以UTC为标准存储日期,而我需要根据登陆用户显示实际日期。为此,我想到了覆盖Laravel Eloquent模型。

只需像下面这样扩展Illuminate\Database\Eloquent\Model

<?php namespace Vendor\Package;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class Model extends EloquentModel
{

    /**
    * Return a timestamp as a localized DateTime object.
    *
    * @param  mixed  $value
    * @return \Carbon\Carbon
    */
    protected function asDateTime($value)
    {
        $carbon = parent::asDateTime($value);
        // only make localized if timezone is known
        if(Auth::check() && Auth::user()->timezone)
        {
            $timezone = new DateTimeZone(Auth::user()->timezone);
            // mutates the carbon object immediately
            $carbon->setTimezone($timezone);
        }

        return $carbon;
    }
    /**
    * Convert a localized DateTime to a normalized storable string.
    *
    * @param  \DateTime|int  $value
    * @return string
    */
    public function fromDateTime($value)
    {
        $save = parent::fromDateTime($value);

        // only make localized if timezone is known
        if(Auth::check() && Auth::user()->timezone)
        {
            // the format the value is saved to
            $format = $this->getDateFormat();

            // user timezone
            $timezone = new DateTimeZone(Auth::user()->timezone);

            $carbon = Carbon::createFromFormat($format, $value, $timezone);
            // mutates the carbon object immediately
            $carbon->setTimezone(Config::get('app.timezone'));

            // now save to format
            $save = $carbon->format($format);
        }

        return $save;
    }
}

或许这对于其他遇到此问题的人有所帮助。

作为参考:

  • laravel 5 (2015-03-18):Illuminate\Database\Eloquent\Model:2809-2889
  • laravel 4.2 (2015-03-18):Illuminate\Database\Eloquent\Model:2583-2662

1
如果我理解正确,您想实现的是将时区从A格式转换为B格式并发送给用户,其中A格式存储在数据库中,而B格式是在检索记录后转换的。以下是一种简便方法。
在需要进行转换的模型(如User和MyModel)中,添加一个模型函数:
public function getConversionAttribute()
{
    $conversion = Convert($this->SomeDate);
    //Convert is the customized function to convert data format
    //SomeDate is the original column name of dates stored in your database
    return $conversion;
}

现在,如果您使用$user = User::find(1)查询用户模型或MyModel,您可以通过访问转换属性$user->conversion来获取转换后的日期。

干杯!

然而,以这种方式添加的属性不会包含在转换数组中。您需要在模型中添加另一个函数。

public function toArray()
{
    $array = parent::toArray();
    //if you want to override the original attribute
    $array['SomeDate'] = $this->conversion;
    //if you want to keep both the original format and the current format
    //use this: $array['Conversion'] = $this->conversion;
    return $array;
}

一般版本:
public function toArray() {
    $array = parent::toArray();
    //if you want to override the original attribute
    $dates = $this->getDates();

    foreach ($dates as $date) {
        $local = $this->{$date}->copy();
        $local->timezone = ...
        $array[$date] = (string)$local;
    }
    //if you want to keep both the original format and the current format
    //use this: $array['Conversion'] = $this->conversion;
    return $array;
}

问题在于,如果我在模型上调用 ->toArray() 方法,我想要的值,例如 start_time,仍然处于数据库时区。 - David Xu
尝试这个:public function getConversionAttribute() { $this->SomeDate = Convert($this->SomeDate); //Convert是自定义的用于转换数据格式的函数 //SomeDate是存储在您的数据库中的日期的原始列名 return $this->SomeDate; } - Ray
只要您不使用“$this->save()”将其保存到数据库中,它就可以按您的意愿更改原始格式,而不会影响数据库条目。 - Ray
嘿,请查看我的编辑答案。现在应该可以按照您的要求工作了。 - Ray
1
太酷了,我刚刚制作了一个更加通用的版本,我会现在接受你的函数并且也会添加这个更加通用的版本。 - David Xu
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接