Laravel-excel: [BUG] problema de formato de fecha de importación

Creado en 14 oct. 2018  ·  38Comentarios  ·  Fuente: Maatwebsite/Laravel-Excel

  • [x] Capaz de reproducir el comportamiento fuera de su código, el problema está aislado en Laravel Excel.
  • [x] Verificó que su problema aún no se haya presentado.
  • [x] Se verificó si no se envió ningún RP que solucione este problema.

Versiones

  • Versión de PHP: 7.1
  • Versión de Laravel: 5.7
  • Versión del paquete: 3.1

Descripción

Creé una nueva importación con WithChunkReading y tamaño de lote. El problema al que me enfrento es que el importador convierte las columnas de fecha en una marca de tiempo (creo que su marca de tiempo) 43257.0. después de invertir el problema, encontré un hilo muy antiguo https://github.com/Maatwebsite/Laravel-Excel/issues/404 y una de las soluciones que lo solucionó fue establecer el valor verdadero en falso en la clase ReadChunk disponible en vendor/maatwebsite/excel/src/Jobs/ReadChunk.php . la línea es $this->reader->setReadDataOnly(true);
Esta solución funciona por ahora, pero cuando hagamos la actualización del compositor, desaparecerá ya que no se puede configurar en la biblioteca.

Pasos para reproducir

  1. crear Excel
  2. agregue una columna con cualquier formato de fecha.
  3. importar Excel usando el importador de método de fragmentos a través de la biblioteca.

Comportamiento esperado:

Esperaría que la biblioteca cargue la fecha como se esperaba.

Comportamiento real:

la biblioteca convierte la fecha en marca de tiempo (asumiendo su marca de tiempo)

información adicional

Aquí está mi clase de importación

' espacio de nombres App \ Imports;

use App \ Sample;
use Maatwebsite \ Excel \ Concerns \ ToModel;
use Maatwebsite \ Excel \ Concerns \ WithBatchInserts;
use Maatwebsite \ Excel \ Concerns \ WithChunkReading;
use Maatwebsite \ Excel \ Concerns \ WithHeadingRow;
use Maatwebsite \ Excel \ Imports \ HeadingRowFormatter;

HeadingRowFormatter :: default ('ninguno');

clase Sample implementa 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

Comentario más útil

Comencé a trabajar en un PR, pero rápidamente me di cuenta de que iba a ser complicado de implementar. @patrickbrouwers iba a revisar el código base para ver cuál sería la mejor manera de avanzar.

Mientras tanto, solo estoy usando un método auxiliar en mi objeto Importar:

