Laravel-excel: [BUG] проблема с форматом даты импорта

Созданный на 14 окт. 2018  ·  38Комментарии  ·  Источник: Maatwebsite/Laravel-Excel

  • [x] Возможность воспроизвести поведение за пределами вашего кода, проблема изолирована в Laravel Excel.
  • [x] Проверено, что ваша проблема еще не отправлена.
  • [x] Проверено, не был ли отправлен PR, который решает эту проблему.

Версии

  • Версия PHP: 7.1
  • Версия Laravel: 5.7
  • Версия пакета: 3.1

Описание

Я создал новый импорт с WithChunkReading и размером пакета. Проблема, с которой я сталкиваюсь, заключается в том, что импортер преобразует столбцы даты в метку времени (я считаю, что это метка времени) 43257.0. после инвестирования в проблему я обнаружил очень старый поток https://github.com/Maatwebsite/Laravel-Excel/issues/404, и одно из решений, которое исправило это для меня, установило истинное значение на false в классе ReadChunk, доступном в vendor/maatwebsite/excel/src/Jobs/ReadChunk.php . строка $this->reader->setReadDataOnly(true);
Это решение работает на данный момент, но когда мы выполним обновление композитора, оно исчезнет, ​​поскольку его нельзя настроить в библиотеке.

Действия по воспроизведению

  1. создать Excel
  2. добавить столбец с любым форматом даты.
  3. импортировать Excel с помощью импортера метода фрагментов через библиотеку.

Ожидаемое поведение:

Я ожидал, что библиотека загрузит дату, как и ожидалось.

Фактическое поведение:

библиотека преобразует дату в метку времени (при условии, что ее метка времени)

Дополнительная информация

Вот мой класс импорта,

` пространство имен App \ Imports;

используйте App \ Sample;
используйте Maatwebsite \ Excel \ Concerns \ ToModel;
используйте Maatwebsite \ Excel \ Concerns \ WithBatchInserts;
используйте Maatwebsite \ Excel \ Concerns \ WithChunkReading;
используйте Maatwebsite \ Excel \ Concerns \ WithHeadingRow;
используйте Maatwebsite \ Excel \ Imports \ HeadingRowFormatter;

HeadingRowFormatter :: default ('нет');

Класс Sample реализует ToModel, WithHeadingRow, WithBatchInserts, WithChunkReading
{

public function model(array $row)
{


    return new user([
        'UserName'           => $row['UserName'],
        'Password'           => $row['Password'],
        'date'               => $row['date'],
    ]);
}

public function batchSize(): int
{
    return 1000;
}

public function chunkSize(): int
{
    return 1000;
}

} `

enhancement

Самый полезный комментарий

Я начал работать над PR, но быстро понял, что это будет сложно реализовать. @patrickbrouwers собирался просмотреть кодовую базу, чтобы увидеть, каким будет лучший путь вперед.

А пока я просто использую вспомогательный метод для своего объекта Import:

/**
 * Transform a date value into a Carbon object.
 *
 * <strong i="8">@return</strong> \Carbon\Carbon|null
 */
public function transformDate($value, $format = 'Y-m-d')
{
    try {
        return \Carbon\Carbon::instance(\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value));
    } catch (\ErrorException $e) {
        return \Carbon\Carbon::createFromFormat($format, $value);
    }
}
class Sample implements ToModel
{
    public function model(array $row)
    {
        return new user([
            'name' => $row[0],
            'email' => $row[1],
            'birth-date' => $this->transformDate($row[2]),
        ]);
    }
}

Все 38 Комментарий

Не проверено, но я считаю, что вам нужно сделать:

 return new user([
        'UserName'           => $row['UserName'],
        'Password'           => $row['Password'],
        'date'               => \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['date']),
    ]);

Я помню, что setReadDataOnly вызывает проблемы в некоторых других ситуациях, поэтому мне неудобно просто добавлять это снова. Возможно, мы сможем сделать это добровольным участником или что-то в этом роде.

