warder57 warder57 - 5 months ago 11
Ruby Question

Ruby: List DateTime Format Options

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.

, 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)

  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?
      scrubbed_date = date.gsub(/^([0-9]+)[\/-]+([0-9]+)[\/-]+00$/, '\\1/\\2/2000')
        parsed = Chronic.parse(scrubbed_date)
        parsed = nil
      return nil if parsed.nil?

      cached_date = parsed.to_date

    @@date_cache[date] = cached_date
    return cached_date


  # 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)

  def self.match_instance_date?(date)
    return /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d+)\/(\d+)/i.match(date)

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.