/**
 * 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]),
        ]);
    }
}

Todos 38 comentarios

No probado, pero creo que debe hacer:

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

Recuerdo que setReadDataOnly causó problemas en algunas otras situaciones, por lo que no me siento cómodo con solo agregar eso nuevamente. Quizás podamos convertirlo en una preocupación de suscripción voluntaria o algo así.

También experimenté este problema. excelToDateTimeObject devolvió la fecha real, pero sería bueno tener una preocupación por esto.

Quizás algo como:

    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'];
        }
    }

Entonces laravel excel detectaría si la importación implementa WithDateObjects y establecería \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($this->cell->getValue()) según sea necesario.

PD: ¿Debería convertirse esto en carbono?

Aceptaría un PR que agregue la preocupación WithDates que devuelve Carbon fechas;)

bueno, yo también tengo el problema, antes de eso funciona bien y ahora tengo el error unexpected data found , e incluso cuando agrego esto

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

y todavía tengo un error

A non well formed numeric value encountered

También traté de convertirlo con carbono pero no funcionó.

Entonces, su $row['date'] no es un número entero. excelToDateTimeObject solo funciona cuando la fecha tiene el formato de fecha en Excel.

hmmm ya está formateado en la fecha

screen shot 2018-10-25 at 16 48 06

estoy usando "maatwebsite/excel": "^3.1",

Intente depurarlo, vea qué hay en $row['data'] .

Puedo enviar un PR con WithDates pero será al menos una semana. Estoy en una conferencia y no puedo concentrarme en eso ahora.

es 1/1/18 , debería convertirse automáticamente de la fecha de Excel a la fecha en la tabla mysql, ¿verdad? uso este paquete antes y funciona, solo encuentra ... pero ahora aparece este error

Si recupera 1/1/18 , es claro que no es un número entero :) Por lo tanto, no debe usar excelToDateTimeObject . En su lugar, debe usar Carbon::createFromFormat(...) para lidiar con esto

@patrickbrouwers Quizás la preocupación con withDates pueda primero intentar excelToDateTimeObject y si eso arroja una excepción, intente Carbon :: createFromFormat (). El método dateColumn podría indicar el formato de la fecha.

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

Ps: intenté escribir este código en mi teléfono, no puedo entender cómo hacer los ticks posteriores

@devinfd Suena bien :)

ps arregló sus garrapatas traseras;)

@devinfd ¿

Comencé a trabajar en un PR, pero rápidamente me di cuenta de que iba a ser complicado de implementar. @patrickbrouwers iba a revisar el código base para ver cuál sería la mejor manera de avanzar.

Mientras tanto, solo estoy usando un método auxiliar en mi objeto Importar:

/**
 * 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]),
        ]);
    }
}

También tengo un problema con esto.

@devinfd
Estoy usando su función de ayuda para formatear mi fecha. Vuelve en este formato 1943-10-15 00:00:00

¿De todos modos podría devolverlo como 10/15/1943 ?

¿O sería mejor manejar ese tipo de formato en la interfaz?

Tengo curiosidad, ¿alguien quiere / espera / tiene un caso de uso para ... que los campos de fecha regresen como estos enteros extraños? Parece que estas cosas deberían suceder automáticamente en la importación. No puedo imaginar un escenario en el que una fecha que se puede ver en Excel como "2019-01-10" deba volver a mí como 43473.0

@nickpoulos , así es como Excels lo almacena cuando el formato de número es un formato de fecha.
PhpSpreadsheet intenta convertirlo en un objeto de fecha y hora, consulte la línea 649 de NumberFormat. Si eso falla, no hay mucho que podamos hacer al respecto.

¿Cómo se puede validar que la fecha esté vacía?

Cualquiera puede publicar el código de estado de progreso, en el archivo de importación de Excel.

¿Alguien resolvió el problema?

¿Alguien resolvió el problema?

@ Jaikangam93
Un poco tarde, pero lo solucioné con la ayuda de _WithCustomValueBinder_ escribí un pequeño cheque para la coordenada de la columna de fecha y usé el método excelToDateTimeObject en PhpOffice \ PhpSpreadsheet \ Shared \ Date y finalmente establecí el valor de la celda como cadena.

Aquí está el código:

   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;
    }

Asegúrese de importar todos los espacios de nombres e implementar _WithCustomValueBinder_ en el importador.

Tengo un problema, estoy importando un archivo que en algunas celdas puede tener cualquier valor (numérico, cadena, fecha ...), ¿cómo puedo saber que el valor original en esa celda era una fecha, para poder formatearlo? ¿correctamente?

Deberá saber específicamente qué columna contendría una entrada de fecha y luego hacer referencia a la misma en su corrección. Si está utilizando Encabezados, puede verificar con la clave de columna. En general, averiguar un valor de fecha sería fácil si fuera genérico, pero con Excel, almacena la fecha como marca de tiempo de Excel, que es básicamente una cadena. Lo que puede hacer es averiguar un patrón en las marcas de tiempo y, en consecuencia, hacer una verificación para cada columna y, si hay una coincidencia, puede continuar con el formato.

Por lo que he visto, las marcas de tiempo de Excel generalmente tienen el patrón _ \ d * \. \ D _ que es un montón de dígitos seguidos de un punto y luego otro dígito.

Espero que ayude.

Eso no es posible, los datos en estas columnas pueden tener cualquier tipo de valor (números, fechas, cadenas) y no puedo saberlo de antemano, estos datos finalmente se cargan en un campo json. Y podría ser perfectamente un valor como 42434.12 y no ser una fecha.

Comencé a trabajar en un PR, pero rápidamente me di cuenta de que iba a ser complicado de implementar. @patrickbrouwers iba a revisar el código base para ver cuál sería la mejor manera de avanzar.

Mientras tanto, solo estoy usando un método auxiliar en mi objeto Importar:

/**
 * 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]),
        ]);
    }
}

Funciona de maravilla. Gracias

@ vin33t @patrickbrouwers
Primero verifique si hay un campo vacío (antes de try / catch), de lo contrario, la función devolverá la fecha 1970-01-01 si está vacía:

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

Hola, encuentro esta solución, ¡funciona para mí!

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

y yo uso

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

¿Alguien puede explicar cómo lidiar con 04/04/1977 como en el formato m/d/Y ?

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

como veo, no acepta el argumento de formato?

para fines de demostración:

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

Y por supuesto que hay 04/04/1977 yo obtengo 0000-00-00.... , porque no sabe cuál es el mes y cuál es el día: man_facepalming:

ACTUALIZADO :
No importa , resulta que el problema es solo para personas cuyo cumpleaños es antes de 1970 ...

La segunda edición cambió el tipo de columna de timestamp a dateTime - caso cerrado.

Quiero importar un archivo de Excel con el campo de fecha, pero tengo un error entre el formato 1992/03/01 que se hizo 1992-03-01, ¿o hay otra forma de solucionarlo?
Ayúdame a superar este problema.

Quiero importar un archivo de Excel con el campo de fecha, pero tengo un error entre el formato 1992/03/01 que se hizo 1992-03-01, ¿o hay otra forma de solucionarlo?
Ayúdame a superar este problema.

Hola nizardani,
Intente así, en su archivo de Excel Edite en la columna del campo de fecha 1992-03-01.
Esta fecha 1992-03-01 no la aceptará Laravel Excel, por lo que debe actualizar / cambiar el tipo de campo de la columna.
Paso para resolver: -
1) Abra el archivo de Excel para importar. Seleccione la columna de fecha Haga clic con el botón derecho, seleccione Formato de celda
2) Formato de celda> Número> Personalizado> Tipo> dd-mm-aaaa hh: mm: ss
La muestra será así> 03-06-2018 00:00:00
Después de cambiar el formato de celda. Si aparece como 03-06-2018.
Asegúrese de hacer doble clic en eso para que cambie a este 03-06-2018 00:00:00.

Nuevamente Importar archivo ... Espero que funcione ahora.

Quiero importar un archivo de Excel con el campo de fecha, pero tengo un error entre el formato 1992/03/01 que se hizo 1992-03-01, ¿o hay otra forma de solucionarlo?
Ayúdame a superar este problema.

Hola nizardani,
Intente así, en su archivo de Excel Edite en la columna del campo de fecha 1992-03-01.
Esta fecha 1992-03-01 no la aceptará Laravel Excel, por lo que debe actualizar / cambiar el tipo de campo de la columna.
Paso para resolver: -

  1. Abra el archivo de Excel para Importar. Seleccione la columna de fecha Haga clic con el botón derecho, seleccione Formato de celda
  2. Dar formato a celda> Número> Personalizado> Tipo> dd-mm-aaaa hh: mm: ss
    La muestra será así> 03-06-2018 00:00:00
    Después de cambiar el formato de celda. Si aparece como 03-06-2018.
    Asegúrese de hacer doble clic en eso para que cambie a este 03-06-2018 00:00:00.

Nuevamente Importar archivo ... Espero que funcione ahora.

15788022402754767278257596482531
Vuelvo a tener el problema después de cambiar un tipo de fecha en Excel, ¿qué debo usar Carbon nesbot?

Entonces, ¿alguien ha descubierto cómo saber que la celda tiene un formato de fecha antes de decidir si necesita convertirse en un objeto de fecha o de fecha y hora? Excel obviamente lo sabe, ya que un campo de fecha guardado volverá como un campo de fecha cuando la hoja de cálculo se cargue nuevamente en Excel. Entonces, debe haber algunos metadatos para esa celda que puedan decirnos que es una celda que contiene una fecha.

Eché un vistazo rápido y está en la hoja de trabajo:

<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>

La celda A4 es un número entero y la celda A5 es una fecha. La diferencia es el atributo s="1" . Eso la convierte en una cita. Si agrego este atributo a la celda A4 editando mi Book1.xlsx , abrir la hoja de cálculo me da dos fechas. 54321 es el 20 de septiembre de 2048, en caso de que se lo pregunte ;-)

Entonces, los datos están en la hoja de cálculo para informar a esta biblioteca que este número no es un número, sino una fecha. No he buscado esto en la especificación, así que espero que haya más sutileza involucrada que eso, pero el punto es que la aplicación que importa la hoja de cálculo no debería tener que saber cuál es el tipo de datos de antemano.

Actualización: muy buena respuesta aquí El s=1 pasa por un par de referencias de redireccionamiento en styles.xml y finalmente le da un formato interno o un formato personalizado. Sospecho que el formato de visualización es todo lo que necesita para determinar si la intención era que el número fuera una fecha. Algunos de los formatos internos son bastante fáciles de codificar, pero los formatos personalizados serán un poco más difíciles de decodificar.

PhpSpreadsheet conoce este formato. Sin embargo, por razones de rendimiento, se recomienda leer el archivo de Excel en modo de solo lectura. Si puede deshabilitar eso aquí: https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/config/excel.php#L47 PhpSpreadsheet debería leer la fecha como fecha automáticamente. Si prefiere la configuración de mayor rendimiento, puede formatear las fechas usted mismo usando PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($date)

No probado, pero creo que debe hacer:

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

Recuerdo que setReadDataOnly causó problemas en algunas otras situaciones, por lo que no me siento cómodo con solo agregar eso nuevamente. Quizás podamos convertirlo en una preocupación de suscripción voluntaria o algo así.

Lamentablemente, este método cambia mi fecha a 1900-01-08

si su necesidad implica resolver los campos de fecha y hora de forma dinámica, he escrito un método que es responsable de detectar automáticamente si el valor es una fecha y hora de forma dinámica (independientemente de si sabe o no si habrá una fecha y hora en esa columna) o he probado varios tipos de datos y funciona bien

       /**
     * <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;
        }

    }

Hola. Tengo el mismo problema. Estoy usando la interfaz ToCollection.

El método public function collection(Collection $collection) se declara ok y se llama y tiene todos los procesamientos.

Problema : en el método collection (), una celda de fecha y hora se convierte en un número en lugar de una cadena de fecha y hora. ¿Cómo desactivo esta conversión? No comprende el formato de valor. Y no puedo modificar el archivo XLS de todos modos.

class MyImport implements WithCustomValueBinder, ToCollection
- no funciona tampoco para mí, ya que bindValues no se llama antes de la colección ();

ToModel no puedo usar, ya que collection () ya está implementado. Y el formato de XLS no es lineal. Necesita procesamiento personalizado.

Hola, problema similar aquí. Quería usar el validador de laravel para verificar un determinado formato de fecha y asegurarme de que esté antes de otro campo de fecha. ¿Es esto posible de alguna manera? La única solución que encontré hasta ahora es guardar la celda de fecha y hora como una cadena en el archivo xlsx y ejecutar el validador de esta manera:

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',
        ];
    }

Sí, también cambio manualmente el formato de celda a String y luego analiza bien. Pero no puedo modificar siempre los archivos xls, ya que se transmitirán regularmente con formato de celda Dateteme.

El problema aún persiste.

¿Fue útil esta página
0 / 5 - 0 calificaciones