Я тоже столкнулся с этой проблемой. excelToDateTimeObject вернул фактическую дату, но было бы неплохо позаботиться об этом.

Возможно что-то вроде:

    interface WithDateObjects
    {
        /**
         * An array of columns to convert to DateTime Objects.
         * Either named column headers or coordinate columns.
         *
         * <strong i="8">@return</strong> array
         */
        public function dateColumns(): array;
    }

    class MyImport implements WithDateObjects
    {
        public function dateColumns(): array
        {
            return ['birthday', 'wedding_date'];
        }
    }

Тогда laravel excel определит, реализует ли импорт WithDateObjects и установит \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($this->cell->getValue()) мере необходимости?

PS: Следует ли это преобразовать в углерод?

Я бы согласился с PR, который добавляет WithDates озабоченность, которая возвращает Carbon ates;)

ну, у меня тоже возникла проблема, до этого она просто отлично работала, а теперь я получил ошибку unexpected data found , и даже когда я добавляю это

\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['date'])

и все еще есть ошибка

A non well formed numeric value encountered

также пытался переоборудовать карбоном, но не работает

Тогда ваш $row['date'] не является целым числом. excelToDateTimeObject работает только тогда, когда дата отформатирована как дата в Excel.

хммм он уже отформатирован на дату

screen shot 2018-10-25 at 16 48 06

я использую "maatwebsite/excel": "^3.1",

Попробуй отладить, посмотри что в $row['data'] .

Я могу отправить PR с WithDates но это будет минимум неделю. Я на конференции и не могу сейчас сосредоточиться на этом.

это 1/1/18 он должен автоматически конвертироваться из даты Excel в таблицу mysql, верно? Я использую этот пакет раньше, и он работает, просто нахожу ... но теперь получаю эту ошибку

Если вы вернете 1/1/18 , это явно не целое число :) Так что вам не следует использовать excelToDateTimeObject . Вместо этого вам нужно использовать Carbon::createFromFormat(...) чтобы справиться с этим.

@patrickbrouwers Возможно, проблема withDates может сначала попытаться выполнить excelToDateTimeObject, и если это вызовет исключение, попробуйте Carbon :: createFromFormat (). Метод dateColumn может указывать формат даты.

public function dateColumns(): array
        {
            return ['birthday' => ‘d/m/Y’, 'wedding_date'];
        }

Ps: Я пробовал написать этот код на своем телефоне, не могу понять, как сделать обратные галочки

@devinfd Звучит хорошо :)

ps исправил ваши обратные клещи;)

@devinfd, как далеко вы уже продвинулись с реализацией? Было бы интересно.

Я начал работать над PR, но быстро понял, что это будет сложно реализовать. @patrickbrouwers собирался просмотреть кодовую базу, чтобы увидеть, каким будет лучший путь вперед.

А пока я просто использую вспомогательный метод для своего объекта Import:

/**
 * Transform a date value into a Carbon object.
 *
 * <strong i="8">@return</strong> \Carbon\Carbon|null
 */
public function transformDate($value, $format = 'Y-m-d')
{
    try {
        return \Carbon\Carbon::instance(\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value));
    } catch (\ErrorException $e) {
        return \Carbon\Carbon::createFromFormat($format, $value);
    }
}
class Sample implements ToModel
{
    public function model(array $row)
    {
        return new user([
            'name' => $row[0],
            'email' => $row[1],
            'birth-date' => $this->transformDate($row[2]),
        ]);
    }
}

Также проблема с этим.

@devinfd
Я использую вашу вспомогательную функцию для форматирования даты. Он возвращается в этом формате 1943-10-15 00:00:00

Могу ли я вернуть его как 10/15/1943 ?

Или было бы лучше обрабатывать этот тип форматирования во внешнем интерфейсе?

