views:

214

answers:

3

In my application I have a variety of date sequences, such as Weekly, Monthly and Annually. Given an arbitrary date in the past, I need to calculate the next future date in the sequence.

At the moment I'm using a sub-optimal loop. Here's a simplified example (in Ruby/Rails):

def calculate_next_date(from_date)
  next_date = from_date
  while next_date < Date.today
    next_date += 1.week # (or 1.month)
  end
  next_date
end

Rather than perform a loop (which, although simple, is inefficient especially when given a date in the distant past) I'd like to do this with date arithmetic by calculating the number of weeks (or months, years) between the two dates, calculating the remainder and using these values to generate the next date.

Is this the right approach, or am I missing a particularly clever 'Ruby' way of solving this? Or should I just stick with my loop for the simplicity of it all?

+6  A: 

Because you tagged this question as ruby-on-rails, I suppose you are using Rails. ActiveSupport introduces the calculation module which provides an helpful #change method.

date = Date.today
date.advance(:weeks => 1)
date.change(:days => 7)
# => next week
Simone Carletti
I think you mean `date.advance(:weeks => 1)`, because that code doesn't work.
htanata
`change` doesn't support the `:weeks` option. I updated the code, thank you.
Simone Carletti
+3  A: 

I have used the recurrence gem in the past for this purpose. There are a few other gems that model recurring events listed here.

anshul
A: 

If you are using a Time object, you can use Time.to_a to break the time into an array (with fields representing the hour, day, month, etc), adjust the appropriate field, and pass the array back to Time.local or Time.utc to build a new Time object.

If you are using the Date class, date +/- n will give you a date n days later/earlier, and date >>/<< n will give you a date n months later/earlier.

You can use the more generic Date.step instead of your loop. For example,

from_date.step(Date.today, interval) {|d|
    # Each iteration of this block will be passed a value for 'd'
    #   that is 'interval' days after the previous 'd'.
}

where interval is a length of time in days.

If all you are doing is calculating elapsed time, then there is probably a better approach to this. If your date is stored as a Date object, doing date - Date.today will give you the number of days between that date and now. To calculate months, years, etc, you can use something like this:

# Parameter 'old_date' must be a Date object
def months_since(old_date)
    (Date.today.month + (12 * Date.today.year)) - (old_date.month + (12 * old_date.year))
end
def years_since(old_date)
    Date.today.year - old_date.year
end
def days_since(old_date)
    Date.today - old_date
end
bta