【问题标题】:Identifying date types in Spreadsheet::ParseExcel识别电子表格中的日期类型::ParseExcel
【发布时间】:2015-02-15 10:56:58
【问题描述】:

我们正在从基于 MS Excel OLE 的模块迁移到 Spreadsheet::ParseExcel(或类似模块)。由于我们有数百个程序在使用我们的模块,因此我们希望我们提供一个替换,即返回的数据是相同的。

问题在于日期 - 使用 Excel,我们得到一个 VT_DATE 类型的 Win32::OLE::Variant 对象。作为一种解决方法,我们可以通过检查$cell->type() eq 'Date' 并返回对象来手动构造它。

问题是类型设置不可靠,所以我们不能总是这样做。

Date 类型设置在两个地方。这是FmtDefault.pm中使用的逻辑:

if (   ( ( $iFmtIdx >= 0x0E ) && ( $iFmtIdx <= 0x16 ) )
    || ( ( $iFmtIdx >= 0x2D ) && ( $iFmtIdx <= 0x2F ) ) )
{
    return "Date";
}

如果此检查失败并且我们得到Numeric,那么它会在ParseExcel.pm 中进行备份检查:

if ( $FmtStr =~ m{^[dmy][-\\/dmy]*$}i ) {
    $rhKey{Type} = "Date";
}

但是,一些常见的格式字符串不起作用,例如:

[$-C09]dddd\\,\\ d\\ mmmm\\ yyyy;@ i.e. Sunday, 24 January 1982
d/m/yyyy;@ i.e. 24/1/1982

我在 openoffice.org 上检查了 Excel 规范,还阅读了诸如 http://jonvonderheyden.net/excel/a-comprehensive-guide-to-number-formats-in-excel/#date_code 之类的指南,似乎以下规则将匹配日期格式字符串:

包含 d、m 或 y 字符的字符串,它们不在 "" 或 [] 之间,除非它是 \\,否则不以 \ 开头,并且后面不带 - 或 *。

这看起来非常复杂且容易出错。有没有更好的办法?

似乎Spreadsheet::ParseExcel::Utility::ExcelFmt()$format_mode 下标记了一个日期格式,所以也许可以修改此逻辑以返回该标记?但如果可能的话,我更希望在不更改 Spreadsheet::ParseExcel 模块的情况下准备好一些东西。

【问题讨论】:

    标签: excel perl parsing spreadsheet


    【解决方案1】:

    你知道哪些列应该是日期吗?

    在我的使用中,我这样做了,并将它们转换为:

    $val = $cell->unformatted();
    # if it was properly set as a Date cell, the value will be a number of days since 1900 or 1904
    # that we can convert to a date, regardless of the format they were shown.
    if ( $val =~ /^[0-9]{5}(?:\.[0-9]+)?\z/ ) {
        $date = Spreadsheet::ParseExcel::Utility::ExcelFmt( 'YYYY-MM-DD', $val, $wb->{'Flg1904'} );
    }
    else {
        $val = $cell->value();
        $val =~ s/^'//;
        # try parsing it with Date::Manip, which handles all common formats (see its ParseDateString doc)
        use Date::Manip ();
        Date::Manip::Date_Init("TZ=GMT","DateFormat=US");
        $date = Date::Manip::UnixDate( $val, '%Y-%m-%d' );
    }
    

    更新:听起来你最好修改 ExcelFmt,像这样(未经测试):

    --- Utility.pm.orig 2014-12-17 07:16:06.609942082 -0800
    +++ Utility.pm  2014-12-17 07:18:14.453965764 -0800
    @@ -69,7 +69,7 @@
     #
     sub ExcelFmt {
    
    -    my ( $format_str, $number, $is_1904, $number_type, $want_subformats ) = @_;
    +    my ( $format_str, $number, $is_1904, $number_type, $want_subformats, $want_format_mode ) = @_;
    
         # Return text strings without further formatting.
         return $number unless $number =~ $qrNUMBER;
    @@ -956,8 +956,14 @@
         $result =~ s/^\$\-/\-\$/;
         $result =~ s/^\$ \-/\-\$ /;
    
    -    # Return color and locale strings if required.
    -    if ($want_subformats) {
    +    # Return format mode and/or color and locale strings if required.
    +    if ( $want_subformats && $want_format_mode ) {
    +        return ( $result, $color, $locale, $format_mode );
    +    }
    +    elsif ($want_format_mode) {
    +        return ( $result, $format_mode );
    +    }
    +    elsif ($want_subformats) {
             return ( $result, $color, $locale );
         }
         else {
    

    请务必将其提交给maintainer,以便包含在以后的版本中。

    【讨论】:

    • 不幸的是,这完全是任意的。现有的代码库只是读取 Excel 并将每一行作为由列名键入的值的哈希值返回。我们使用 'ref' 或变量上的类似方法检测日期,并在需要时调用 ->Date(...) 来格式化它们。对于新工作,我们绝对可以要求程序员提供一个类型,但它不适用于遗留代码库。
    • 谢谢我倾向于这样做,可能使用 $$date_format 来避免修改返回值。我会看看其他人是否有更好的主意,否则很快就会很乐意接受。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 1970-01-01
    相关资源
    最近更新 更多