Skip to content

Instantly share code, notes, and snippets.

@shad
Created May 20, 2009 11:02
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shad/114749 to your computer and use it in GitHub Desktop.
Save shad/114749 to your computer and use it in GitHub Desktop.
Ruby helper for parsing dates of an unknown format.
# ParseDate
#
# A helper for parsing dates of an unknown format.
#
# Takes a randomly formatted date and makes a best guess at the date, or throws and exception if
# there's no best guess. Will take international dates into account if you pass in a 'short form' date
# to help suggest a starting point for the search.
# Will also try Chronic parsing to get relative dates (yesterday, tomorrow, in 3 days, etc.)
#
# Examples:
# date = ParseDate.parse('08-09-1977')
# date = ParseDate.parse('09-08-1977', :short_form => '%d/%m/%y')
# date = ParseDate.parse('August 09, 1977')
# date = ParseDate.parse('yesterday')
#
# ParseDate is free and open... you can redistribute it and/or modify
# it anyway you see fit. No warrantee or guarantee provided :)
require 'date'
require 'rubygems'
require 'chronic'
require 'activesupport'
module ParseDate
# Parse a date of unknown format.
# Optionally, you can pass in a "short_form" to suggest potential characteristics expected.
# It will be used to determine if the date is international format, or US formatted
# (Note: Date separators don't matter with short dates, they will be stripped out)
def self.parse( date_string, options={} )
date = nil
short_form = options[:short_form]
if(day_before_month?(short_form))
# Normalize separators
shorted_date_string = date_string.gsub(/[\.-]/,"/")
shorted_short_form = short_form.gsub(/[\.-]/,"/")
if(long_year?(date_string))
date ||= Date.strptime(shorted_date_string, shorted_short_form.gsub(/\%y/,"\%Y")) rescue nil
else
date ||= Date.strptime(shorted_date_string, shorted_short_form.gsub(/\%Y/, "\%y")) rescue nil
end
end
date ||= Chronic.parse(date_string).to_date rescue nil
date ||= Date.parse(date_string) # allow this exception through if we can't parse at all
date
end
private
# determine if this is an international date format (day before month before year)
def self.day_before_month?(short_form)
return false if short_form.nil?
(/\%d/i =~ short_form) < (/\%m/i =~ short_form)
end
# Figure out if we have a long (4 digit) year
def self.long_year?(date)
# if no spaces...
if(date.strip.index(' ').nil?)
# split on -, /, or . (valid separators for dates I guess.)
parts = date.split(/[-\/\.]/)
# if the first bit, or last bit if 4 chars
(parts.first.size == 4) || (parts.last.size == 4)
end
end
end
# Tests for ParseDate
# Drop this in you with your unit tests.
#
# Lots of examples of the formats that are accepted.
require File.dirname(__FILE__) + '/../test_helper'
require 'parse_date'
class ParseDateTest < ActiveSupport::TestCase
def test_simple_dates
# Simple Date
assert_date '1977-08-19'
assert_date '19/08/1977'
assert_date '08-19-1977'
assert_date '08-09-1977', :expect=>'1977-08-09'
end
def test_simple_date_with_short_form
# Simple Date
assert_date '1977-08-19', :short_form=>'%m/%d/%y'
assert_date '19/08/1977', :short_form=>'%m/%d/%y'
assert_date '08-19-1977', :short_form=>'%m/%d/%y'
assert_date '08-09-1977', :short_form=>'%m/%d/%y', :expect=>'1977-08-09'
end
def test_simple_date_short_year
# Simple Date - short year
assert_date '19/08/77'
assert_date '08-19-77'
assert_date '08-09-77', :expect=>'1977-08-09'
end
def test_long_form
# Long Form
assert_date 'August 19, 1977'
assert_date 'August 9, 1977', :expect=>'1977-08-09'
end
def test_chronic_strings
# Chonic Relative Date
assert_date 'yesterday', :expect => (Date.today - 1.day)
assert_date 'tomorrow', :expect => (Date.today + 1.day)
end
def test_international_dates_long_year
# Date Flipped - Long Year
assert_date '09/08/1977', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '09-08-1977', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '09.08.1977', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '09/08/1977', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
assert_date '09-08-1977', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
assert_date '09.08.1977', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
end
def test_international_dates_short_year
# Date Flipped - Short Year
assert_date '09/08/77', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '09-08-77', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '09.08.77', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '09/08/77', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
assert_date '09-08-77', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
assert_date '09.08.77', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
end
def test_international_dates_short_year_month_day
# Date Flipped - Short Year - Sort Month/Day
assert_date '9/8/77', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '9-8-77', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '9.8.77', :short_form=>'%d/%m/%y', :expect=>'1977-08-09'
assert_date '9/8/77', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
assert_date '9-8-77', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
assert_date '9.8.77', :short_form=>'%d/%m/%Y', :expect=>'1977-08-09'
end
private
def assert_date(input, options={})
options[:expect] ||= '1977-08-19'
expect = options[:expect]
expect = Date.parse(expect) if expect.is_a?(String)
got = nil
got = ParseDate.parse(input, :short_form => options[:short_form])
assert_equal(expect, got)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment