How can I use US-style dates in Rails using Ruby 1.9?
Asked Answered
C

7

12

I'm in the U.S., and we usually format dates as "month/day/year". I'm trying to make sure that my Rails app, using Ruby 1.9, assumes this format everywhere, and works the way it did under Ruby 1.8.

I know that lots of people have this issue, so I'd like to create a definitive guide here.

Specifically:

  1. '04/01/2011' is April 1, 2011, not Jan 4, 2011.
  2. '4/1/2011' is also April 1, 2011 - the leading zeros should not be necessary.

How can I do this?

Here's what I have so far.

Controlling Date#to_s behavior

I have this line in application.rb:

    # Format our dates like "12/25/2011'
    Date::DATE_FORMATS[:default] = '%m/%d/%Y'

This ensures that if I do the following:

d = Date.new(2011,4,1)
d.to_s

... I get "04/01/2011", not "2011-04-01".

Controlling String#to_date behavior

ActiveSupport's String#to_date method currently looks like this (source):

 def to_date
    return nil if self.blank?
    ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
  end

(In case you don't follow that, the second line creates a new date, passing in year, month and day, in that order. The way it gets the year, month and day values is by using Date._parse, which parses a string and somehow decides what those values are, then returns a hash. .values_at pulls the values out of that hash in the order Date.new wants them.)

Since I know that I will normally pass in strings like "04/01/2011" or "4/1/2011", I can fix this by monkeypatching it like this:

class String

  # Keep a pointer to ActiveSupport's String#to_date
  alias_method :old_to_date, :to_date

  # Redefine it as follows
  def to_date
    return nil if self.blank?
    begin
      # Start by assuming the values are in this order, separated by /
      month, day, year = self.split('/').map(&:to_i)
      ::Date.new(year, month, day)
    rescue
      # If this fails - like for "April 4, 2011" - fall back to original behavior
      begin
      old_to_date
      rescue NoMethodError => e
        # Stupid, unhelpful error from the bowels of Ruby date-parsing code
        if e.message == "undefined method `<' for nil:NilClass"
          raise InvalidDateError.new("#{self} is not a valid date")
        else
          raise e
        end
      end
    end
  end
end

class InvalidDateError < StandardError; end;

This solution makes my tests pass, but is it crazy? Am I just missing a configuration option somewhere, or is there some other, easier solution?

Are there any other date-parsing cases I'm not covering?

Centralism answered 2/9, 2011 at 16:26 Comment(3)
Or you could move somewhere where the date formats are coherent and the metric and celsius systems used ;-)Buckler
@Benoit Garret - Hey, I'm with you on metric and celsius, but neither date format is really logical, in my view. It should be most-to-least specific, like "31-01-2011", because you get the information you're least likely to know first.Centralism
you mean "2011-01-31" is a valid US date format?Buckler
C
9

Gem: ruby-american_date

This gem was created since I asked this question. I'm now using it and have been pleased.

https://github.com/jeremyevans/ruby-american_date

Centralism answered 3/1, 2012 at 13:45 Comment(0)
W
2

Date.strptime is probably what you're looking for in ruby 1.9.

You're probably stuck monkeypatching it onto string.to_date for now, but strptime is the best solution for parsing dates from strings in ruby 1.9.

Also, the formats are symmetric with strftime as far as I know.

Witmer answered 12/9, 2011 at 20:57 Comment(0)
H
2

you can use rails-i18n gem or just copy the en-US.yml and set your default locale "en-US" in config/application.rb

Hermes answered 14/9, 2011 at 3:40 Comment(0)
S
2

For parsing US-style dates, you could use:

Date.strptime(date_string, '%m/%d/%Y')

In console:

> Date.strptime('04/01/2011', '%m/%d/%Y')
 => Fri, 01 Apr 2011 
> Date.strptime('4/1/2011', '%m/%d/%Y')
 => Fri, 01 Apr 2011 
Sachsen answered 1/5, 2014 at 13:7 Comment(0)
M
0

Use REE? :D

Seriously though. If this is a small app you have complete control over or you are standardizing on that date format, monkey patching for a project is totally reasonable. You just need to make sure all your inputs come in with the correct format, be it via API or website.

Molar answered 2/9, 2011 at 17:15 Comment(2)
Thanks for the affirmation. :) We were using REE previously, but would rather use MRI 1.9.2. Personally I'm hoping to get on Rubinius when it's ready, because it's like "more Ruby than Ruby itself," and has awesome garbage collection and debugging and a stable memory footprint.Centralism
1.9.2 has a lot going for it. It also has a lot of breaking inconsistencies with 1.8. I am also trying to get it in everything new, and it has a lot of surprises like the one you're asking about. :(Molar
Z
0

Instead of using to_s for Date instances, get in the habit of using strftime. It takes a format string that gives you complete control over the date format.

Edit: strptime gives you full control over the parsing by specifying a format string as well. You can use the same format string in both methods.

Zach answered 4/9, 2011 at 14:17 Comment(1)
Good suggestion. For displaying dates, we actually normally use an application-wide view helper, so we can be consistent everywhere and only specify our format once. The real problem for me was String#to_date, since that's used by Ripple (Riak ORM) to take our user-entered dates and turn them into Date objects as properties on the model. But I wanted this page to be comprehensive.Centralism
G
0

Another option is Chronic - http://chronic.rubyforge.org/

You just need to set the endian preference to force only MM/DD/YYYY date format:

Chronic::DEFAULT_OPTIONS[ :endian_precedence ] = [ :middle ]

However the default for Chronic is the out-of-order US date format anyway!

Giorgi answered 19/9, 2011 at 2:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.