Ruby strptime не работает при чтении файла

У меня есть следующий код:

require 'date'

f = File.open(filepath)

f.each_with_index do |line, i|
    a, b = line.split("\t")
    d = DateTime.strptime(a, '%m/%d/%Y %I:%M %p')
    puts "#{a} --- #{b}"
    break unless i < 100
end

И я получаю следующую ошибку:

c_reader.rb:10:in `strptime': invalid date (ArgumentError)
  from c_reader.rb:10:in `block in <main>'
  from c_reader.rb:6:in `each'
  from c_reader.rb:6:in `each_with_index'
  from c_reader.rb:6:in `<main>'

Содержание файла:

30.01.2014 1:00 1251,6  
30.01.2014 2:00 AM 1248  
30.01.2014 3:00 AM 1246.32  
30.01.2014 4:00 AM   1242.96  
30.01.2014 5:00 утра 1282.08  
30.01.2014 6:00 1293.84  
30.01.2014 7:00 1307.04  
30.01.2014 8:00 1337,76  
30.01.2014 9:00 1357.92  

Если я наберу это в IRB, он отлично работает:

DateTime.strptime("1/30/2014 2:00 PM", '%m/%d/%Y %I:%M %p')

Может кто-нибудь, пожалуйста, скажите мне, что здесь происходит?

1 ответ

Решение

Данные вашего примера не соответствуют тому, что ваш код пытается обработать, поэтому я настроил это для этого. Кроме того, нужно что-то, чтобы показать, что AM / PM был удостоен чести.

С этими настройками ваш код работает нормально. strptime возвращает действительные объекты DateTime.

require 'date'

[
  "1/30/2014 1:00 AM\t1251.6",
  "1/30/2014 2:00 AM\t1248",
  "1/30/2014 3:00 PM\t1246.32",
  "1/30/2014 4:00 PM\t1242.96",
].each do |line|
  a, b = line.split("\t")
  puts DateTime.strptime(a, '%m/%d/%Y %I:%M %p')
end
# >> 2014-01-30T01:00:00+00:00
# >> 2014-01-30T02:00:00+00:00
# >> 2014-01-30T15:00:00+00:00
# >> 2014-01-30T16:00:00+00:00

Ваш файл данных имеет спецификацию ("метка порядка следования байтов"). Первые два байта указывают "порядковый номер" порядка байтов в файле. Кроме того, каждый символ фактически занимает два байта. Это файл UTF-16LE, потому что fffeимеет недостающий бит (0xfe ==0b11111110) означает, что конец пары байтов меньше первого байта. Если бы это былоfeffэто будет "big-endian":

0000000: коэффициент 3100 2f00 3300 3000 2f00 3200 3000..1./. 3.0./.2.0.

Ruby не знает, что с ними делать, потому что ожидает по умолчанию UTF-8. Чтобы это исправить, нужно указать Ruby, как это интерпретировать. Посмотрите документацию дляIO.new чтобы увидеть, как определить кодировки. Ruby предполагает, что данные будут в формате UTF-8, поэтому входящие данные должны быть преобразованы из UTF-16LE в UTF-8. Это один из способов сделать это:

require 'date'

File.open(
  "test.csv",
  "rb:BOM|UTF-16LE:UTF-8"
) do |fi|
  fi.each_with_index do |line, i|
    a, b = line.split("\t")
    d = DateTime.strptime(a, '%m/%d/%Y %I:%M %p')
    puts "#{ 1 + i } #{a} --- #{b}"
    break unless i < 100
  end
end

Запуск, что выводит:

1 30.01.2014 1:00 AM --- 1251,6
2 1/30/2014 2:00 AM --- 1248
3 30.01.2014 3:00 AM --- 1246.32
4 30.01.2014 4: 00:00 --- 1242.96
5 30.01.2014 5:00 AM --- 1282.08
6 1/30/2014 6:00 AM --- 1293.84
7 1/30/2014 7:00 AM --- 1307.04
8 1/30/2014 8:00 AM --- 1337,76
9 1/30/2014 9:00 AM --- 1357,92
Другие вопросы по тегам