Created
May 20, 2009 11:02
Ruby helper for parsing dates of an unknown format.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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