89

I have an event with start_time and end_time and want to check if the event is "in progress". That would be to check if today's date is in the range between the two dates.

How would you do this in a function?

Benjamin Oakes
  • 12,262
  • 12
  • 65
  • 83
Landitus
  • 1,890
  • 4
  • 23
  • 27

9 Answers9

259

In Ruby 1.9.2 === doesn't work, I get an error:

irb(main):019:0> (Time.now .. (Time.now+1)) === Time.now
TypeError: can't iterate from Time
    from (irb):19:in `each'
    from (irb):19:in `include?'
    from (irb):19:in `include?'
    from (irb):19:in `==='
    from (irb):19
    from /opt/ruby192/bin/irb:12:in `<main>'

Instead use #cover?:

irb(main):002:0> (Time.now..Time.now+4).cover?(Time.now)
=> true
irb(main):003:0> (Time.now..Time.now+4).cover?(Time.now+10)
=> false
Mick F
  • 7,312
  • 6
  • 51
  • 98
heathd
  • 2,741
  • 2
  • 14
  • 5
66

Use ===


Actually, there is an operator that will do this. Make a Range and compare Time objects to it using the === operator.

start   = Time.now.to_i

range   = start..(start + 2)
inside  = start + 1
outside = start + 3        # ok, now...

range === inside  # true
range === outside # false


Update post-comment-flood: This version works well everywhere. (In Rails, in Ruby 1, and in Ruby 2.) The earlier irb example also worked fine but the interactive example wasn't always reproduced correctly in some experiments. This one is easier to cut-and-paste.

It's all straightened out now.

DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • 1
    Does not work in Rails 3 on Ruby 1.8.7, So I prefer @heathd answer. – Sandro L Dec 04 '12 at 10:06
  • 3
    Getting TypeError: Can't iterate from time in ruby 2.0 – Pencilcheck Jun 01 '13 at 14:29
  • 3
    What is the catch here? Like the other two comments, I tried this code exactly, and get the same error. Why did 19 people up vote this answer? Did it ever work? heathd's answer does work for me. – Ryan Oct 06 '13 at 20:51
  • Also doesn't work for me. Perhaps it worked in an older version, but doesn't appear to anymore. – stuckj Nov 01 '13 at 20:05
  • Ok, fixed for Ruby 2, will comment on why later. – DigitalRoss Dec 19 '13 at 20:09
  • Doesn't work in 2.2.2, I'm guessing because I'm not using Rails. [heathd's answer](http://stackoverflow.com/a/7176956/27358) (use `cover?`) works. – David Moles Jun 03 '15 at 22:08
  • That was meant as an interactive demo. I've updated it with code that will work without typing delays. – DigitalRoss Jan 20 '16 at 20:02
  • 3
    Note: for this to work, you need to convert the dates `.to_i` and then used `===` to see if they are in the range. IMO, `.coverage?` is a better solution for dates. – erroric Jan 23 '17 at 16:58
  • can you do it with minutes? – Steven Aguilar Apr 13 '18 at 01:45
  • We should also mention that range === time is not the same as time === range. range === time is equivalent to calling the #=== method on range. e.g. range.===(time) (https://ruby-doc.org/core-2.5.1/Range.html#method-i-3D-3D-3D) – Matthew Lemieux May 31 '19 at 17:53
40

If you're using Rails you can use TimeWithZone#between?. You'd then have something like this:

> start_time = Time.zone.parse('12pm')      => Thu, 26 Jul 2012 12:00:00 EDT -04:00
> end_time = start_time + 1.hour            => Thu, 26 Jul 2012 13:00:00 EDT -04:00
> inside = Time.zone.parse('12:30pm')       => Thu, 26 Jul 2012 12:30:00 EDT -04:00
> outside = Time.zone.parse('1:30pm')       => Thu, 26 Jul 2012 13:30:00 EDT -04:00
> inside.between?(start_time, end_time)     => true
> outside.between?(start_time, end_time)    => false
Aaron
  • 13,349
  • 11
  • 66
  • 105
21

Because the date class includes the Comparable module, every date object has a between? method.

require 'date'

today           = Date.today
tomorrow        = today + 1
one_month_later = today >> 1

tomorrow.between?(today, one_month_later) # => true
steenslag
  • 79,051
  • 16
  • 138
  • 171
6

summary

  d1      = DateTime.parse('2018/04/01')
  d2      = DateTime.parse('2018/04/29')
  outside = DateTime.parse('2018/04/30')
  inside  = DateTime.parse('2018/04/15')

  # include?
  (d1...d2).include?(d1)      # true
  (d1...d2).include?(d2)      # false
  (d1...d2).include?(outside) # false
  (d1...d2).include?(inside)  # true

  (d1..d2).include?(d1)      # true
  (d1..d2).include?(d2)      # true
  (d1..d2).include?(outside) # false
  (d1..d2).include?(inside)  # true

  # ===
  (d1...d2) === d1      # true
  (d1...d2) === d2      # false
  (d1...d2) === outside # false
  (d1...d2) === inside  # true

  (d1..d2) === d1      # true
  (d1..d2) === d2      # true
  (d1..d2) === outside # false
  (d1..d2) === inside  # true

  # cover?
  (d1...d2).cover?(d1)      # true
  (d1...d2).cover?(d2)      # false
  (d1...d2).cover?(outside) # false
  (d1...d2).cover?(inside)  # true

  (d1..d2).cover?(d1)      # true
  (d1..d2).cover?(d2)      # true
  (d1..d2).cover?(outside) # false
  (d1..d2).cover?(inside)  # true

  # between?
  d1.between?(d1, d2)       # true
  d2.between?(d1, d2)       # true
  outside.between?(d1, d2)  # false
  inside.between?(d1, d2)   # true
srghma
  • 4,770
  • 2
  • 38
  • 54
5

If you are using Rails, you could try this:

ruby-1.8.7-p299 :015 > a = DateTime.now
 => Fri, 02 Dec 2011 11:04:24 -0800 
ruby-1.8.7-p299 :016 > (a.beginning_of_day..a.end_of_day).include_with_range? a
 => true 
ruby-1.8.7-p299 :017 > (a.beginning_of_day..a.end_of_day).include_with_range? a+10.days
 => false 
ruby-1.8.7-p299 :018 > (a.beginning_of_day..a.end_of_day).include_with_range? a+25.hours
 => false 
ruby-1.8.7-p299 :019 > (a.beginning_of_day..a.end_of_day).include_with_range? a+2.hours
 => true 

Note: I just used beginning_of_day and end_of_day to provide an easy range. The important part is the include_with_range? method on a Range.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
sorens
  • 4,975
  • 3
  • 29
  • 52
  • It looks like the Range#include_with_range? method provided by ActiveSupport simply adds the ability check if one range includes another range using #include?, for example: (1..5).include?(1..5). Other than that, it behaves identically to Ruby's native include? method. Since your argument is not another range, this ActiveSupport extension should make no difference. In any case, you should simply call include? and not include_with_range? since include_with_range? is simply an alias to include? (using alias_method_chain). – Tyler Rick Nov 02 '12 at 19:40
  • a = DateTime.now; (a.beginning_of_day..a.end_of_day).include_with_range?(a) returns false for me (not true) when I tried it in the console in Rails 3.2.8 and ruby-1.9.3-p194. (a.beginning_of_day..a.end_of_day).cover?(a) returns true, however, so I'll just use that instead. – Tyler Rick Nov 02 '12 at 19:46
  • a valid point. not sure why I opted to use `include_with_range` since it wasn't a case where a range was being compared to a range. Probably some kind of brain tumor. Good catch, thanks @TylerRick. @heathd answer is really the best one. – sorens Nov 04 '12 at 18:48
4

If they're timestamps:

def in_progress?
  (start_time..end_time).include?(Time.now)
end
cdmwebs
  • 933
  • 7
  • 13
4

Checked is current date in between two dates. Using Ruby

currentDate = DateTime.now
start_date = "2017-03-31"
end_date = "2018-03-31"
currentDate.between?(start_date, end_date)

**Out Put Will be** true or false
Dinesh Vaitage
  • 2,983
  • 19
  • 16
0

Thit may help some one out there.

ruby only can understand date between calculations if the date format like this :

.strftime('%Y/%m/%d')

not

.strftime('%d/%m/%Y')

If you do it the wrong way it will give you a false positive.

This works perfectly now

   if tmpadd182.between?(datebeginingmonth, dateendofmonth)
Kingsley Mitchell
  • 2,412
  • 2
  • 18
  • 25