【问题标题】:reading dates from xls to csv through Perl通过 Perl 从 xls 读取日期到 csv
【发布时间】:2015-01-12 03:03:36
【问题描述】:

我有一批带有类似行的 excel 文件

1/13/04 21

我正在尝试将它们转换为.csv,但发现该行已转换为

36537,21

原来这是 excel 存储规则的副作用。 Excel 应将日期存储为自 1900 年 1 月 1 日以来的天数。根据该规则,这是错误的整数,对应于 2001 年 1 月 12 日而不是 2004 年 1 月 13 日(即1/13/04 表示的日期)。

  • Excel 到底怎么会犯这个错误?
  • 如何获得未格式化的原始值,从而避开此处的有效转换?

这是代码的粗略草图:

my $xlsparser = Spreadsheet::ParseExcel->new();
my $xlsbook = $xlsparser->Parse('xls_test.xls');
my $xls = $xlsbook->{Worksheet}[0];
my $csv = '';

# then a loop over rows and columns with...
  my $cell = $xls->get_cell( $row, $col );
  $cellcon = $cell->unformatted();
  $csv .= $cellcon; 

如果我的阐述不够清楚或者您无法重现该问题,这里有一个最小的数据集和脚本可以为我重现它:

https://dl.dropboxusercontent.com/u/58760/softwareGrr/xls_example.pl https://dl.dropboxusercontent.com/u/58760/softwareGrr/junk.xls

【问题讨论】:

  • 我无法复制。 01/13/04 在 Excel 中为我转换为 37999。你用的是什么版本?
  • 对于我正在做的事情,我应该不需要使用 Excel;一切都在 Perl 中(版本 5.16.3,Win x64,带有 Spreadsheet::ParseExcel 包的版本 0.65)。我正在查看的文件根据其元数据采用 Excel 97-2003 格式。我会看看我是否可以从(机密)文件中摘录日期并看到相同的结果(在这种情况下,我会将其放在 Dropbox 上)。
  • @Degustaf 我添加了一个链接,指向在我的计算机上重现该问题的文件。
  • perl -MTime::Piece -MTime::Seconds -Mstrict -wE 'my $t = Time::Piece->strptime("1900 1 1", "%Y %m %d"); $t = $t->add(ONE_DAY) for 1 .. 36537; say $t' 返回Fri Jan 14 00:00:00 2000
  • @choroba 好的,所以现在是 2000 年 1 月 14 日而不是 2001 年 1 月 12 日。怪罪于我用来检查该值的网站。您的评论是否旨在启发他人?我仍然没有看到 2000 年 1 月 14 日与“04 年 1 月 13 日”的原始值之间的对应关系。作为更新:我刚刚在家用计算机上尝试了上述文件并发现了相同的结果。

标签: string excel perl date csv


【解决方案1】:

如果你想将 Excel 日期序列值格式 36537,21 转换为 perl 中的时间/日期变量,那么你可以使用自己的函数来转换日期。 函数下方

sub date2excelvalue {
  my($day1, $month, $year, $hour, $min, $sec) = @_;
  my @cumul_d_in_m = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365);
  my $doy = $cumul_d_in_m[$month - 1] + $day1;

  #
  full years + your day
  for my $y(1900..$year) {
    if ($y == $year) {
      if ($month <= 2) {

        #
        dont add manually extra date
        if inJanuary or February
        last;
      }
      if ((($y % 4 == 0) && ($y % 100 != 0)) || ($y % 400 == 0) || ($y == 1900)) {
        $doy++;#
        leap year
      }
    } else {#
      full years
      $doy += 365;
      if ((($y % 4 == 0) && ($y % 100 != 0)) || ($y % 400 == 0) || ($y == 1900)) {
        $doy++;#
        leap year
      }

    }
  }#
  end
  for y# calculate second parts as a fraction of 86400 seconds
  my $excel_decimaltimepart = 0;
  my $total_seconds_from_time = ($hour * 60 * 60 + $min * 60 + $sec);
  if ($total_seconds_from_time == 86400) {
    $doy++;#
    just add a day
  } else {#
    add decimal in excel
    $excel_decimaltimepart = $total_seconds_from_time / (86400);
    $excel_decimaltimepart = ~s / 0\. //;
  }
  return "$doy\.$excel_decimaltimepart";

}

