Working hard, thinking hard, sharing the love

Calculate next weekday in Ruby on Rails

I had to advance a Time to the next weekday for a client Ruby on Rails project. I admit I dreaded writing that code, I somehow imagined awful hacks atop awful hacks.

I'm happy to report it didn't turn out quite so bad after all:

def next_weekday(original_date)
  one_day = 60 * 60 * 24 # in Rails just say 1.day
  weekdays = 1..5 # Monday is wday 1
  result = original_date
  result += one_day until result > original_date && weekdays.member?(result.wday)
  result
end

And the test that drove it:

def test_next_weekday_skips_weekend
  original_date = Time.at(1165606025) # a Friday
  assert_equal(5, original_date.wday, "this test should give a Friday")
  new_date = next_weekday(original_date)
  assert(
    new_date > original_date,
    "new date should be after the original date"
  )
  assert_equal(
    1, new_date.wday,
    "new date should be a Monday since we gave a Friday"
  )
end

Please feel free to embarrass me with your improvements in the comments.

7 comments:

logic said...

Here's my crack at it. :-)

def next_weekday(original_date)
one_day = 60 * 60 * 24
result = original_date + one_day
original_date.wday == 5 && result += one_day * 2
result
end

Stephen said...

Nice code with a small flaw.

When doing addition on a date in ruby with the '+' symbol, one day is equal to the integer 1. Instead of using one_day = 60 * 60 * 24, use 1. That is assuming you are using a Date object for original_date. If you are using a Time object for original_date your code is correct for generating the same time at the next weekday. You might want to add code to this method to check the class type of the object being passed in to be on the safe side.

Stephen said...

Had I learned to read as a young child I would have noticed you did specify that this was a Time object.

I would still recommend adding a check on the object being passed though.

Something along the lines of
one_day = 60 * 60 * 24 if original_date.class == Time else one_day = 1

Just in case some n00b like myself tried to throw a Date at it figuring that this method was intended to work on a date based on your choice of parameter name.

Ryan Platte said...

Stephen, Ruby's Date implementation is very heavyweight for day-to-day use (it handles lots of odd cases in past dates, different calendars, etc.), so it's much less used than Time. I wrote this for a Rails app, where Time is the standard way to represent a date.

But you're absolutely right that this code doesn't check its assumption. Good point.

Anonymous said...

Here's a slightly more concise version

def next_weekday (timeobj)
begin
timeobj += (60*60*24)
end until timeobj != 6 && timeobj != 0
timeobj
end


Because the until condition is at the end that loop will run once, adding one day, no matter what. If the next day is a weekday it quits and returns the day.

If it's not a weekday it will loop until it hits the next monday and then return the day. Hopefully this is handy for someone out there!

Anonymous said...

def next_weekday (timeobj)
begin
timeobj += (60*60*24)
end until timeobj.wday != 6 && timeobj.wday != 0
timeobj
end

a tiny revision to actually make this work...

Shane said...

This seems to be working for me...

original_date += 1.days until (1..5).member?(original_date.wday)

This is just a refactor of the previous comment, please feel free to let me know if it is incorrect!

Archive