поля timestampTz в Laravel

Laravel 5.4 поддерживает тип поля Postgres TIMESTAMP WITH TIME ZONE в миграциях:

$table->timestampTz('scheduled_for');

Laravel можно настроить для преобразования полей даты (DATE, DATETIME, TIMESTAMP) в объекты Carbon (и делает это по умолчанию для полей created_at и updated_at TIMESTAMP), но установка scheduled_for в поле $dates вызывает ошибку в версии с учетом часового пояса. :

InvalidArgumentException with message 'Trailing data'

Глядя в базу данных и возиться, значение поля выглядит примерно как 2017-06-19 19:19:19-04. Есть ли собственный способ получить объект Carbon из одного из этих типов полей? Или я застрял, используя аксессор?


person ceejayoz    schedule 19.06.2017    source источник


Ответы (2)


Воскрешая этот вопрос, надеюсь, с полезным ответом, который будет принят.

Laravel предполагает формат временной метки базы данных Y-m-d H:i:s. Если вы используете столбец Postgres timestampz, это явно отличается. Вам нужно сообщить Eloquent, как заставить Carbon анализировать этот формат.

Просто определите свойство $dateFormat в вашей модели следующим образом:

Class MyModel extends Eloquent {

    protected $dateFormat = 'Y-m-d H:i:sO';

}

Кредит, которому следует отдать должное: я нашел это решение в GitHub

person Jim Rubenstein    schedule 24.09.2018
comment
Неато, это элегантно! - person ceejayoz; 24.09.2018
comment
Что делать, если у меня есть типы столбцов timestamp и timestampTz в одной таблице/модели/? - person Inigo; 21.05.2020
comment
@Inigo Прошло некоторое время с тех пор, как я собирался заняться этим, но я предполагаю, что вам придется написать что-то более обширное, включающее создание хэша, содержащего сопоставление column -> format, а затем переопределить метод, который ищет $dateFormat, и выполнить поиск самостоятельно, а затем вернуть правильный/желаемый. - person Jim Rubenstein; 27.05.2020
comment
Спасибо за ответ, @Jim. В итоге я просто сохранил часовой пояс в другом поле, но буду иметь это в виду. - person Inigo; 27.05.2020

Поместите это в свою модель

protected $casts = [
    'scheduled_for' => 'datetime'   // date | datetime | timestamp
];

Использование $dates, скорее всего, устарело, так как $casts делает то же самое (может быть, за исключением атрибута $dateFormat, который может работать только для $dates полей iirc, но я видел некоторые жалобы на него)

Изменить

Однажды я тестировал Carbon на Laravel 5.4 и создал для него трейт.

это еще не производственный код, поэтому включите его в свою модель на свой страх и риск

<?php namespace App\Traits;

use Carbon\Carbon;

trait castTrait
{
    protected function castAttribute($key, $value)
    {
        $database_format        = 'Y-m-d H:i:se';   // Store this somewhere in config files
        $output_format_date     = 'd/m/Y';          // Store this somewhere in config files
        $output_format_datetime = 'd/m/Y H:i:s';    // Store this somewhere in config files

        if (is_null($value)) {
            return $value;
        }

        switch ($this->getCastType($key)) {
            case 'int':
            case 'integer':
                return (int) $value;
            case 'real':
            case 'float':
            case 'double':
                return (float) $value;
            case 'string':
                return (string) $value;
            case 'bool':
            case 'boolean':
                return (bool) $value;
            case 'object':
                return $this->fromJson($value, true);
            case 'array':
            case 'json':
                return $this->fromJson($value);
            case 'collection':
                return new BaseCollection($this->fromJson($value));
            case 'date':
                Carbon::setToStringFormat($output_format_date);
                $date = (string)$this->asDate($value);
                Carbon::resetToStringFormat();  // Just for sure
                return $date;
            case 'datetime':
                Carbon::setToStringFormat($output_format_datetime);
                $datetime = (string)$this->asDateTime($value);
                Carbon::resetToStringFormat();
                return $datetime;
            case 'timestamp':
                return $this->asTimestamp($value);
            default:
                return $value;
        }
    }

    /**
     * Return a timestamp as DateTime object with time set to 00:00:00.
     *
     * @param  mixed  $value
     * @return \Carbon\Carbon
     */
    protected function asDate($value)
    {
        return $this->asDateTime($value)->startOfDay();
    }

    /**
     * Return a timestamp as DateTime object.
     *
     * @param  mixed  $value
     * @return \Carbon\Carbon
     */
    protected function asDateTime($value)
    {
        $carbon = null;
        $database_format = [ // This variable should also be in config file
            'datetime'  => 'Y-m-d H:i:se',      // e -timezone
            'date'      => 'Y-m-d'
        ];

        if(empty($value)) {
            return null;
        }

        // If this value is already a Carbon instance, we shall just return it as is.
        // This prevents us having to re-instantiate a Carbon instance when we know
        // it already is one, which wouldn't be fulfilled by the DateTime check.
        if ($value instanceof Carbon) {
            $carbon = $value;
        }

         // If the value is already a DateTime instance, we will just skip the rest of
         // these checks since they will be a waste of time, and hinder performance
         // when checking the field. We will just return the DateTime right away.
        if ($value instanceof DateTimeInterface) {
            $carbon = new Carbon(
                $value->format($database_format['datetime'], $value->getTimezone())
            );
        }

        // 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.
        if (is_numeric($value)) {
            $carbon = Carbon::createFromTimestamp($value);
        }

        // 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.
        if ($this->isStandardDateFormat($value)) {
            $carbon = Carbon::createFromFormat($database_format['date'], $value)->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.
        $carbon = Carbon::createFromFormat(
            $database_format['datetime'], $value
        );

        return $carbon;
    }
}
person Bartłomiej Sobieszek    schedule 19.06.2017
comment
$casts не работает. Кажется, он все еще запускает его через Carbon (безуспешно). - person ceejayoz; 19.06.2017
comment
Причина в неправильном формате, который исходит из базы данных. Laravel пытается проанализировать вашу строку ввода даты и времени, используя неправильную маску формата - person Bartłomiej Sobieszek; 19.06.2017
comment
Учитывая, что Laravel поддерживает timestampTz в миграциях, это нельзя назвать неправильным форматом. На данный момент кажется, что Laravel не полностью поддерживает его, отсюда и мой вопрос - кажется странным реализовывать его в миграциях, не используя его в реальном коде. - person ceejayoz; 19.06.2017
comment
Я обновил свой ответ, вы можете проверить предоставленную мной черту - person Bartłomiej Sobieszek; 19.06.2017
comment
Я склоняюсь к тому, чтобы просто использовать поле timestamp и хранить рядом с ним scheduled_tz в виде строки, что позволяет мне использовать метод доступа, например public function getScheduledForAttribute($value) { return Carbon::parse($value)->setTimezone($this->scheduled_tz); } - person ceejayoz; 19.06.2017
comment
Мое решение делает это, но вам не нужно объявлять функции атрибутов, так что это даже лучше - person Bartłomiej Sobieszek; 19.06.2017