Rails Date Parser 1

Posted by Tom Willett Fri, 27 Jun 2008 20:11:00 GMT

I became frustrated with date parsing in Rails and Ruby and so created a user friendly parser for dates. I wanted to avoid a big javascript library or using a date-picker widget. Quite frankly, when I am entering dates, I don't like to have to reach for the mouse and scroll through the dates.

So this is a simple to install simple to use extension to the ruby Date class. To use it simply copy the following code to a file named dateparse.rb and place it in the lib directory in your rails app. Then open up the application controller and and put require 'dateparse' at the top of the file. (Note: put it before the class declaration).

Then in your controller or view code use Date.parse_date() in place of Date.parse(). It recognizes the following formats
    YYYY-mm-dd  ISO Standard
    mm-dd-YYYY  American
    mm-dd      Assume current year
    mm/dd/YYYY American standard
    mm/dd 
    mm.dd.yyyy  European
    dd.mm.yyyy  
    dd mmmmm yyyy example: 22 may 1945 or 22nd may 1945
    dd mmmmm   example: 22 may  (current year)
    mmmm dd yyyy  example: may 22 1945 or may 22nd 1945
    mmmm dd, yyyy  example: may 22, 1945
    mmmm dd     example may 22 or may 22nd
    dd          example  4 or 4th
    today (tod) or now   common names for days and abbreviations
    tomorrow (tom)
    yesterday  (yes)
    Next Week, Last Week, Next Month, Last Month, Next Year, Last Year
    +n[units] or -n[units]: Date from today: examples: '+22', '+22 days',
       '+22d', '-4 weeks', '-4w', '-4week', '+6 months', '+6m', '+6month',  
       '-2 years', '-2y'