sub excelvalue2date {
  my($excelvalueintegerpart, $excelvaluedecimalpart) = @_;
  my @cumul_d_in_m = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365);
  my @cumul_d_in_m_leap = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366);
  my @cumul_d_in_m_selected;
  my($day1, $month, $year, $hour, $min, $sec);
  $day1 = 0;#
  all days all years
  my $days_in_year;
  my $acumdays_per_month;
  my $daysinmonth;
  my $day;

  #
  full years + your day
  for my $y(1900. .3000) {
    my $leap_year = 0;#
    leap year
    my $leap_year_mask = 0;#
    leap year
    if ((($y % 4 == 0) && ($y % 100 != 0)) || ($y % 400 == 0) || ($y == 1900)) {
      $leap_year = 1;#
      leap year
      @cumul_d_in_m_selected = @cumul_d_in_m_leap;

    } else {
      $leap_year = 0;#
      leap year
      @cumul_d_in_m_selected = @cumul_d_in_m;
    }

    if (($day1 + (365 + $leap_year)) > $excelvalueintegerpart) {

      #
      found this year $y
      $year = $y;
      print "year $y\n";

      $days_in_year = $excelvalueintegerpart - $day1;
      $acumdays_per_month = 0;
      print "excelvalueintegerpart  $excelvalueintegerpart\n";
      print "day1  $day1\n";
      print "daysinyear $days_in_year\n";
      for my $i(0..$# cumul_d_in_m) {
        if ($i == $# cumul_d_in_m) {
          $month = $i + 1;#
          month 12 December
          $day = $days_in_year - $cumul_d_in_m_selected[$i];
          last;

        } else {

          if (($days_in_year > ($cumul_d_in_m_selected[$i])) && ($days_in_year <= ($cumul_d_in_m_selected[$i + 1]))) {
            $month = $i + 1;
            $day = $days_in_year - $cumul_d_in_m_selected[$i];
            last;
          }

        }

      }#
      end
      for $i months

      # end year
      last;

    } else {#
      full years
      $day1 += (365 + $leap_year);
    }

  }#
  end
  for years interger part comparator

  my $total_seconds_inaday;
  $total_seconds_inaday = "0\.$excelvaluedecimalpart" * 86400;

  $sec = $total_seconds_inaday;
  $hour = int($sec / (60 * 60));
  $sec -= $hour * (60 * 60);
  $min = int($sec / 60);
  $sec -= $min * (60);
  $sec = int($sec);
  return ($day, $month, $year, $hour, $min, $sec);

}
my $excelvariable = date2excelvalue(1, 3, 2018, 14, 14, 30);
print "Excel variable: $excelvariable\n";
my($integerpart, $decimalwithoutzero) = ($1, $2) if ($excelvariable = ~m / (\d + )\.(\d + ) / );
my($day1, $month, $year, $hour, $min, $sec) = excelvalue2date($integerpart, $decimalwithoutzero);
print "Excel Date from value: $day1, $month, $year, $hour, $min, $sec\n";

尽情享受吧!

【讨论】:

    【解决方案2】:

    有问题的行是

    $cellcon = $cell->unformatted();
    

    除非有人能提供更好的解释,否则我会认为这是一个错误。我替换的行是

    $cellcon = $cell->Value;
    

    【讨论】:

    • 我仍然不知道我的问题的第一部分(为什么 Excel 为自 1900 年 1 月 1 日以来的天数提供了错误的整数),但此时并不在意。如果有人可以回答,我当然会接受他们的回答而不是我的回答。
    猜你喜欢
    • 1970-01-01
    • 2016-09-05
    • 1970-01-01
    • 2015-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-03
    相关资源
    最近更新 更多