Jack comes to code

Ruby / Rails / Sinatra / APIs

Play Around Time Range in Ruby on Rails

Recently in Ruby 1.9.2, came across lots of confusing definitions about Time, DateTime, TimeWithZone… Finally the point taking me to remove hands from keyboards and to think about and clarify them was the ‘INFINITE’ warning:

Time#succ warning
1
lib/active_support/time_with_zone.rb:327: warning: Time#succ is obsolete; use time + 1

This warning keeps being throwing and your implement is stuck on somewhere.

In fact it has nothing to do with the warning itself or the succ method deprecation, but the usage of how to get a range of a time period. Some examples:

Time Range Examples
1
2
3
4
5
6
7
8
(1.days.ago .. DateTime.now) # => Tue, 21 Feb 2012 09:29:12 UTC +00:00..2012-02-22 17:29:12 +0800
1.days.ago.class # => ActiveSupport::TimeWithZone
DateTime.now.class # => Time
(DateTime.now - 1.day .. DateTime.now) # => 2012-02-21 17:31:25 +0800..2012-02-22 17:31:25 +0800

(1.days.ago .. DateTime.now).to_a # => *Infinite warnings as above
(DateTime.now - 1.day .. DateTime.now).to_a # => [Tue, 21 Feb 2012 17:33:14 +0800, Wed, 22 Feb 2012 17:33:14 +0800]
(10.seconds.ago .. DateTime.now).to_a # => Not infinite but 11 warnings as above

Here figures out the problem, to count on a range in time-like formats, for ActiveSupport::TimeWithZone, it counts seconds by seconds, while DateTime counts by day.

Furthermore, if the task is to check if a time is within a time range, you could use include? or cover? in Range class, but they are not always the same:

Task: Check within a time range
1
2
(10.seconds.ago..Time.now).include?(Time.now-1.second) # => 11 warnings as above
(10.seconds.ago..Time.now).cove?(Time.now-1.second) # => true

The reason is that include? will convert numerical (including ActiveSupport::TimeWithZone) parameters into integers, while cover? does not. Here are their source code:

Source code of Range#inlcude?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static VALUE
range_include(VALUE range, VALUE val)
{
    VALUE beg = RANGE_BEG(range);
    VALUE end = RANGE_END(range);
    int nv = FIXNUM_P(beg) || FIXNUM_P(end) ||
             rb_obj_is_kind_of(beg, rb_cNumeric) ||
             rb_obj_is_kind_of(end, rb_cNumeric);

    if (nv ||
        !NIL_P(rb_check_to_integer(beg, "to_int")) ||
        !NIL_P(rb_check_to_integer(end, "to_int"))) {    /* Where to integer conversion happens; */
        if (r_le(beg, val)) {
            if (EXCL(range)) {
                if (r_lt(val, end))
                    return Qtrue;
            }
            else {
                if (r_le(val, end))
                    return Qtrue;
            }
        }
        return Qfalse;
    }
    ...
}
Source code of Range#cover?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static VALUE
range_cover(VALUE range, VALUE val)
{
    VALUE beg, end;

    beg = RANGE_BEG(range);
    end = RANGE_END(range);
    if (r_le(beg, val)) {
        if (EXCL(range)) {
            if (r_lt(val, end))
                return Qtrue;
        }
        else {
            if (r_le(val, end))
                return Qtrue;
        }
    }
    return Qfalse;
}