I am importing data from a CSV file and I want to allow my user to specify the format for a DateTime column. Rather than allowing them to type in a format string and deal with the pain of validating the input, I was planning on giving them a select box that lists all of the format options (e.g. %m/%d/%Y
, etc...). Someone mentioned that a gem already exists which will list all of the reasonable DateTime formats, but I have been unable to find it. How can I get get a list of the available DateTime formats for display?
You might be interested in another alternative. I had to do something similar not too long ago, and support almost arbitrary date formats. In the end, we detected 29 distinct formats (all domestic US and Canada) from all of the data sources (CSV files) that we supported.
I came up with this class to parse and cache the result:
class DateParser
@@date_cache = {}
def self.is_date?(date)
return self.match_digital_date?(date) || self.match_instance_date?(date)
end
def self.parse_date(date)
return nil if date.blank?
return date if date.instance_of?(Date)
date = date.to_s
cached_date = @@date_cache[date]
return cached_date if !cached_date.nil?
match = self.match_instance_date?(date)
if !match.nil?
month_str = match[1].upcase
day = match[2].to_i
year = match[3].to_i
year += (year < 20) ? 2000 : 1900 if year < 1600
month = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'].find_index {|mon| mon == month_str }
cached_date = Date.new(year, month+1, day) if !month.nil?
else
scrubbed_date = date.gsub(/^([0-9]+)[\/-]+([0-9]+)[\/-]+00$/, '\\1/\\2/2000')
begin
parsed = Chronic.parse(scrubbed_date)
rescue
parsed = nil
end
return nil if parsed.nil?
cached_date = parsed.to_date
end
@@date_cache[date] = cached_date
return cached_date
end
private
# Matches dates in these formats
# : 2015-01-13
# : 01-13-2015
# : 01-13-15
# : 13-01-15
# : 01/13/2015
# : 01/13/15
# : 13/01/15
def self.match_digital_date?(date)
return /^([0-9]+)[\/-]+([0-9]+)[\/-]+([0-9]+)$/.match(date)
end
def self.match_instance_date?(date)
return /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d+)\/(\d+)/i.match(date)
end
end
This uses the Chronic gem to do some of the date parsing, based on an initial regex match to choose the parse path, as well as some necessary fixups that we encountered along the way. Correctness and speed were the primary goals of this approach. Caching the results helped speed up the processing of dates from the CSV file by 5X or more in most cases.
If you want to skip asking the user which format they used, and just get on with consuming any dates that you choose, this should give you what you need.