Rails assert_equal doesn't always work with DateTimes - ruby-on-rails

I get an error in my functional test when using assert_equal:
1) [31mFailure[0m:
test_should_allow_dealer_to_extend_offer:21
<Thu, 14 Apr 2011 23:59:59 PDT -07:00> expected but was
<Thu, 14 Apr 2011 23:59:59 PDT -07:00>.
Notice that the two show the same time and time zone. I checked and they are the same class type (ActiveSupport::TimeWithZone). So why aren't they equal?
It's a standard DateTime field in the database, which I think is only stored down to the second right?
I can get it to pass by converting them to integers or using assert_in_delta with a range of 1 minute. But was just wondering.
Btw this is Rails 2.3.8 and MySQL.

I'm getting the same error too. It looks like this was reported back in 2009:
I've seen this happen in tests before - typically caused by the database having a different time resolution than the system. So even though the two times print identically, one is really (for instance) 15:45:32.012445362 and the DB loads back 15:45:32, which doesn't compare as equal.
The suggested solution, which worked for me:
In your tests, you can try coercing to_a before comparing; usec value isn't returned in the to_a representation:

Related

PG::UndefinedFunction when calling from Active Record

I am using ruby 2.2.6 and rails 4.1.16, with pg as database.
I am getting the PG::UndefinedFunction: ERROR : Operator does not exist. timestamp without time zone > time without time zone.
I know this is due to the comparing of different datatype, so I need to change the dataype from the query itself.
I tried looking around and found this guide but it is for rails 5. and another probable solution is to use cast in the sql query.
My query is:
normal = Staff.joins([department: {profils: :schedule_times}], :time_loggers).where(department: selected_departments, status: 1, schedule_times: {day_state: 1, day: wday}, time_loggers: {daterec: date}).where("time_loggers.timein > schedule_times.bstart")
I tried modifying the query by adding a cast to the sql,the query works but didn't return the intended result.
normal = Staff.joins([department: {profils: :schedule_times}], :time_loggers).where(department: selected_departments, status: 1, schedule_times: {day_state: 1, day: wday}, time_loggers: {daterec: date}).where("cast(time_loggers.timein AS TIME) > schedule_times.bstart")
Thanks in advance
EDIT
Actually the query with cast works and returned results, the problem is actually the datacontent.
If I switch the operator to be < as in
where("cast(time_loggers.timein AS TIME) > schedule_times.bstart") then it gives me some results.
time_loggers.timein is Wed, 05 Jan 2022 09:05:00 +08 +08:00
schedule_times.bstart is 2000-01-01 09:00:00 UTC
Now the problem changed already i guess. I need to change the data content directly in the sql before comparing those data, is it possible? Because I need to avoid using each to iterate then compare, as I have thousand of records so the website will be very slow.
What I want is to get those data in my timezone, GMT+8 then only get the hour and minutes and do the comparing.
Or maybe should I post this as another topic?
I managed to work the query.
normal = Staff.joins([department: {profils: :schedule_times}], :time_loggers).where(department: selected_departments, status: 1, schedule_times: {day_state: 1, day: wday}, time_loggers: {daterec: date}).where("to_char(time_loggers.timein + interval '8 hours', 'HH24:MI' > to_char(schedule_times.bstart, 'HH24:MI')")
I converted both timein and bstart using postgres to_char and added 8 hours into timein match the bstart, as it is in GMT+8 using + interval '8 hours.
Special thanks to jad for giving the appropriate direction, that pg documentation is very helpful.

Mongoid 1.month.ago is returning 01 for 1st of March

I have a ROR app: ruby 2.3.0, rails 4.2.5.1 and mongoid 5.0, and in one of my models I have :
field :statement_month, default: 1.month.ago.strftime('%m') , but only on 1st of March it saves a wrong result: "01" instead of "02" .
I have no problems for other months in first day of the month.
I also added some logs, before_create and after_create , printing:
"-------1_month_ago_month------------------------" + 1.month.ago.strftime('%m') => in logs it show "02" but in DB object is "01". It is a mongoid issue, or maybe a TimeZone issue ?
The correct syntax for dynamic defaults uses Procs. See https://docs.mongodb.com/mongoid/master/tutorials/mongoid-documents/#defaults.
MongoDB stores times as UTC timestamps, your program does not explicitly convert to UTC thus it is 1) potentially misbehaving with respect to time zones and 2) potentially misbehaving with respect to daylight savings time. Date math generally must be explicitly performed in either local time (and you should know the time zone you are operating in) or in UTC. Mixing the two eventually causes problems.
To troubleshoot the wrong month, set your system time to March 1 and debug the program. In particular, try March 1 01:00 and March 1 23:00. Those are often different dates in UTC for the same local date.