Мне любопытно, кто-нибудь хочет / ожидает / имеет вариант использования для .... полей даты, чтобы вернуться в виде этих странных целых чисел? Кажется, это должно происходить автоматически при импорте. Я не могу представить себе сценарий, при котором дата, которую можно просмотреть в Excel как «2019-01-10», должна возвращаться мне как 43473.0

@nickpoulos , именно так Excels сохраняет его, когда числовой формат является форматом даты.
PhpSpreadsheet пытается преобразовать его в объект datetime, см. Строку NumberFormat 649. Если это не удается, мы мало что можем с этим поделать.

Как вы можете убедиться, что дата не указана?

Кто угодно может опубликовать код показа статуса выполнения при импорте файла Excel.

Кто-нибудь решил проблему ??

Кто-нибудь решил проблему ??

@ Jaikangam93
Немного поздно, но мне удалось исправить это с помощью _WithCustomValueBinder_, написал небольшую проверку для координаты столбца даты и использовал метод excelToDateTimeObject в PhpOffice \ PhpSpreadsheet \ Shared \ Date и, наконец, установил значение для ячейки как строку.

Вот код:

   public function bindValue(Cell $cell, $value)
   {
       if(preg_match('/^E*\d*$/', $cell->getCoordinate())){
                $cell->setValueExplicit(Date::excelToDateTimeObject($value)->format('Y-m-d'), DataType::TYPE_STRING);
        }
        else{
            $cell->setValueExplicit($value, DataType::TYPE_STRING);
        }

        return true;
    }

Обязательно импортируйте все пространства имен и реализуйте _WithCustomValueBinder_ на импортере.

У меня проблема, я импортирую файл, который в некоторых ячейках может иметь любое значение (числовое, строковое, дата ...), как я могу узнать, что исходное значение в этой ячейке было датой, поэтому я могу его отформатировать правильно?

Вам нужно конкретно знать, какой столбец будет содержать запись даты, а затем указывать на нее в вашем исправлении. Если вы используете заголовки, вы можете проверить с помощью ключа столбца. Как правило, определить значение даты было бы легко, если бы оно было общим, но в Excel дата сохраняется как метка времени Excel, которая по сути является строкой. Что вы можете сделать, так это выяснить шаблон временных меток и, соответственно, проверить каждый столбец, и, если есть совпадение, вы можете продолжить форматирование.

Из того, что я видел, временные метки Excel обычно имеют шаблон _ \ d * \. \ D _, который представляет собой набор цифр, за которыми следует точка, а затем еще одна цифра.

Надеюсь, это поможет.

Это невозможно, данные в этих столбцах могут иметь любой тип значения (числа, даты, строки), и я не могу знать это заранее, эти данные, наконец, загружаются в поле json. И это вполне может быть значение типа 42434.12, а не дата.

Я начал работать над PR, но быстро понял, что это будет сложно реализовать. @patrickbrouwers собирался просмотреть кодовую базу, чтобы увидеть, каким будет лучший путь вперед.

А пока я просто использую вспомогательный метод для своего объекта Import:

/**
 * Transform a date value into a Carbon object.
 *
 * <strong i="9">@return</strong> \Carbon\Carbon|null
 */
public function transformDate($value, $format = 'Y-m-d')
{
    try {
        return \Carbon\Carbon::instance(\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value));
    } catch (\ErrorException $e) {
        return \Carbon\Carbon::createFromFormat($format, $value);
    }
}
class Sample implements ToModel
{
    public function model(array $row)
    {
        return new user([
            'name' => $row[0],
            'email' => $row[1],
            'birth-date' => $this->transformDate($row[2]),
        ]);
    }
}

Работает как шарм. Спасибо

@ vin33t @patrickbrouwers
Сначала проверьте наличие пустого поля (перед try / catch), иначе функция вернет дату 1970-01-01, если она пуста:

if(!strlen($value)) return null;

