Laravel-excel: [BUG] problema de formato de data de importação

Criado em 14 out. 2018  ·  38Comentários  ·  Fonte: Maatwebsite/Laravel-Excel

  • [x] Capaz de reproduzir o comportamento fora do seu código, o problema é isolado no Laravel Excel.
  • [x] Verificou se seu problema ainda não foi arquivado.
  • [x] Verificado se nenhum PR foi enviado que corrige este problema.

Versões

  • Versão PHP: 7.1
  • Versão do Laravel: 5.7
  • Versão do pacote: 3.1

Descrição

Criei uma nova importação com WithChunkReading e tamanho do lote. O problema que estou enfrentando é o importador converte as colunas de data em carimbo de data / hora (acredito que seja o carimbo de data / hora) 43257.0. depois de investir no problema, encontrei um tópico muito antigo https://github.com/Maatwebsite/Laravel-Excel/issues/404 e uma das soluções que o corrigiu para mim foi definir o valor verdadeiro como falso na classe ReadChunk disponível em vendor/maatwebsite/excel/src/Jobs/ReadChunk.php . linha é $this->reader->setReadDataOnly(true);
Esta solução funciona por enquanto, mas quando fizermos a atualização do compositor, ela terá desaparecido, pois não é configurável na biblioteca.

Passos para reproduzir

  1. criar excel
  2. adicione coluna com qualquer formato de data.
  3. import Excel usando o importador de método chunk via biblioteca.

Comportamento esperado:

Eu esperaria que a biblioteca carregasse a data conforme o esperado.

Comportamento real:

biblioteca converte data em carimbo de data / hora (assumindo seu carimbo de data / hora)

Informações adicionais

Aqui está minha aula de importação,

` namespace 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 ('nenhum');

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

Comentários muito úteis

Comecei a trabalhar em um PR, mas rapidamente percebi que seria complicado de implementar. @patrickbrouwers iria revisar a base de código para ver qual seria o melhor caminho a seguir.

Nesse ínterim, estou apenas usando um método auxiliar no meu objeto 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]),
        ]);
    }
}

Todos 38 comentários

Não testado, mas acredito que você precisa fazer:

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

Lembro-me de que setReadDataOnly causa problemas em algumas outras situações, então não me sinto confortável apenas adicionando isso de novo. Talvez possamos torná-lo uma preocupação opcional ou algo assim.

Eu também experimentei esse problema. excelToDateTimeObject trouxe de volta a data real, mas pode ser bom ter uma preocupação com isso.

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

Então laravel excel detectaria se a importação implementa WithDateObjects e definir \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($this->cell->getValue()) conforme necessário?

PS: Isso deve se converter em carbono?

Eu aceitaria um PR que adiciona a preocupação WithDates que retorna Carbon datas;)

Bem, eu também entendi o problema, antes disso ele simplesmente funciona bem e agora recebo o erro unexpected data found , e mesmo quando adiciono isso

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

e ainda tenho erro

A non well formed numeric value encountered

também tentei convertê-lo com carbono, mas não funcionou

Seu $row['date'] não é um número inteiro então. excelToDateTimeObject só funciona quando a data é formatada como uma data no excel.

hmmm já formatado na data

screen shot 2018-10-25 at 16 48 06

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

Tente depurá-lo, veja o que está em $row['data'] .

Posso enviar um PR com WithDates mas vai demorar pelo menos uma semana. Estou em uma conferência e não consigo me concentrar nisso agora.

é 1/1/18 deve ser convertido automaticamente da data do Excel para a data na tabela do mysql, certo? Eu uso este pacote antes e está funcionando, apenas encontre ... mas agora estou recebendo este erro

Se você receber 1/1/18 volta, é claro que não é um número inteiro :) Portanto, você não deve usar excelToDateTimeObject . Em vez disso, você precisa usar Carbon::createFromFormat(...) para lidar com isso

@patrickbrouwers Talvez a preocupação withDates possa primeiro tentar excelToDateTimeObject e se isso lançar uma exceção, tente Carbon :: createFromFormat (). O método dateColumn pode indicar o formato da data.

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

Ps: Eu tentei escrever este código no meu telefone e não consigo descobrir como fazer os tiques para trás

@devinfd Parece bom :)

ps consertou seus tique-taques;)

@devinfd o quão longe você já está com a implementação? Também estaria interessado.

Comecei a trabalhar em um PR, mas rapidamente percebi que seria complicado de implementar. @patrickbrouwers iria revisar a base de código para ver qual seria o melhor caminho a seguir.

Nesse ínterim, estou apenas usando um método auxiliar no meu objeto 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]),
        ]);
    }
}

Também tendo um problema com isso.

@devinfd
Estou usando sua função auxiliar para formatar minha data. Ele retorna neste formato 1943-10-15 00:00:00

Existe alguma maneira de devolvê-lo como 10/15/1943 ?

Ou seria melhor lidar com esse tipo de formatação no front end?

Estou curioso, alguém quer / espera / tem um caso de uso para .... campos de data voltarem como esses inteiros estranhos? Parece que essas coisas devem acontecer automaticamente na importação. Não consigo imaginar um cenário em que uma data que está visível no excel como "10/01/2019" volte para mim como 43473,0

@nickpoulos é exatamente como o Excels armazena quando o formato de número é um formato de data.
PhpSpreadsheet tenta convertê-lo em um objeto datetime, consulte NumberFormat linha 649. Se isso falhar, não há muito que possamos fazer sobre isso.

Como você pode validar que a data está vazia?

Qualquer pessoa pode postar o código de exibição de status de progresso, no arquivo Excel de importação.

Alguém resolveu o problema ??

Alguém resolveu o problema ??

@ Jaikangam93
Um pouco tarde, mas resolvi com a ajuda de _WithCustomValueBinder_ escrevi uma pequena verificação para a coordenada da coluna de data e usei o método excelToDateTimeObject em PhpOffice \ PhpSpreadsheet \ Shared \ Date e finalmente defina o valor da célula como string.

Aqui está o 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;
    }

Certifique-se de importar todos os namespaces e implementar _WithCustomValueBinder_ no importador.

Estou com um problema, estou importando um arquivo que em algumas células pode ter qualquer valor (numérico, string, data ...), como posso saber que o valor original naquela célula era uma data, para poder formatá-lo corretamente?

Você precisará saber especificamente qual coluna conteria uma entrada de data e, em seguida, referenciá-la em sua correção. Se estiver usando títulos, você pode verificar com a chave de coluna. Geralmente, descobrir um valor de data seria fácil se fosse genérico, mas com o Excel, ele armazena a data como carimbo de data / hora do Excel, que é basicamente uma string. O que você pode fazer é descobrir um padrão nos carimbos de data / hora e, portanto, fazer uma verificação para cada coluna e, se houver uma correspondência, você pode continuar com a formatação.

Pelo que tenho visto, os timestamps do excel geralmente têm o padrão _ \ d * \. \ D _ que é um monte de dígitos seguidos por um ponto e outro dígito.

Espero que ajude.

Isso não é possível, os dados nestas colunas podem ter qualquer tipo de valor (números, datas, strings) e não posso saber de antemão, esses dados são finalmente carregados em um campo json. E poderia ser perfeitamente um valor como 42434,12 e não uma data.

Comecei a trabalhar em um PR, mas rapidamente percebi que seria complicado de implementar. @patrickbrouwers iria revisar a base de código para ver qual seria o melhor caminho a seguir.

Nesse ínterim, estou apenas usando um método auxiliar no meu objeto 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]),
        ]);
    }
}

Funciona como um encanto. Obrigada

@ vin33t @patrickbrouwers
Verifique primeiro se há um campo vazio (antes de try / catch), caso contrário, a função retornará a data 1970-01-01 se vazia:

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

Olá, acho que esta solução funciona para mim!

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

e eu uso

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

Alguém pode explicar como lidar com 04/04/1977 na formatação de m/d/Y ?

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

como eu vejo não aceita argumento de formato?

para fins de demonstração:

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

E é claro que nós há 04/04/1977 que recebo 0000-00-00.... , porque não sabe qual é o mês e qual é o dia: man_facepalming:

ATUALIZADO :
Deixa pra lá Acontece que o problema é apenas para pessoas cujo aniversário é abaixo de 1970 ...

A 2ª edição mudou o tipo de coluna de timestamp para dateTime - caso encerrado.

Quero importar um arquivo excel com o campo data, mas tenho um erro entre o formato 01/03/1992 sendo feito 01/03/1992, ou existe outra forma de contornar isso?
Por favor me ajude a superar esse problema.

Quero importar um arquivo excel com o campo data, mas tenho um erro entre o formato 01/03/1992 sendo feito 01/03/1992, ou existe outra forma de contornar isso?
Por favor me ajude a superar esse problema.

Oi nizardani,
Tente assim, em seu arquivo Excel, edite na coluna do campo de data 1992-03-01.
Esta Data 01-03-1992 não será aceita pelo Laravel Excel, então você precisa atualizar / alterar o tipo de campo da coluna.
Etapa para resolver: -
1) Abra o arquivo excel para importar. Selecione a coluna de data Clique com o botão direito, selecione Formatar Célula
2) Formatar célula> Número> Personalizado> Tipo> dd-mm-aaaa hh: mm: ss
A amostra será assim> 03-06-2018 00:00:00
Depois de alterar a célula de formato. Se estiver aparecendo como 03-06-2018.
Certifique-se de clicar duas vezes nele para que mude para assim 03-06-2018 00:00:00.

Novamente, importar arquivo .... Espero que funcione agora.

Quero importar um arquivo excel com o campo data, mas tenho um erro entre o formato 01/03/1992 sendo feito 01/03/1992, ou existe outra forma de contornar isso?
Por favor me ajude a superar esse problema.

Oi nizardani,
Tente assim, em seu arquivo Excel, edite na coluna do campo de data 1992-03-01.
Esta Data 01-03-1992 não será aceita pelo Laravel Excel, então você precisa atualizar / alterar o tipo de campo da coluna.
Etapa para resolver: -

  1. Abra o arquivo excel para importar. Selecione a coluna de data Clique com o botão direito, selecione Formatar Célula
  2. Formatar célula> Número> Personalizado> Tipo> dd-mm-aaaa hh: mm: ss
    A amostra será assim> 03-06-2018 00:00:00
    Depois de alterar a célula de formato. Se estiver aparecendo como 03-06-2018.
    Certifique-se de clicar duas vezes nele para que mude para assim 03-06-2018 00:00:00.

Novamente, importar arquivo .... Espero que funcione agora.

15788022402754767278257596482531
Eu recebo o problema novamente depois de alterar um tipo de data no excel, o que devo usar o nesbot de carbono?

Então, alguém descobriu como saber se a célula é um formato de data antes de decidir se ela precisa ser convertida em um objeto de data ou data e hora? O Excel obviamente sabe, já que um campo de data salvo retornará como um campo de data quando a planilha for carregada novamente no Excel. Portanto, deve haver alguns metadados para essa célula que podem nos dizer que é uma célula que contém uma data.

Dei uma olhada rápida e está na planilha:

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

A célula A4 é um número inteiro e a célula A5 é uma data. O diferente é o atributo s="1" . Isso o torna um encontro. Se eu adicionar este atributo à célula A4 editando meu Book1.xlsx , abrir a planilha me dará duas datas. 54321 é 20 de setembro de 2048, caso você esteja se perguntando ;-)

Então os dados estão na planilha para informar a essa biblioteca que esse número não é um número, mas uma data. Eu não pesquisei isso nas especificações, então espero que haja mais sutileza envolvida do que isso, mas o ponto é, o aplicativo que importa a planilha não deve precisar saber com antecedência qual é o tipo de dados.

Atualização: realmente ótima resposta aqui O s=1 passa por algumas referências de redirecionamento em styles.xml e, finalmente, fornece um formato interno ou um formato personalizado. Suspeito que o formato de exibição é tudo que você precisa para determinar se a intenção era que o número fosse uma data. Alguns dos formatos internos são fáceis de codificar, mas os formatos personalizados serão um pouco mais difíceis de decodificar.

PhpSpreadsheet conhece este formato. No entanto, por motivos de desempenho, é recomendável ler o arquivo Excel no modo somente leitura. Se você pode desativar isso aqui: https://github.com/Maatwebsite/Laravel-Excel/blob/3.1/config/excel.php#L47 PhpSpreadsheet deve então ler a data como data automaticamente. Se preferir a configuração de melhor desempenho, você mesmo pode formatar as datas usando o PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($date)

Não testado, mas acredito que você precisa fazer:

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

Lembro-me de que setReadDataOnly causa problemas em algumas outras situações, então não me sinto confortável apenas adicionando isso de novo. Talvez possamos torná-lo uma preocupação opcional ou algo assim.

Infelizmente, esse método mudou minha data para 08/01/1900

se sua necessidade envolve resolver campos de data e hora dinamicamente, escrevi um método que é responsável por detectar automaticamente se o valor é uma data e hora dinamicamente (independentemente de você saber ou não se haverá uma data e hora nessa coluna) ou tentei vários tipos de dados e funciona bem

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

    }

Olá. Eu tenho o mesmo problema. Estou usando a interface ToCollection.

O método public function collection(Collection $collection) é declarado ok e é chamado e possui todos os processamentos.

Problema : No método collection (), uma célula de data e hora é convertida em um número em vez de uma string de data e hora. Como faço para desabilitar essa conversão? Ele não entende o formato do valor. E eu não posso modificar o arquivo XLS de qualquer maneira.

class MyImport implements WithCustomValueBinder, ToCollection
- não funciona nem para mim, pois bindValues não é chamado antes da coleção ();

ToModel não posso usar, como já implementado collection (). E o formato do XLS não é linear. Necessita de processamento personalizado.

Olá, problema semelhante aqui. Eu queria usar o validador do laravel para verificar um determinado formato de data e ter certeza de que ele está antes de outro campo de data. Isso é de alguma forma possível? A única solução que encontrei até agora é salvar a célula de data e hora como string no arquivo xlsx e executar o validador assim:

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

Sim, eu também altero manualmente o formato da célula para String e então analisa ok. Mas não posso modificar sempre os arquivos xls, pois eles serão regularmente transmitidos com o formato de célula Dateteme.

O problema ainda persiste.

Esta página foi útil?
0 / 5 - 0 avaliações