# This library adds the parse_date method to the Standard Ruby Date Class.
# Its purpose is to make the entry of user friendly date formates painless
# in Rails applications.
#
# My motivation for making this was the desire for user friendly date input
# in rails applications without resorting to javascript or some kind of widget.
#
# To use: drop this file into ./lib, require it into the application 
# controller.
#
# Written by Tom Willett 
# Version 1.0
#
# with a tip of the hat to
# Stuart Rackham 
# and Datetime ToolBocks
#
# Date Formats recognized by this method:
#    YYYY-mm-dd  ISO Standard
#    mm-dd-YYYY  American
#    mm-dd      Assume current year
#    mm/dd/YYYY American standard
#    mm/dd 
#    mm.dd.yyyy  European
#    dd.mm.yyyy  
#    dd mmmmm yyyy example: 22 may 1945 or 22nd may 1945
#    dd mmmmm   example: 22 may  (current year)
#    mmmm dd yyyy  example: may 22 1945 or may 22nd 1945
#    mmmm dd, yyyy  example: may 22, 1945
#    mmmm dd     example may 22 or may 22nd
#    dd          example  4 or 4th
#    today (tod) or now   common names for days and abbreviations
#    tomorrow (tom)
#    yesterday  (yes)
#    Next Week, Last Week, Next Month, Last Month, Next Year, Last Year
#    +n[units] or -n[units]: Date from today: examples: '+22', '+22 days',
#       '+22d', '-4 weeks', '-4w', '-4week', '+6 months', '+6m', '+6month',  
#       '-2 years', '-2y'
#
class Date

  # Parse date string with one of the following formats:
  #
  # The string argument is first converted to a string with #to_s.
  # Returns nil if passed nil or an empty string.
  # Raises ArgumentError if string can't be parsed.
  #
  def self.parse_date(org_string)
    string = org_string.to_s.strip.downcase
    return nil if org_string.empty?
    today = Date.today
    if string =~ /^(\d{4})-(\d{1,2})-(\d{1,2})$/
      # YYYY-mm-dd format.  ISO Standard
      year = $1.to_i
      month = $2.to_i
      day = $3.to_i
    elsif string =~ /^(\d{1,2})-(\d{1,2})-(\d{4})$/
      # mm-dd-yyyy format
      year = $3.to_i
      month = $1.to_i
      day = $2.to_i
    elsif string =~ /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
      # mm.dd.yyyy or dd.mm.yyyy format
      year = $3.to_i
      if $1.to_i > 12
        day = $1.to_i
        month = $2.to_i
      else
        day = $2.to_i
        month = $1.to_i
      end
    elsif string =~ /^(\d{1,2})-(\d{1,2})$/
      # mm-dd format
      year = today.year.to_i
      month = $1.to_i
      day = $2.to_i
    elsif string =~ /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/
      # mm/dd/yyyy format
      year = $3.to_i
      month = $1.to_i
      day = $2.to_i
    elsif string =~ /^(\d{1,2})\/(\d{1,2})$/
      # mm/dd format
      year = today.year
      month = $1.to_i
      day = $2.to_i
    elsif string =~ /^(\d{1,2})(?:st|nd|rd|th)?,? (\w+) (\d{2,4})$/
      # 'd mmmm yyyy' format
      date_array = ParseDate.parsedate("#{$1} #{$2} #{$3}", true)
      year = date_array[0].to_i
      month = date_array[1].to_i
      day = date_array[2].to_i
    elsif string =~ /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{2,4})$/
      # 'mmmm d yyyy' format
      date_array = ParseDate.parsedate("#{$2} #{$1} #{$3}", true)
      year = date_array[0].to_i
      month = date_array[1].to_i
      day = date_array[2].to_i
    elsif string =~ /^(\d{1,2})(?:st|nd|rd|th)?,? (\w+)$/
      # 'd mmmm' format
      y = today.year.to_s
      date_array = ParseDate.parsedate("#{$1} #{$2} #{y}", true)
      year = date_array[0].to_i
      month = date_array[1].to_i
      day = date_array[2].to_i
    elsif string =~ /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,?$/
      # 'mmmm d' format
      y = today.year.to_s
      date_array = ParseDate.parsedate("#{$2} #{$1} #{y}", true)
      year = date_array[0].to_i
      month = date_array[1].to_i
      day = date_array[2].to_i
    elsif string =~ /^(\d{1,2})(st|nd|rd|th)?$/
      # 'd' format
      year = today.year
      month = today.month
      day = $1.to_i
    elsif string =~ /^tod[\w]+$|^now$/
      # today or now
      year = today.year
      month = today.month
      day = today.day
    elsif string =~ /^tom/
      # tomorrow
      today += 1
      year = today.year
      month = today.month
      day = today.day
    elsif string =~ /^yes/
      # yesterday
      today -= 1
      year = today.year
      month = today.month
      day = today.day
    elsif string =~ /^(next|last)\s(week|month|year)$/
      # Next Week, Last Week, Next Month, Last Month, Next Year, Last Year
      year = today.year
      month = today.month
      day = today.day
      case $2
        when 'week'
          if $1 == 'next'
            today += 7
          else
            today -= 7
          end
          year = today.year
          month = today.month
          day = today.day
        when 'month'
          month = (($1 == 'next') ? (month + 1) : (month - 1))
          if month > 12
            month = 1
            year += 1
          elsif month < 1
            month = 12
            year -= 1
          end
        when 'year'
          year = (($1 == 'next') ? (year + 1) : (year - 1))
      end
    elsif string =~ /^([+-]\d+)(?:\s*(d|days?|w|weeks?|m|months?|y|years?))?$/
      # Date intervals.
      year = today.year
      month = today.month
      day = today.day
      n = $1.to_i
      units = $2 || 'days'
      case units.first
      when 'd'
        today = today + n
        year = today.year
        month = today.month
        day = today.day
      when 'w'
        today = today + n*7
        year = today.year
        month = today.month
        day = today.day
      when 'm'
        sign = n <=> 0
        month = today.month + sign * (n.abs % 12)
        year = today.year + sign * (n.abs / 12)
        if month <1
          month += 12
          year -= 1
        elsif month > 12
          month -= 12
          year += 1
        end
      when 'y'
        year = year + n
      end
    else
      raise ArgumentError.new("\"#{org_string}\" is not recognized as a Date")
    end
    
    begin
      result = Date.new(year,month,day)
    rescue
      raise ArgumentError.new("\"#{org_string}\" is not recognized as a Date")
    end
    result
  end
end
Hope this helps someone.
Comments

Leave a comment

  1. Marcelo de Moraes Serpa about 1 month later:

    Awesome! Thanks for taking your time to write and share it!

Comments