Search code examples
rubyruby-on-rails-3.2comparatorruby-1.9date-comparison

Ruby Benign vale for nil DateTime


When comparing DateTimes, all objects being compared must be the same type. However, I have a data set that has nil dates. I want to treat these dates as older (or perhaps newer) than any other date. Is there a way to construct a benign value that will compare as older (or alternatively, newer) than any other date?

example:

data = [
  { name: :foo, timestamp: make_benign(some_valid_timestamp) },
  { name: :bar, timestamp: make_benign(nil)}
]

data.sort_by{|datum| datum[:timestamp]} #=> [<bar>, <foo>]
data.max_by {|datum| datum[:timestamp]} #=> <foo>
data.min_by {|datum| datum[:timestamp]} #=> <bar>

EDIT: I happen to be stuck on ruby 1.9 for this problem, so solutions for older versions of ruby would be nice. (But newer solutions also are nice for future reference)


Solution

  • From the docs, the requirement is not that "all objects are the same type". It says:

    The other should be a date object or a numeric value as an astronomical Julian day number.

    So for a benign value that is guaranteed to be before/after any date, you could use -Float::INFINITY and Float::INFINITY accordingly.

    DateTime.now > Float::INFINITY  #=> false
    DateTime.now > -Float::INFINITY #=> true
    

    EDIT:

    So we need a solution that works in Ruby 1.9 and Rails 3.2.9, huh...

    Well the reason the above won't work is because of this monkeypatch in ActiveSupport:

    class DateTime
      def <=>(other)
        super other.to_datetime
      end
    end
    

    This is particularly problematic. Unfortunately, you may need to just use a "very big/small number" instead...

    However, if you're able to upgrade a little bit to Rails 3.2.13 (or apply this updated monkeypatch manually), where the method signature was changed to:

    class DateTime
      def <=>(other)
        super other.kind_of?(Infinity) ? other : other.to_datetime
      end
    end
    

    ...Then you can use Date::Infinity (TIL that's a thing) instead of Float::Infinity, and this "fixed" version of the method now handles it correctly:

    DateTime.now > Date::Infinity.new  #=> false
    DateTime.now > -Date::Infinity.new #=> true