Why are those two datetimes different?

Why isn't Time.current equal to its parsed equivalent?
current = Time.current
# Wed, 16 Sep 2015 17:10:56 CEST +02:00
parsed = Time.zone.parse('16 Sep 2015 17:10:56')
# Wed, 16 Sep 2015 17:10:56 CEST +02:00
current == parsed
# false <= What ?!
current.to_i == parsed.to_i
# true
Ticket.create(datetime: current)
# ...
Ticket.find_by_datetime(parsed)
# nil <= Why ?!
I'm actually having trouble with this in a Ruby on Rails application where I try to find a record based on a datetime attribute that has been parsed, like shown on the last lines.
I really don't get it. Time zones are the same, times are the same down to seconds. What's happening here?
Moreover, how should I proceed to find a record based on a parsed datetime?
They should not be the same:
current.to_f #=> 1442417032.6567826
parsed.to_f #=> 1442417032.0
When parsing, you miss milliseconds.
Thanks everyone for the help. I hope you don't mind but since pieces of the final answer are scattered across multiple answers I will answer my own question building on what you all said.
So as for why the dates are different, it's because of the milliseconds missing in the parsed datetime. As #dimakura mentioned.
current.to_f #=> 1442417032.6567826
parsed.to_f #=> 1442417032.0
Then the answer about how could we fetch the Ticket record based a the parsed datetime.
First it's important to know this will be relevant only for PostgreSQL (my case) or other databases that actually store milliseconds. Thanks to #sjagr for mentioning this.
So we have to query for a range from parsed to parsed + 1 second, like #Stefan explained:
Ticket.where(datetime: parsed...parsed+1).first
And if we have control on the Ticket creation, we could also remove the millisecond precision before saving the the database. Thanks to #sjagr for providing an easy way to do that.
current = Time.current
Ticket.create(datetime: current.change(usec: 0))
Thanks everyone !
It's because they are not equal, they differ by parts of the second. What you see in the console is the result of inspect method called on those dates, which by default, drop any sub-second parts.

Rails 3: Is it possible to access a model's attribute in a query?

Sorry if that question sounds strange, but I'm diving into Rails and I'm still learning the jargon. Basically, I'm trying to create a single-pass query that uses the value of one of the model's attributes in a calculation in the query (assuming that's even possible).
I have a Tournament model that has a start_date attribute that is a DateTime object. I'm trying to create a query that returns all the Tournaments that have a start_date no older than 1 hour + the length of the tournament, or put another way, all tournaments that haven't yet started or have started, but haven't ended longer than an hour ago. My current query, which doesn't work, looks like this...
validTourneys = Tournament.where("start_date > (? - duration_in_mins)", (DateTime.now.utc - 1.hour))
where duration_in_mins is an integer attribute of the Tournament model, but this query doesn't work and it seems to be returning all the Tournaments all the time. I'd like to include duration_in_mins in the (DateTime.now.utc - 1.hour) part of the calculation, but I don't know how to reference it, which is why I included it in the string part of the query, hoping that would work. Am I at least on the right track?
I should mention I'm using SQLite for development and PostgreSQL for production.
Thanks for your wisdom!
The problem is that if you subtract minutes from a DateTime object, you are not subtracting minutes but days.
# This works as expected
dt = DateTime.now # Thu, 28 Apr 2011 09:55:14 +0900
an_hour_ago = dt - 1.hour # Thu, 28 Apr 2011 08:55:14 +0900
# But, this does not...
two_hours_in_minutes = 120
two_hours_ago = dt - two_hours_in_minutes # Wed, 29 Dec 2010 09:55:14 +0900
In the last example 120 days are subtracted instead of minutes. This is probably also happening in your query. You have to convert duration_in_minutes to days and then subtract.
I don't know enough about SQL to answer your question directly (I think this will probably also depend on what database you're using, so you might want to mention that).
Have you considered, though, having start_date and end_date as DateTime columns instead of start_date and duration_in_mins? If this is going to be a common query, that would certainly make it more performant, as well as making your code easier to read and understand.
This query will only work if your database is smart enough to know how to add (what I am assuming) is a DateTime and and integer. And I can't think of a database that will do that correctly the way you have it coded. No database will assume minutes. Some might do ticks, seconds, or days.
This part of the calculation
(? - duration_in_mins)
is going to happen on the database, not in Ruby-land.

