Laravel-excel: [BUG] 导入日期格式问题

创建于 2018-10-14  ·  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为我修复它的解决方案之一是在vendor/maatwebsite/excel/src/Jobs/ReadChunk.php ReadChunk 类中将真值设置为 false $this->reader->setReadDataOnly(true);
该解决方案目前有效,但是当我们进行 composer update 时,它​​将消失,因为它在库中不可配置。

重现步骤

  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('none');

类 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,它添加了返回Carbon日期的WithDates关注点;)

好吧,我也遇到了这个问题,在那之前它运行良好,现在我得到了错误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']

我可以用WithDates发送 PR,但至少需要一周时间。 我正在参加一个会议,现在不能专注于此。

它是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这就是当 numberformat 是日期格式时
PhpSpreadsheet 尝试将其转换为日期时间对象,请参阅 NumberFormat 第 649 行。如果失败,我们无能为力。

您如何验证日期是否为空?

任何人都可以在导入excel文件时发布进度状态显示的代码。

有没有人解决问题??

有没有人解决问题??

@Jaikangam93
有点晚了,但我碰巧在 _WithCustomValueBinder_ 的帮助下修复了它,对日期列的坐标进行了一些检查,并在 PhpOffice\PhpSpreadsheet\Shared\Date 上使用了 excelToDateTimeObject 方法,最后将单元格的值设置为字符串。

这是代码:

   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:

更新
没关系事实证明这个问题只适用于生日在 1970 年以下的人......

第二次编辑将列类型从timestamp更改dateTime - 案例已关闭。

我想导入一个带有日期字段的 excel 文件,但是我在格式 1992/03/01 和 1992-03-01 之间有错误,或者有其他方法可以克服这个问题吗?
请帮助我克服这个问题。

我想导入一个带有日期字段的 excel 文件,但是我在格式 1992/03/01 和 1992-03-01 之间有错误,或者有其他方法可以克服这个问题吗?
请帮助我克服这个问题。

嗨尼扎达尼,
像这样尝试,在您的 Excel 文件上编辑日期字段 1992-03-01 的栏。
Laravel Excel 不接受此日期 1992-03-01,因此您需要更新/更改列字段类型。
解决步骤:-
1) 打开要导入的 excel 文件。 选择日期栏右键单击,选择格式单元格
2)格式单元格>数字>自定义>类型>dd-mm-yyyy hh:mm:ss
示例将是这样的 > 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 的栏。
Laravel Excel 不接受此日期 1992-03-01,因此您需要更新/更改列字段类型。
解决步骤:-

  1. 打开要导入的 excel 文件。 选择日期栏右键单击,选择格式单元格
  2. 格式单元格>数字>自定义>类型>dd-mm-yyyy hh:mm:ss
    示例将是这样的 > 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"属性。 这使它成为约会。 如果我通过编辑Book1.xlsx将此属性添加到单元格 A4,则打开电子表格会给我两个日期。 54321 是 2048 年 9 月 20 日,以防万一;-)

所以数据在电子表格中是为了通知这个库这个数字不是一个数字,而是一个日期。 我没有在规范中查过这个,所以我希望比这更微妙,但重点是,导入电子表格的应用程序不必事先知道数据类型是什么。

更新:这里s=1通过styles.xml的几个重定向引用,最终为您提供内部格式或自定义格式。 我怀疑显示格式是您必须确定的意图是否是将数字作为日期。 一些内部格式很容易硬编码,但自定义格式会更难解码。

PhpSpreadsheet 确实了解这种格式。 但是出于性能原因,建议以只读模式读取 Excel 文件。 如果您可以在此处禁用它: https : 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

如果您需要动态解析日期时间字段,我编写了一个方法,负责自动检测该值是否为动态日期时间(无论您是否知道该列中是否有日期时间),或者我尝试了各种数据类型,它工作正常

       /**
     * <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)被声明为 ok 并被调用,它具有所有处理。

问题:在 collection() 方法中,一个日期时间单元格被转换为数字而不是日期时间字符串。 如何禁用此转换? 它不理解值格式。 而且我无论如何都无法修改 XLS 文件。

class MyImport implements WithCustomValueBinder, ToCollection
— 对我也不起作用,因为在 collection() 之前没有调用bindValues

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

是的,我也手动将单元格格式更改为字符串,然后解析正常。 但是我不能总是修改 xls 文件,因为它们将定期以单元格格式 Dateteme 流式传输。

问题仍然存在。

此页面是否有帮助?
0 / 5 - 0 等级