dfrankow
dfrankow

Reputation: 21459

How do you use Ruby CSV converters?

Suppose you have the following file:

textfield,datetimefield,numfield
foo,2008-07-01 17:50:55.004688,1
bar,2008-07-02 17:50:55.004688,2

The Ruby code to read a .csv is something like:

#!/usr/bin/env ruby

require 'csv'

csv = CSV($stdin, :headers => true, :converters => :all)
csv.each do |row|
  print "#{row}"
  the_date = row['datetimefield'].to_date
end

That code gives this error message:

./foo2.rb:8:in `block in <main>': undefined method `to_date' for "2008-07-01 17:50:55.004688":String (NoMethodError)

What gives?

I've read the docs, but I don't get it.

Edit: Yes, I could parse the fields individually. The point of this question is that I want to learn how to use the documented converters feature.

Upvotes: 15

Views: 15350

Answers (3)

JamesSchiiller
JamesSchiiller

Reputation: 83

Works!!! Executable version of code:

#Define test data
src =
  "textfield,datetimefield,numfield\n" +
  "foo,2008-07-01 17:50:55.004688,1\n" +
  "bar,2008-07-02 17:50:55.004688,2"

require 'csv'
require 'time'

CSV::Converters[:mytime] = lambda{|s|
  begin
    Time.parse(s)
  rescue ArgumentError
    s
  end
}

csv = CSV(src, :headers => true, :converters => [:mytime])
array_of_hash_objects = csv.to_a.map {|row| row.to_hash }

array_of_hash_objects.each do |row|
  print "#{row}"
  the_date = row['datetimefield'].to_date
  puts
  print the_date
  puts
end

CSV::Converters[:time2date] = lambda{|s|
  begin
    Time.parse(s).to_date
  rescue ArgumentError
    s
  end
}

csv = CSV(src, :headers => true, :converters => CSV::Converters.keys + [:time2date])
array_of_hash_objects = csv.to_a.map {|row| row.to_hash }

array_of_hash_objects.each do |row|
  print "#{row}"
  puts
  p row['datetimefield'] #Date-field
end

=============

Outputs:

{"textfield"=>"foo", "datetimefield"=>2008-07-01 17:50:55 -0400, "numfield"=>"1"}
2008-07-01
{"textfield"=>"bar", "datetimefield"=>2008-07-02 17:50:55 -0400, "numfield"=>"2"}
2008-07-02
{"textfield"=>"foo", "datetimefield"=>2008-07-01 17:50:55 -0400, "numfield"=>1}
2008-07-01 17:50:55 -0400
{"textfield"=>"bar", "datetimefield"=>2008-07-02 17:50:55 -0400, "numfield"=>2}
2008-07-02 17:50:55 -0400

Upvotes: -3

knut
knut

Reputation: 27875

You are asking how to use converters.

My example defines the usage of a new converter mytime. The converter my_time tries to build a time object if possible.

#Define test data
src = <<csv
textfield,datetimefield,numfield
foo,2008-07-01 17:50:55.004688,1
bar,2008-07-02 17:50:55.004688,2
csv

require 'csv'
require 'time'
CSV::Converters[:mytime] = lambda{|s| 
  begin 
    Time.parse(s)
  rescue ArgumentError
    s
  end
}

csv = CSV(src, :headers => true, :converters => [:mytime])
csv.each do |row|
  print "#{row}"
  the_date = row['datetimefield'].to_date
end

Using this technique, you may also define a converter to create a date from time-like strings. This solution also keeps all other converters.

#define test data
src = <<csv
textfield,datetimefield,numfield
foo,2008-07-01 17:50:55.004688,1
bar,2008-07-02 17:50:55.004688,2
csv

require 'csv'
require 'time'
CSV::Converters[:time2date] = lambda{|s| 
  begin 
    Time.parse(s).to_date
  rescue ArgumentError
    s
  end
}

csv = CSV(src, :headers => true, :converters => CSV::Converters.keys + [:time2date])
csv.each do |row|
  print "#{row}"
  p row['datetimefield'] #Date-field
end

Upvotes: 27

Frederick Cheung
Frederick Cheung

Reputation: 84182

Your date times don't match the CSV::DateTimeMatcher regexp that CSV uses to decide whether it should attempt a date time conversion. By the looks of it it's doing so because of the fractional seconds you've got.

You could either overwrite that constant or write your own converter (DateTime.parse seems happy with your strings)

Upvotes: 7

Related Questions