Weird time inconsistencies between production and development

For some reason times are appearing differently in development (my local Mac) and production (Heroku). Take a look: (Just prior to doing this I did a heroku db:pull, so the databases should be identical)
Production (Heroku)
>> Annotation.last.id
=> 2028
>> Annotation.last.created_at
=> Sat, 12 Sep 2009 06:51:33 UTC +00:00
>> Time.zone
=> #<ActiveSupport::TimeZone:0x2b4972a4e2f0 #tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, #utc_offset=0, #name="UTC">
>> Time.now.zone
=> "PDT"
Development (my Macbook Pro)
>> Annotation.last.id
=> 2028
>> Annotation.last.created_at
=> Sat, 12 Sep 2009 09:51:33 UTC +00:00
>> Time.zone
=> #<ActiveSupport::TimeZone:0x23c92c0 #tzinfo=#<TZInfo::DataTimezone: Etc/UTC>, #utc_offset=0, #name="UTC">
>> Time.now.zone
=> "EDT"
Since the created_at times differ by 3 hours, I assume this is related to the 3 hour difference between EDT and PDT, but I'm not sure what's going on.
EDIT: Here's what the raw data looks like:
sqlite> Select created_at from annotations where id = 2028;
2009-09-12T09:51:33-04:00
It looks like this is a problem when moving databases between Heroku and your development machine. Running the SQL query directly gives me this:
Local: {"created_at"=>"2009-10-30 22:34:55.919586"}
Remote: {"created_at"=>"2009-10-31 01:34:55.919586"}
Same problem, exactly a three hour shift. Looking through the source for Taps, the Gem heroku uses for DB sync, does not provide any clues into what could be going wrong here. I've opened a heroku support ticket, and I'll update this post with what I find.
UPDATE:
Ricardo and Morten from Heroku replied. Specify the TZ on the command line like so:
TZ=America/Los_Angeles heroku db:pull
Looks like it assumes the dates in the DB are stored in your local time-zone which is different for the 2 environments and then it translates it to UTC. since the value in DB is essentially the same you get 2 different UTC values.
What is the value of config.time_zone from your "config/environment.rb"?
Also what is the value of "Select created_at from annotations where id= 2028" ?
This is a problem I've encountered before as well.
If you do something like Annotation.last.created_at on the rails console, the result already has the timezone applied to it. You should look at the "pure" date in mysql, via the mysql console :-)
Perhaps one or both of the machines has it's system time setting set to local timezone, instead of UTC. If that's the case, then it would be confused on what value the UTC time (int) actually is.
There are a number of areas to check on both machines:
system timezone
local(user) process timezone
database system timezone
database local timezone
This doesn't seem explicable by one or the other reporting its local time as the UTC time: Eastern time is 4 hours away from UTC, and Pacific time is 7 hours away, but you're seeing a three-hour difference, not a 4- or 7-hour difference.
It seems like both of them are producing incorrect output. SQLite is clearly saying that the 09:51 time is supposed to be an -04:00 time, i.e. Eastern time, but Rails is incorrectly claiming that it's UTC in one case — and in the other case, it's translating it from Eastern to Pacific, then incorrectly claiming that the result is UTC.
Maybe this is a bug in the SQLite backend for ActiveRecord? Because it seems like the kind of thing that would have been caught and squished long ago if it were in widely-used code.

Resources