Search code examples
rubydatetime

Ruby - Get time at start of next minute


I'm looking for a concise way to get a Ruby Time object representing the top of the next minute (and hour/day/month/year, if possible). I want this to work in a pure Ruby environment, so the Rails function Time.change or similar doesn't fit the bill.

At first this seems simple - just add 1 to Time.now, but there are edge cases where if, for example, you try to instantiate a Time object with Time.now.min + 1 when the current minute is 59, you get an ArgumentError: min out of range. This goes for hour, day, and month as well.

I have some lengthy code that does the job. It's ugly, but I'm just experimenting:

def add_minute

    now = Time.local
    year = now.year
    month = now.month
    day = now.day
    hour = now.hour
    min = now.min

    if now.min == 59
        if now.hour == 23
            if now.day == Date.civil(now.year, now.month, -1).day
                if month == 12
                    year = year + 1
                    month = 1
                    day = 1
                    hour = 0
                    min = 0
                else
                    month = now.month + 1
                    day = 1
                    hour = 0
                    min = 0
                end
            else
                day = now.day + 1
                hour = 0
                min = 0
            end
        else
            hour = now.hour + 1
            min = 0
        end
    else
        min = now.min + 1
    end

    Time.local year, month, day, hour, min, 0
end

This seems absurdly verbose for what seems like it should be a simple or built-in task, but I haven't found a native Ruby solution. Does one exist?


Solution

  • You could convert the Time object to UNIX epoch time (seconds since 1970) using #to_i, add 60 s, and then convert back to a Time object.

    time_unix = Time.now.to_i
    time_unix_one_min_later = time_unix + 60
    time_one_min_later = t = Time.at(time_unix_one_min_later)
    time_one_min_later_rounded_down = Time.new(t.year, t.month, t.day, t.hour, t.min)
    

    EDIT: Even shorter - you can just add integer seconds to Time.now directly:

    time_one_min_later = t = Time.now + 60
    time_one_min_later_rounded_down = Time.new(t.year, t.month, t.day, t.hour, t.min)
    

    EDIT 2: One-liner - just subtract Time.now.sec:

    time_one_min_later_rounded_down = Time.now + 60 - Time.now.sec