Привет, я нашел это решение, у меня работает!

protected function formatDateExcel($date){ if (gettype($date) === 'double') { $birthday = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($date); return $birthday->format('n/j/Y'); } return $date; }

и я использую

$this->formatDateExcel($row['birthday']);

Кто-нибудь может объяснить, как работать с 04/04/1977 как в форматировании m/d/Y ?

Date::excelToDateTimeObject($row['birth_date'])

как я вижу, он не принимает аргумент формата?

для демонстрационных целей:

'birthday' => $row['birth_date'] ? Date::excelToDateTimeObject($row['birth_date']) : null,

И, конечно, есть 04/04/1977 Я получаю 0000-00-00.... , потому что он не знает, какой месяц, а какой день: man_facepalming:

ОБНОВЛЕНО :
Nevermind Оказывается, проблема только в людях, чей день рождения меньше 1970 года ...

2-е изменение изменило тип столбца с timestamp на dateTime - дело закрыто.

Я хочу импортировать файл Excel с полем даты, но у меня есть ошибка между форматом 1992/03/01, сделанным 1992-03-01, или есть другой способ преодолеть это?
Пожалуйста, помогите мне решить эту проблему.

Я хочу импортировать файл Excel с полем даты, но у меня есть ошибка между форматом 1992/03/01, сделанным 1992-03-01, или есть другой способ преодолеть это?
Пожалуйста, помогите мне решить эту проблему.

Привет, низардани,
Попробуйте сделать это в своем файле Excel. Отредактируйте в столбце поля даты 1992-03-01.
Эта дата 1992-03-01 не будет принята Laravel Excel, поэтому вам необходимо обновить / изменить тип поля столбца.
Шаг для решения: -
1) Откройте файл Excel для импорта. Выберите столбец даты Щелкните правой кнопкой мыши, выберите ячейку формата
2) Форматировать ячейку> Число> Пользовательский> Тип> дд-мм-гггг чч: мм: сс
Пример будет таким> 03-06-2018 00:00:00
После изменения формата ячейки. Если он появится как 03-06-2018.
Убедитесь, что вы дважды щелкнули по нему, чтобы он изменился на 03-06-2018 00:00:00.

Снова импортируйте файл .... Надеюсь, теперь все заработает.

Я хочу импортировать файл Excel с полем даты, но у меня есть ошибка между форматом 1992/03/01, сделанным 1992-03-01, или есть другой способ преодолеть это?
Пожалуйста, помогите мне решить эту проблему.

Привет, низардани,
Попробуйте сделать это в своем файле Excel. Отредактируйте в столбце поля даты 1992-03-01.
Эта дата 1992-03-01 не будет принята Laravel Excel, поэтому вам необходимо обновить / изменить тип поля столбца.
Шаг для решения: -

  1. Откройте файл Excel для импорта. Выберите столбец даты Щелкните правой кнопкой мыши, выберите ячейку формата
  2. Форматировать ячейку> Число> Пользовательский> Тип> дд-мм-гггг чч: мм: сс
    Пример будет таким> 03-06-2018 00:00:00
    После изменения формата ячейки. Если он появится как 03-06-2018.
    Убедитесь, что вы дважды щелкнули по нему, чтобы он изменился на 03-06-2018 00:00:00.

Снова импортируйте файл .... Надеюсь, теперь все заработает.

15788022402754767278257596482531
У меня снова возникает проблема после того, как я изменил тип даты в Excel, что мне использовать Carbon nesbot?

Итак, кто-нибудь придумал, как определить, что ячейка является форматом даты, прежде чем решать, нужно ли преобразовывать ее в объект даты или даты и времени? Excel, очевидно, знает, поскольку сохраненное поле даты вернется как поле даты, когда электронная таблица снова загружается в Excel. Таким образом, для этой ячейки должны быть какие-то метаданные, которые могут сказать нам, что это ячейка, содержащая дату.

