Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

[PATCH] Fixing inconsistencies with Time{WithZone}#{hash,eql?}

#6534

If foo.eql?(bar) returns true then so should bar.eql?(foo).
Moreover, foo.hash must then be the same as bar.hash.
Otherwise the Hash lookup will fail. TimeWithZone want to act like a Time, but currently:

bar = Time.utc(2000)
foo = bar.in_time_zone

foo.eql?(bar) # => true
bar.eql?(foo) # => false
foo.hash == bar.hash # => false (only in Ruby 1.9)

The attached patch fixes this. Since TimeWithZone wants to be freely replaceable with a Time:

bar.eql?(foo) # => now true
foo.hash == bar.hash # => now true (even in Ruby 1.9)

There is also a problem with DateTime vs TimeWithZone:

dt = foo.to_datetime
foo.eql?(dt)  # => true
dt.eql?(foo)  # => false
dt.hash == foo # => false.

Since DateTime and a Time are both builtin classes and that a Time is never eql? to a DateTime or vice-versa (as per the doc and the implementation), the patch also insures that a TimeWithZone can not be eql? to a DateTime:

foo.eql?(dt)  # => now false

Thanks

Reported by Marc-André Lafortune · March 5th, 2011 @ 08:51 PM

State: new
Milestone: none
Assigned to: nobody
Importance: none

Activity

  1. Marc-André Lafortune
  2. Pan Thomakos
    Pan Thomakos

    It seems to me that these objects should behave like regular ruby objects do. For instance:

    10 == 10.00 # Same value
    => true
    10.eql?(10.00) # Same value different type
    => false
    

    Using the latter code as an example, I would expect Time and TimeWithZone to behave like this:

    bar = Time.utc(2000)
    foo = bar.in_time_zone
    foo.eql?(bar)
    => false
    bar.eql?(foo)
    => false
    

    It seems like making eql? more lenient is removing that flexibility in testing for types. I do believe that foo.eql?(bar) should produce the same result as bar.eql?(foo), but I think that if foo and bar are of different types (Time) and (ActiveSupport::TimeWithZone) the eql? comparison should resolve to false.

    March 12th, 2011 @ 01:26 AM

  3. Marc-André Lafortune
    Marc-André Lafortune

    Floats and Integers are quite different: there is no bijection between them and they don't respond to the same methods (e.g. you can't do 10.0.times{}).

    Note that regular objects do not distinguish class with eql?:

    class MyString < String; end
    MyString.new("foo").eql?("foo") # => true
    

    In any case, one shouldn't care about class/type but about what objects respond to.

    Unless I'm mistaken, TimeWithZone is the same as Time, except that it allows for a timezone information and that timezone information is not considered when doing comparisons.

    TimeWithZone is meant to be so similar to a Time that TimeWithZone#to_time returns... a TimeWithZone. My impression is that one shouldn't have to care too much about what is a Time and what is a TimeWithZone; I'd like to use Hash lookups without caring either.

    Thanks.

    March 12th, 2011 @ 06:28 PM

  4. Pan Thomakos
    Pan Thomakos

    What you write makes sense. After taking a look at the class documentation this does seem to be the intent.

    I like your implementation, it's simple and elegant, and I was able to get tests to pass in both 1.9.2 and 1.8.7.

    You mentioned that your patch ensures that TimeWithZone cannot eql? a DateTime. Unless I am mistaken I didn't see a test for this. Would it be worth adding one?

    March 12th, 2011 @ 08:29 PM

  5. Marc-André Lafortune
    Marc-André Lafortune

    Thanks for testing on your side.

    Yes, it could be worthwhile to add a test as you suggest.

    March 13th, 2011 @ 07:48 PM

  6. Ryan Orr
    Ryan Orr

    I too ran the code that you made and thought it made sense. I added the test that Pan Thomakos discussed in this updated patch.

    March 21st, 2011 @ 02:49 AM