Working with and testing dates in rails - ruby-on-rails

My application uses dates a lot. A lot of ajax calls and urls involve datetimes and I find the typical format '1920-10-10 18:30:00' to be unfriendly for these purposes. I work around this by creating a helper method that basically strips the unnecessary characters out of the date (192010101830) and another method for converting the string back in to a date object.
When I build a url it goes something like this:
=link_to "Send Date", thing_my_date_path(date_to_string(DateTime.now))
Then when the thing_date action receives, it converts the parameter back in to a datetime object
def my_date
#date = string_to_date(params[:mydate])
....
end
This works fine in development. However I am completely open to other suggestions.
The problem is when I go to test my application. Tests fail because the helper methods for date_to_string and string_to_date are not present. I could include them in the tests but I feel like they should be kept separate.
So I'm looking for
a) a better way to pass dates around, and more importantly
b) a method of testing an action that is dependent on helper methods.

There are built-in methods for that methinks.
> DateTime.now.to_s(:number)
=> "20110429162748"
> DateTime.parse("20110429162748")
=> Fri, 29 Apr 2011 16:27:48 +0000
Hope that helps..

Related

Convert Time.now into ActiveSupport::TimeZone

My website has a functionality of using User system time unless they are logged in.
The problem comes when trying to test this functionality, as Time.now.zone gives me a string representation of the zone instead of an ActiveSupport::TimeZone object.
The one I get from Time.now.zone also can't be used to look up the timezone:
Time.now.zone
=> 'BST'
while I need:
=> #<ActiveSupport::TimeZone:0x00007ffe1afd9470 #name="Europe/London", #utc_offset=nil, #tzinfo=#<TZInfo::DataTimezone: Europe/London>>
Use Time.zone instead of Time.now.zone
Thanks to #Stefan, my misunderstanding of Time.now has been cleared, it reporting server machine time, not user's, which of course makes all the sense. Thankfully, when it comes to tests, I can assume the client and server are the on the same machine, so its not a big problem.
I managed to also find a solution to my original problem. The method I was looking for was getlocal, which converts the DateTime object into system time(as mentioned above, that would be the server time, which is ok in tests). That made tests pass on both CircleCI and locally.

Testing Internationalised Dates in Rspec

I have some business logic in a controller that I want to test that involves setting two values to today's date and yesterday's date.
Initially I had a passing test that essentially looked like this;
controller:
def wibble
#start_time = Date.yesterday
end
test:
it 'blah blah'
get :wibble
assigns(:start_date).should eq(Date.yesterday)
end
But a new requirement has been added that means the date should be i18n'd, which means that the controller is returning back something different.
My Thoughts
I had thought about mocking the variables, because I could only care that they are set to something, but then the business logic of Today and Yesterday isn't being exercised.
I also considered forcing i18n on the test, but this seems way to brittle.
Can anyone suggest a good way to test this?

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

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:
'04/01/2011' is April 1, 2011, not Jan 4, 2011.
'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?
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
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.
you can use rails-i18n gem or just copy the en-US.yml and set your default locale "en-US" in config/application.rb
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
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.
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.
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!

ruby soap datatime adds Z at the end

I have some strange behavior with ruby.
In a rake file I pass in a date to soap method. In the response it appends a Z at the end of the date.
in a lib file, same thing, same requirements, it doesn't.
I need the case where it doesn't. It executes the same.
What could it be?
<n1:startDate>2009-08-18T00:00:00-05:00Z</n1:startDate>
<n1:endDate>2009-08-26T00:00:00-05:00Z</n1:endDate>
The letter at the end is an indicator of the timezone (in this case UTC). The timestamp is in ISO8601 format (pretty much the standard now-a-days for all things internet), so I'm not sure why you'd want otherwise.
Without seeing the code that's generating it I'm not sure what more I can offer. Why do you want it without the 'Z'?
You've got an ISO 8601 formatted date there, the Z indicates 'Zulu' time or UTC, but not sure why you're getting differing formats though.
I am accessing an API which doesn t support this ISO standard. The date should not have a Z at the end. but when the soap response is build, it adds it. And I don t send it with that Z .. as I pass it to the web method.
Here is the code
factory = SOAP::WSDLDriverFactory.new(WSDL_STATS)
driver = factory.create_rpc_driver
driver.wiredump_dev = STDOUT
response = driver.getAllLeads({"pubID" => AFF_ID_TEST, "startDate" => start_date, "endDate" => end_date})
The end date that are passed, tried various .. isn t with that Z , from zone, at the end.
If I overwritte the zone method, maybe it will work, but I don t want to do that.
<n1:startDate>2009-08-18T00:00:00-05:00Z</n1:startDate>
<n1:endDate>2009-08-26T00:00:00-05:00Z</n1:endDate>
I pass the date without the Z, but when the soap is constructed the Z is added somehow.
This is what I want
<n1:startDate>2009-08-18T00:00:00-05:00</n1:startDate>
<n1:endDate>2009-08-26T00:00:00-05:00</n1:endDate>
Thank you ;)

How do I work with Time in Rails?

I've been pulling my hair out trying to work with Time in Rails. Basically I need to set all time output (core as well as ActiveSupport) to the server's local time -- no GMT, no UTC, etc. I've seen various posts relating to Time, but they usually involve someone's need to set it for each user. Mine isn't nearly as complex, I simply want consistency when I use any Time object. (I'd also appreciate not receiving errors every 3 seconds telling me that I can't convert a Fixnum (or some other type) to string -- it's Ruby, just do it!)
I also seem to be getting drastically different times for Time.new vs the ActiveSupport 1.second.ago. Anyway, does anyone have any quality suggestions as regards working with Time in Rails?
If you just want Time objects to be consistent, then why not stick with UTC? I just tried Time.new and 1.second.ago using script/console and I get the same output (give or take a second for typing the command). How are you doing it?
Somewhere in your initializers, define the format(s) that you want to use.
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:default => '%m/%d/%Y %H:%M')
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(:my_special_format => '%H:%M %p')
Then when you want to print a Time object, it works like the following example. Notice that the Time object in my console is already aware of my time zone. I'm not performing any magical transformations here.
>> t = Time.now
=> Wed Jul 15 18:47:33 -0500 2009
>> t.to_s
=> "07/15/2009 18:47"
>> t.to_s(:my_special_format)
=> "18:47 PM"
Calling Time#to_s uses the :default format, or you can pass in the name of the format you'd rather use like I did with :my_special_format.
You can see the various options for formatting a Time object here.
If u don't want to store each user time setting, the only solution is to use javascript time system because it work on user client time. For example i have an application that each time user try it, the app will create some example data with each data have a initial date value "today". At first time, it confuse me a lot because my host server is in australia and lot of user is on western part, so sometime the initial date value is not "today", it said "yesterday" because of different time region.
After a couple day of headache i finally take decision to JUST use javascript time system and include it in the link, so when user click the "try now" link it will also include today date value.
<% javascript_tag do -%>
var today = new Date();
$("trynow").href = "<%= new_invitation_path %>?today=" + today.toLocaleString();
<% end -%>
Add the following to config/environment.rb to handle time correctly and consistently all the time within the context of Rails. It's important to know that it will store your times to the database in UTC -- but this is what you want -- all the conversion is done automatically.
config.time_zone = 'Pacific Time (US & Canada)'
You can run rake time:zones:local from your Rails root directory to get a list of valid time zone strings in your area.
A quick addition to the DATE_FORMAT solution posted above. Your format can be a string, in which case it works as noted above by calling strftime, but you can also define the format as a lambda:
CoreExtensions::Time::Conversions::DATE_FORMATS.merge! :my_complex_format => lambda {|time|
# your code goes here
}

Resources