Быстро взглянул, и он на листе:

<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" mc:Ignorable="x14ac xr xr2 xr3" xr:uid="{A7382266-55D2-4E6B-95C9-90E61705D12D}">
   ...
   <sheetData>
      <row r="4" spans="1:1" x14ac:dyDescent="0.25">
         <c r="A4">
            <v>54321</v>
         </c>
      </row>
      <row r="5" spans="1:1" x14ac:dyDescent="0.25">
         <c r="A5" s="1">
            <v>43924</v>
         </c>
      </row>
   </sheetData>
   ...
</worksheet>

Ячейка A4 - это целое число, а ячейка A5 - это дата. Другое дело - атрибут s="1" . Это делает свидание. Если я добавлю этот атрибут в ячейку A4, отредактировав свой Book1.xlsx , открытие электронной таблицы даст мне две даты. 54321 - это 20 сентября 2048 года, если вам интересно ;-)

Таким образом, данные находятся в электронной таблице, чтобы сообщить этой библиотеке, что это число не число, а дата. Я не искал это в спецификации, поэтому я полагаю, что здесь есть более тонкие детали, чем это, но дело в том, что приложение, импортирующее электронную таблицу, не должно знать, какой тип данных заранее.

Обновление: действительно отличный ответ . s=1 проходит через пару ссылок перенаправления в styles.xml и в конечном итоге дает вам внутренний формат или настраиваемый формат. Я подозреваю, что формат отображения - это все, что вам нужно, чтобы определить, было ли намерение сделать число датой. Некоторые внутренние форматы достаточно легко запрограммировать жестко, но специальные форматы будет немного сложнее декодировать.

PhpSpreadsheet знает об этом формате. Однако по соображениям производительности рекомендуется читать файл Excel в режиме read_only. Если вы можете отключить это здесь: https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/config/excel.php#L47 PhpSpreadsheet должен автоматически считывать дату как дату. Если вы предпочитаете более производительную настройку, вы можете самостоятельно отформатировать даты с помощью PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($date)

Не проверено, но я считаю, что вам нужно сделать:

 return new user([
        'UserName'           => $row['UserName'],
        'Password'           => $row['Password'],
        'date'               => \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['date']),
    ]);

Я помню, что setReadDataOnly вызывает проблемы в некоторых других ситуациях, поэтому мне неудобно просто добавлять это снова. Возможно, мы сможем сделать это добровольным участником или что-то в этом роде.

К сожалению, этот метод меняет мою дату на 1900-01-08

