Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: Create range of dates

I'm looking for an elegant way to make a range of datetimes, e.g.:

def DateRange(start_time, end_time, period)   ... end  >> results = DateRange(DateTime.new(2013,10,10,12), DateTime.new(2013,10,10,14), :hourly) >> puts results 2013-10-10:12:00:00 2013-10-10:13:00:00 2013-10-10:14:00:00 

The step should be configurable, e.g. hourly, daily, monthly.

I'd like times to be inclusive, i.e. include end_time.

Additional requirements are:

  • Original timezone should be preserved, i.e. if it's different from the local timezone, it should still be maintained.
  • Should use proper advance methods, e.g. Rails :advance, to handle things like variable number of days in months.
  • Ideally performance will be good, but that's not a primary requirement.

Is there an elegant solution?

like image 723
Stefan Avatar asked Sep 30 '13 11:09

Stefan


People also ask

How do you create a range in Ruby?

Ranges as Sequences Sequences have a start point, an end point, and a way to produce successive values in the sequence. Ruby creates these sequences using the ''..'' and ''...'' range operators. The two-dot form creates an inclusive range, while the three-dot form creates a range that excludes the specified high value.

Is a range an array in Ruby?

Yes, the method does look like an array class. However, you need to add a pair of parenthesis to let Ruby know you are using the Array method and not the class. The resulting value is the range of values in an array format.


1 Answers

Using @CaptainPete's base, I modified it to use the ActiveSupport::DateTime#advance call. The difference comes into effect when the time intervals are non-uniform, such as `:month" and ":year"

require 'active_support/all' class RailsDateRange < Range   # step is similar to DateTime#advance argument   def every(step, &block)     c_time = self.begin.to_datetime     finish_time = self.end.to_datetime     foo_compare = self.exclude_end? ? :< : :<=      arr = []     while c_time.send( foo_compare, finish_time) do        arr << c_time       c_time = c_time.advance(step)     end      return arr   end end  # Convenience method def RailsDateRange(range)   RailsDateRange.new(range.begin, range.end, range.exclude_end?) end 

My method also returns an Array. For comparison's sake, I altered @CaptainPete's answer to also return an array:

By hour

RailsDateRange((4.years.ago)..Time.now).every(years: 1) => [Tue, 13 Oct 2009 11:30:07 -0400,  Wed, 13 Oct 2010 11:30:07 -0400,  Thu, 13 Oct 2011 11:30:07 -0400,  Sat, 13 Oct 2012 11:30:07 -0400,  Sun, 13 Oct 2013 11:30:07 -0400]   DateRange((4.years.ago)..Time.now).every(1.year) => [2009-10-13 11:30:07 -0400,  2010-10-13 17:30:07 -0400,  2011-10-13 23:30:07 -0400,  2012-10-13 05:30:07 -0400,  2013-10-13 11:30:07 -0400] 

By month

RailsDateRange((5.months.ago)..Time.now).every(months: 1) => [Mon, 13 May 2013 11:31:55 -0400,  Thu, 13 Jun 2013 11:31:55 -0400,  Sat, 13 Jul 2013 11:31:55 -0400,  Tue, 13 Aug 2013 11:31:55 -0400,  Fri, 13 Sep 2013 11:31:55 -0400,  Sun, 13 Oct 2013 11:31:55 -0400]  DateRange((5.months.ago)..Time.now).every(1.month) => [2013-05-13 11:31:55 -0400,  2013-06-12 11:31:55 -0400,  2013-07-12 11:31:55 -0400,  2013-08-11 11:31:55 -0400,  2013-09-10 11:31:55 -0400,  2013-10-10 11:31:55 -0400] 

By year

RailsDateRange((4.years.ago)..Time.now).every(years: 1)  => [Tue, 13 Oct 2009 11:30:07 -0400,  Wed, 13 Oct 2010 11:30:07 -0400,  Thu, 13 Oct 2011 11:30:07 -0400,  Sat, 13 Oct 2012 11:30:07 -0400,  Sun, 13 Oct 2013 11:30:07 -0400]  DateRange((4.years.ago)..Time.now).every(1.year)  => [2009-10-13 11:30:07 -0400,  2010-10-13 17:30:07 -0400,  2011-10-13 23:30:07 -0400,  2012-10-13 05:30:07 -0400,  2013-10-13 11:30:07 -0400] 
like image 172
dancow Avatar answered Sep 20 '22 20:09

dancow