rspec - how to compare to an actual DateTime in the expect? - ruby

My rspec:
it "can show the current month name" do
expect(Calendar.create_date_using_month(1)).to eq '2000-01-01 00:00:00 -0500'
end
fails with:
expected: "2000-01-01 00:00:00 -0500"
got: 2000-01-01 00:00:00 -0500
For my code:
def self.create_date_using_month(n)
Time.new(2000,n,1)
end
Should/can I change the RSpec so that I am comparing to an actual string not a date?
I tried: Date.strptime("{ 2000, 1, 1 }", "{ %Y, %m, %d }")
but that gives me
expected: #<Date: 2000-01-01 ((2451545j,0s,0n),+0s,2299161j)>
got: 2000-01-01 00:00:00 -0500

I'm a bit confused about what exactly you're testing here. If create_data_using_month creates a Time object, you should compare it with a Time object.
This message:
expected: "2000-01-01 00:00:00 -0500"
got: 2000-01-01 00:00:00 -0500
is telling you it expected the literal string with the date, but got an object whose to_s happens to be the same.
So I guess you could "fix" it, by changing this:
it "can show the current month name" do
expect(Calendar.create_date_using_month(1).to_s).to eq '2000-01-01 00:00:00 -0500'
end
But that seems odd, is that what you want? You'll also likely have issues if you test on a machine with different time zone settings.
I'd just do this:
it "can show the current month name" do
expect(Calendar.create_date_using_month(1)).to eq Time.new(2000, 1, 1)
end
which passes for me just fine.

I think you're having a microseconds issue.
You should use to_i conversion of the date to avoid dealing with microseconds issue (if not relevant).
Time.now().to_i.should == Time.now().to_i
and I do think this work too
Time.now().should.eql?(Time.now())
I've also write a custom matcher:
RSpec::Matchers.define :be_equal_to_time do |another_date|
match do |a_date|
a_date.to_i.should == another_date.to_i
end
end
which can be use like this
Time.now().should be_equal_to_time(Time.now())

DateTime Class http://www.ruby-doc.org/stdlib-1.9.3/libdoc/date/rdoc/DateTime.html
DateTime.parse('2000-01-01 00:00:00 -0500') == DateTime.new(2000,1,1,0,0,0,'-5')
#=> true
You should always try and compare the object and not the it's string value unless you are specifically testing its ability to return a specific string. This is because to_s is simply am method and not a true representation of the object.

Related

Ruby nil converted to_date

I am writing a little updater for a app that will update the last time someone logs in to the app and then saves it. its in rails 3.2 and ruby 1.9.3p327
def update_last_seen
if current_account.present?
if (Date.current - 1.day) > current_account.last_login_at
current_account.last_login_at = Date.current
current_account.save
end
end
end
I stuck that into the application controller and call it with a before filter. The only thing is that sometimes i have dates that are nil. so comparing date to nil gives errors. you cant call to_date on a nil.
nil.to_f => 0.0
nil.to_i => 0
nil.to_s => ""
nil.to_date => NoMethodError: undefined method `to_date' for nil:NilClass
"2013/07/26".to_date => Fri, 26 Jul 2013
how can i have it set it to be accepted as a blank date as it were.
i could always do
if current_account.last_login_at.blank? || (Date.current - 1.day) > current_account.last_login_at
that way it will set it if its not there but is there a semantic way of doing it?
UPDATE:
You might think this is has no point. the reason i ask is because there are some engines that have a nil for a date. for example excel will return dates two ways 1. as text as in "06/12/2013" or 2. an integer as the number of days from 01/01/1900. that date is excels nil date i was hoping that there was a default date for nils for Ruby. if there isn't you can just comment nope there isn't sorry man. giving a downvote without explanation as to why means that you really don't care about helping/teaching anything you're just there pushing buttons. if i did something wrong with this question you can tell me ill try fix it, if it doesnt make sence?
You could add in another method to clean up the code a little bit.
def new_login_since?(last_login)
last_login.blank? || (Date.current - 1.day) > last_login
end
def update_last_seen
if current_account.present? && new_login_since?(current_account.last_login_at)
current_account.update_attributes { last_login_at: Date.current }
end
end
To answer the actual question ... you can monkey-patch the NilClass like this
class NilClass
def to_date
Date.today
end
end
nil.to_date # => #<Date: 2013-09-26 ((2456562j,0s,0n),+0s,2299161j)>
Of course, the accepted answer shows the better approach.

Get the last Sunday of the Month

I'm using Chronic to get the last Sunday of the month of any given year. It will gladly give me the n‌th Sunday, but not the last.
This works, but is not what I need:
Chronic.parse('4th sunday in march', :now => Time.local(2015,1,1))
This is what I need, but doesn't work:
Chronic.parse('last sunday in march', :now => Time.local(2015,1,1))
Is there any way around this apparent limitation?
UPDATE: I'm upvoting the two answers below because they're both good, but I've already implemented this in "pure Ruby" (in 2 lines of code, besides the require 'date' line), but I'm trying to demonstrate to management that Ruby is the right language to use to replace a Java codebase that is going away (and which had dozens of lines of code to compute this), and I told one manager that I probably could do it in one line of Ruby, and it would be readable and easy to maintain.
I am not sure about Chronic (I haven't heared about it before), but we can implement this in pure ruby :)
##
# returns a Date object being the last sunday of the given month/year
# month: integer between 1 and 12
def last_sunday(month,year)
# get the last day of the month
date = Date.new year, month, -1
#subtract number of days we are ahead of sunday
date -= date.wday
end
The last_sunday method can be used like this:
last_sunday 07, 2013
#=> #<Date: 2013-07-28 ((2456502j,0s,0n),+0s,2299161j)>
Reading the update in your question, I tried to come up with another answer using only a single line of ruby code (without using gems). How about this one?
##
# returns a Date object being the last sunday of the given month/year
# month: integer between 1 and 12
def last_sunday(month,year)
# get the last day of the month, go back until we have a sunday
Date.new(year, month, -1).downto(0).find(&:sunday?)
end
last_sunday 07, 2013
#=> #<Date: 2013-07-28 ((2456502j,0s,0n),+0s,2299161j)>
What about
Chronic.parse('last sunday', now: Chronic.parse('last day of march'))
This works, and is as readable as I can get:
Chronic.parse('last sunday', now: Date.new(year,3,31))
Thanks to Ismael Abreu for the idea to just parse 'last sunday' and control the rest via the :now option.
UPDATE: Please also upvote Ismael's answer.
It's a bit ugly, but you could simply try the 5th or 4th in that order:
d = [5,4].each do |i|
try = Chronic.parse("#{i}th sunday in march", :now => Time.local(2015,1,1))
break try unless try.nil?
end
=> Sun Mar 29 12:30:00 +0100 2015
d = [5,4].each do |i|
try = Chronic.parse("#{i}th sunday in april", :now => Time.local(2015,1,1))
break try unless try.nil?
end
=> Sun Apr 26 12:00:00 +0100 2015

How to Test an Array of Date Objects Is Correct

I'm trying to
I take an array like this:
MY_ARRAY = ["Jan 31, 2013", "Feb 28, 2013", "Mar 31, 2013"]
and then turn it into dates:
def array_as_date_objects
MY_ARRAY.map { |month| Date.parse(month)}
end
which yields this:
[#<Date: 2013-01-31 (4912645/2,0,2299161)>, #<Date: 2013-02-28 (4912703/2,0,2299161)>, #<Date: 2013-03-31 (4912763/2,0,2299161)>]
Great. But I also want to test that this array of date objects is what I expect it to be. How would I do that?
I had hoped something like this would work, but the hashtag comments out the remaining info.
MyClass.new.refined_schedule.should == :[#<Date: 2013-01-31 (4912645/2,0,2299161)>, #<Date: 2013-02-28 (4912703/2,0,2299161)>, #<Date: 2013-03-31 (4912763/2,0,2299161)>]
Edit: So just to give more info on my situation, my main concern is really how to stub this array. I don't need to trust that Ruby created the array correctly, I just need to stub the method correctly. Sorry for not being clear.
describe "#final_schedule" do
it "generates an array of pay dates that do not fall on any weekend day" do
test_schedule = MyClass.new
test_schedule.stub(:refined_schedule) { [#<Date: 2013-01-31 (4912645/2,0,2299161)>, #<Date: 2013-02-28 (4912703/2,0,2299161)>, #<Date: 2013-03-31 (4912763/2,0,2299161)> ] }
test_schedule.final_schedule.should == [ "Thursday, Jan 31, 2013", "Thursday, Feb 28, 2013","Sunday, Mar 31, 2013"]
end
end
#<Date: 2013-01-31 (4912645/2,0,2299161)>
This is a printable representation of an object. You cannot just write it and hope that ruby understands it as a date. Date object is created with constructor:
Date.new( 2013, 1, 31 )
So if you want to test if parsing result was right (which sometimes is necessary, for example for these formats: mm/dd/yy vs dd/mm/yy), you should do something like that:
MyClass.new.refined_schedule.should == [Date.new( 2013, 1, 31 ), Date.new( 2013, 2, 28 ),..]
When I write a test of my code, I look to see if I got an array back, and that all the contents of that array are of a certain type.
Whether those individual elements are correctly converted from strings to DateTime objects via parsing, doesn't concern me because that particular test occurs during the tests of the language when it's compiled and is part of the unit tests for DateTime.
Sure, we can be completely paranoid, or anal, and test every aspect of the code from our methods down to the atomic level, and be convinced everything works correctly, or we can trust that the language's developers are doing their part, and build upon that. So far I haven't had a reason to suspect they are failing on their part, so I can take the easier path.

Ruby: how to get at meridian in Time objects?

If I've got a time object:
t = Time.now
and I want to know if that time is AM or PM, right now the only way I can figure to do this is:
t.strftime("%p") == "PM"
Now, that %p is getting interpolated from something, right? Is there another way to get to it?
I ask because I'm doing some time formatting where I want to display a time range like:
"9:00 AM - 5:00 PM"
"4:00 - 5:30 PM"
"10:15 - 11:45 AM"
Right now I have to do this checking the string value of strftime, but I'd prefer to write something like:
if start_time.am? && end_time.pm? || start_time.pm? && end_time.am?
...instead of the much more verbose strftime string comparisons I'm doing now.
Based on http://www.ruby-doc.org/core-1.9.3/Time.html, I do not believe there is any other way. You could monkey-patch Time to save you some tedious strftime, however:
class Time
def meridian
self.strftime('%p')
end
def am?
self.meridian == 'AM'
end
def pm?
self.meridian == 'PM'
end
end
There isn't anything as nice as time.am? but you can use time.hour < 12 instead.
class Time
def am?
self.hour.in? (0..12)
end
def pm?
self.hour.in? (13..24)
end
end

Calculating a time before 1970 (fails with negative seconds)

I am new in ruby,and when I learn the Time class in ruby(In fact I follow the VTC video) I found something I can not make ,I want to caculate the born year of one person according to his age,
for example,when a person tell his age is "20",then I should caculate his born year.
class Person
attr_accessor :name,:age,:year_born
def initialize(name,age)
#name=name
#age=age
#year_born=(Time.now - age*31556962).year
end
def days_alive
#age*365
end
end
In the following code everything works well except the
#year_born=(Time.now - age*31556962).year
I got an error when I try
Person.new("name",43).year_born
which says:
ArgumentError: time must be positive
./person.rb:6:in `-'
./person.rb:6:in `initialize'
I know Time.now will return the seconds from 1970,that's to say
(2011-1970)<43
So the Time.now-43*31556962 return a invalid value,but I want to know how to implement my requirement?
According to Programming Ruby:
Time is an abstraction of dates and
times. Time is stored internally as
the number of seconds and microseconds
since the epoch, January 1, 1970 00:00
UTC. On some operating systems, this
offset is allowed to be negative. Also
see the Date library module on page
742. (emphasis mine)
Which implies that on some operating systems, the offset is not allowed to be negative. So any of us elderly folks who were born before 1970 may blow up your code. Also keep in mind you're actually calculating number_of_seconds_per_year * age_in_years, which won't be very accurate.
why not do it like this:
note that i am using
(Time.now.year - age)
and that i have year_born as a method.
class Person
attr_accessor :name,:age
def initialize(name,age)
#name=name
#age=age
end
def year_born
(Time.now.year - age)
end
def days_alive
#age*365
end
end
However do not store the age in your DB(if you are going to save this in your DB). Just save the birth date.
The problem is you're using Time, but should be using either Date or DateTime, which have a greater range. Date doesn't know about times, which might fit your application better since you want day granularity.
require 'date'
Date.today - 20 * 365 # => #<Date: 1991-05-07 (4896767/2,0,2299161)>
Date.today - 50 * 365 # => #<Date: 1961-05-14 (4874867/2,0,2299161)>
(Date.today - 50 * 365).year # => 1961

Resources