если вам нужно динамически разрешать поля datetime, я написал метод, который отвечает за автоматическое определение того, является ли значение datetime динамически (независимо от того, знаете ли вы, будет ли в этом столбце datetime), или я пробовал различные типы данных, и он отлично работает

       /**
     * <strong i="6">@param</strong> Cell $cell
     * <strong i="7">@param</strong> $value
     * 
     * <strong i="8">@return</strong> boolean;
     */
    public function bindValue(Cell $cell, $value)
    {
        $formatedCellValue = $this->formatDateTimeCell($value, $datetime_output_format = "d-m-Y H:i:s", $date_output_format = "d-m-Y", $time_output_format = "H:i:s" );
        if($formatedCellValue != false){
            $cell->setValueExplicit($formatedCellValue, DataType::TYPE_STRING);
            return true;
        }

        // else return default behavior
        return parent::bindValue($cell, $value);
    }


    /**
     * 
     * Convert excel-timestamp to Php-timestamp and again to excel-timestamp to compare both compare
     * By Leonardo J. Jauregui ( <strong i="9">@Nanod10</strong> | siskit dot com )
     * 
     * <strong i="10">@param</strong> $value (cell value)
     * <strong i="11">@param</strong> String $datetime_output_format
     * <strong i="12">@param</strong> String $date_output_format
     * <strong i="13">@param</strong> String $time_output_format
     * 
     * <strong i="14">@return</strong> $formatedCellValue
     */
    private function formatDateTimeCell( $value, $datetime_output_format = "Y-m-d H:i:s", $date_output_format = "Y-m-d", $time_output_format = "H:i:s" )
    {

        // is only time flag
        $is_only_time = false;

        // Divide Excel-timestamp to know if is Only Date, Only Time or both of them
        $excel_datetime_exploded = explode(".", $value);

        // if has dot, maybe date has time or is only time
        if(strstr($value,".")){
            // Excel-timestamp to Php-DateTimeObject
            $dateTimeObject = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
            // if Excel-timestamp > 0 then has Date and Time 
            if(intval($excel_datetime_exploded[0]) > 0){
                // Date and Time
                $output_format = $datetime_output_format;
                $is_only_time = false;
            }else{
                // Only time
                $output_format = $time_output_format;
                $is_only_time = true;
            }
        }else{
            // Only Date
            // Excel-timestamp to Php-DateTimeObject
            $dateTimeObject = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
            $output_format = $date_output_format;
            $is_only_time = false;
        }

        // Php-DateTimeObject to Php-timestamp
        $phpTimestamp = $dateTimeObject->getTimestamp();

        // Php-timestamp to Excel-timestamp
        $excelTimestamp = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel( $phpTimestamp );

        // if is only Time
        if($is_only_time){
            // 01-01-1970 = 25569
            // Substract to match PhpToExcel conversion
            $excelTimestamp = $excelTimestamp - 25569;
        }

        /* 
        // uncoment to debug manualy and see if working
        $debug_arr = [
                "value"=>$value,
                "value_float"=>floatval($value),
                "dateTimeObject"=>$dateTimeObject,
                "phpTimestamp"=>$phpTimestamp,
                "excelTimestamp"=>$excelTimestamp,
                "default_date_format"=>$dateTimeObject->format('Y-m-d H:i:s'),
                "custom_date_format"=>$dateTimeObject->format($output_format)
            ];

        if($cell->getColumn()=="Q"){
            if($cell->getRow()=="2"){
                if(floatval($value)===$excelTimestamp){
                    dd($debug_arr);
                }
            }
        }

        */

        // if the values match
        if( floatval($value) === $excelTimestamp ){
            // is a fucking date! ;)
            $formatedCellValue = $dateTimeObject->format($output_format);
            return $formatedCellValue;
        }else{
            // return normal value
            return false;
        }

    }

Привет. У меня такая же проблема. Я использую интерфейс ToCollection.

Метод public function collection(Collection $collection) объявлен нормально, вызывается и выполняет все операции.

Проблема : в методе collection () одна ячейка datetime преобразуется в число вместо строки datetime. Как отключить это преобразование? Он не понимает формат значения. И я все равно не могу изменить файл XLS.

class MyImport implements WithCustomValueBinder, ToCollection
- у меня тоже не работает, так как bindValues не вызывается перед collection ();

ToModel использовать не могу, так как уже реализовал collection (). И формат XLS не линейный. Нужна индивидуальная обработка.

Здравствуйте, похожая проблема здесь. Я хотел использовать валидатор laravel, чтобы проверить определенный формат даты и убедиться, что он находится перед другим полем даты. Это как-то возможно? Единственное решение, которое я нашел до сих пор, - это сохранить ячейку даты и времени в виде строки в файле xlsx и запустить валидатор следующим образом:

public function rules(): array
    {
        return [
            'start' => 'required|date_format:d/m/y H:i|before:*.end',
            'end' => 'required|date_format:d/m/y H:i|after:*.end',
        ];
    }

Да, я также вручную меняю формат ячейки на String, а затем он анализирует нормально. Но я не могу всегда изменять файлы xls, так как они будут регулярно транслироваться с форматом ячеек Dateteme.

Проблема все еще сохраняется.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги