17

我正在使用 Rails 5。我想使用下面的代码解析 .xls(不要与 .xlsx doc 混淆)

  book = Roo::Spreadsheet.open(file_location)
  sheet = book.sheet(0)
  text = sheet.to_csv
  csv = CSV.parse(text)

  arr_of_arrs = csv
  text_content = ""
  arr_of_arrs.each do |arr|
    arr.map!{|v| v && v.to_f < 1 && v.to_f > 0 ? TimeFormattingHelper.time_as_str(v.to_f * 24 * 3600 * 1000) : v}
    text_content = "#{text_content}\n#{arr.join("\t")}"
  end

这是我上面引用的方法

  def time_as_str(time_in_ms)
    regex = /^(0*:?)*0*/
    Time.at(time_in_ms.to_f/1000).utc.strftime("%H:%M:%S.%1N").sub!(regex, '')
  end

我遇到问题的一个领域是我的 .xls 文档中出现的一个单元格

24:08:00

被处理为

1904-01-02T00:08:00+00:00

使用上面的代码。如何解析我在屏幕上看到的值?也就是说,如何将日期值转换为时间值?

作为另一个 Excel 文档的示例,显示为的单元格

24:02:00

被我上面的代码解析为

1899-12-31T00:02:00+00:00
4

4 回答 4

3

您的 .xls 似乎在1904 日期系统中,并且 Roo 无法区分什么是 Duration 和什么是 DateTime,因此您需要将基准日期 1904-01-01 减去单元格值。奇怪的是,在 1900 日期系统的情况下,您需要减去基准日期 1899-12-30,这是由于 Microsoft 在 Excel 中复制的 Lotus 1-2-3 中的一个错误以实现兼容性。

下面是根据基准日期将从电子表格中读取的 DateTime 转换为持续时间的方法:

def duration_as_str(datetime, base_date)
  total_seconds = DateTime.parse(datetime).to_i - base_date.to_i
  hours = total_seconds / (60 * 60)
  minutes = (total_seconds / 60) % 60
  seconds = total_seconds % 60
  "%d:%02d:%02d" % [hours, minutes, seconds]
end

让我们测试一下:

irb(main):019:0> duration_as_str("1904-01-02T00:08:00+00:00", DateTime.new(1904, 1, 1))
=> "24:08:00"
irb(main):020:0> duration_as_str("1899-12-31T00:02:00+00:00", DateTime.new(1899, 12, 30))
=> "24:02:00"

您可以使用book.workbook.date_base.year来确定电子表格的日期系统,然后mapeach循环中添加另一个:

book = Roo::Spreadsheet.open(file_location)
sheet = book.sheet(0)
text = sheet.to_csv
csv = CSV.parse(text)

base_date = book.workbook.date_base.year == 1904 ? DateTime.new(1904, 1, 1) : DateTime.new(1899, 12, 30)
arr_of_arrs = csv
text_content = ""
arr_of_arrs.each do |arr|
  arr.map!{|v| v && v.to_f < 1 && v.to_f > 0 ? TimeFormattingHelper.time_as_str(v.to_f * 24 * 3600 * 1000) : v}
  arr.map!{|v| v =~ /^(1904|1899)-/ ? duration_as_str(v, base_date) : v}
  text_content = "#{text_content}\n#{arr.join("\t")}"
end
于 2017-08-03T07:49:34.180 回答
1

您可以使用类似下面的内容并为该字符串编写自定义解析器。

duration = 0

"24:08:01".split(":").each_with_index do |value, i|
  if i == 0
    duration += value.to_i.hours
  elsif i == 1
    duration += value.to_i.minutes
  else
    duration += value.to_i.seconds
  end
end

duration.value => 86881 (duration in seconds)

此解析器将采用 的格式hours:minutes:seconds并返回 的实例ActiveSupport::Duration。然后,duration.value会给你秒数。

于 2017-08-02T22:06:45.847 回答
1

您需要读取单元格的内部值而不是格式化值。使用时格式化的值被写入 csvto_csv

要读取内部值,您必须使用sheet对象excelx_value方法或row对象的cell_value方法。

这些方法以浮点数(天)返回值。这是一个使用cell_value遍历行的示例,假设没有标题和第一列的值要转换。

使用 Roo 2.7.1(旧版本中存在类似的方法)

book = Roo::Spreadsheet.open(file_location)
sheet = book.sheet(0)

formatted_times = []
time_column_index = 0

sheet.each_row_streaming do |row|
  time_in_days = row[time_column_index].cell_value  
  formatted_times << time_as_str(time_in_days.to_f * 24 * 3600) 
end

def time_as_str(t)
  minutes, seconds = t.divmod(60)
  hours, minutes = minutes.divmod(60)
  "%02d:%02d:%02d" % [hours, minutes, seconds]
end

# eg: time_in_days = 1.0169444444444444
# formatted_time = "24:24:24"
于 2017-08-09T04:32:28.910 回答
0
于 2017-08-09T21:43:54.683 回答