How to round time to the next 15 minutes in Ruby?

Is there an easy way to round the time to the next 15 minutes?

This is what I am doing right now. Is there an easier way to do this?

t = Time.new rounded_t = Time.local(t.year, t.month, t.day, t.hour, t.min/15*15) 
+58
ruby
Jan 16 '09 at 1:59
source share
12 answers

You said “round off”, so I'm not sure if you are really looking for a round or gender, but here is the code for both. I think something like this reads well if you add the round_off and floor methods to the Time class. An added benefit is that you can more easily round any time section.

 require 'active_support/core_ext/numeric' # from gem 'activesupport' class Time # Time#round already exists with different meaning in Ruby 1.9 def round_off(seconds = 60) Time.at((self.to_f / seconds).round * seconds).utc end def floor(seconds = 60) Time.at((self.to_f / seconds).floor * seconds).utc end end t = Time.now # => Thu Jan 15 21:26:36 -0500 2009 t.round_off(15.minutes) # => Thu Jan 15 21:30:00 -0500 2009 t.floor(15.minutes) # => Thu Jan 15 21:15:00 -0500 2009 

Note. ActiveSupport was only needed for the pretty argument of 15.minutes . If you do not want this dependency, use 15 * 60 instead.

+116
Jan 16 '09 at 2:34
source share

I am not very familiar with ruby ​​syntax, but you can round to the next 15 minutes using modulo. (i.e., x - (x modulo 15)). I would suggest that the syntax would be similar to

 t.min - ( t.min % 15) 

This will make your set of possible values ​​0, 15, 30, and 45. Assuming 0 <= t.min <= 59.

+20
Jan 16 '09 at 2:14
source share

I thought I'd post another solution that rounds up and down to the nearest number of seconds. Oh, and that does not change the time zone, like some of the other solutions.

 class Time def round(sec=1) down = self - (self.to_i % sec) up = down + sec difference_down = self - down difference_up = up - self if (difference_down < difference_up) return down else return up end end end t = Time.now # => Mon Nov 15 10:18:29 +0200 2010 t.round(15.minutes) # => Mon Nov 15 10:15:00 +0200 2010 t.round(20.minutes) # => Mon Nov 15 10:20:00 +0200 2010 t.round(60.minutes) # => Mon Nov 15 10:00:00 +0200 2010 

ActiveSupport was used in the examples for the x.minutes function. Instead, you can use 15 * 60.

The floor and ceil methods can be easily implemented based on this solution.

+20
Nov 15 '10 at 8:45
source share

Since Ruby allows arithmetic (in seconds) in Times, you can simply do this:

 t = Time.new rounded_t = tt.sec-t.min%15*60 
+9
Jan 16 '09 at 2:52
source share

I found a very readable solution,

This will round your time to the last rounded 15 minutes. You can change the value of 15.minutes to every possible time interval.

Time.at(Time.now.to_i - (Time.now.to_i % 15.minutes))

+6
Jun 14 '13 at 21:05
source share

I wrote a Rounding gem to handle such cases.

Rounding up time is as simple as calling floor_to in time. Rounding and rounding to the nearest ( ceil_to and round_to ) are round_to .

 require "rounding" Time.current.floor_to(15.minutes) # => Thu, 07 May 2015 16:45:00 UTC +00:00 Time.current.ceil_to(15.minutes) # => Thu, 07 May 2015 17:00:00 UTC +00:00 Time.current.round_to(15.minutes) # => Thu, 07 May 2015 16:45:00 UTC +00:00 

The time zone of the original time is saved (UTC in this case). You do not need to load ActiveSupport - you can write floor_to(15*60) and it will work fine. The gem uses Rational to avoid rounding errors. You can round to next Monday by providing the offset round_to(1.week, Time.parse("2015-5-4 00:00:00 UTC")) . We use it in production.

I wrote a post that explains more. I hope you will enjoy.

+6
May 7, '15 at 4:54
source share

You can do:

 Time.at(t.to_i/(15*60)*(15*60)) 
+4
Jan 16 '09 at 2:12
source share
 # this is an extension of Ryan McGeary solution, specifically for Rails. # Note the use of utc, which is necessary to keep Rails time zone stuff happy. # put this in config/initializers/time_extensions require 'rubygems' require 'active_support' module TimeExtensions %w[ round floor ceil ].each do |_method| define_method _method do |*args| seconds = args.first || 60 Time.at((self.to_f / seconds).send(_method) * seconds).utc end end end Time.send :include, TimeExtensions 
+4
Apr 13 '10 at
source share

Introduction

There are quite a few solutions here, and I began to think about their effectiveness (efficiency is probably not the most important aspect of this problem). I took a little from here and threw a couple of mine. (NB, although the OP asked to round to the next 15 minutes, I made my comparisons and samples in just 1 minute / 60 seconds for simpler samples).

Customization

 Benchmark.bmbm do |x| x.report("to_f, /, floor, * and Time.at") { 1_000_000.times { Time.at((Time.now.to_f / 60).floor * 60) } } x.report("to_i, /, * and Time.at") { 1_000_000.times { Time.at((Time.now.to_i / 60) * 60) } } x.report("to_i, %, - and Time.at") { 1_000_000.times { t = Time.now.to_i; Time.at(t - (t % 60)) } } x.report("to_i, %, seconds and -") { 1_000_000.times { t = Time.now; t - (t.to_i % 60).seconds } } x.report("to_i, % and -") { 1_000_000.times { t = Time.now; t - (t.to_i % 60) } } end 

results

 Rehearsal ----------------------------------------------------------------- to_f, /, floor, * and Time.at 4.380000 0.010000 4.390000 ( 4.393235) to_i, /, * and Time.at 3.270000 0.010000 3.280000 ( 3.277615) to_i, %, - and Time.at 3.220000 0.020000 3.240000 ( 3.233176) to_i, %, seconds and - 10.860000 0.020000 10.880000 ( 10.893103) to_i, % and - 4.450000 0.010000 4.460000 ( 4.460001) ------------------------------------------------------- total: 26.250000sec user system total real to_f, /, floor, * and Time.at 4.400000 0.020000 4.420000 ( 4.419075) to_i, /, * and Time.at 3.220000 0.000000 3.220000 ( 3.226546) to_i, %, - and Time.at 3.270000 0.020000 3.290000 ( 3.275769) to_i, %, seconds and - 10.910000 0.010000 10.920000 ( 10.924287) to_i, % and - 4.500000 0.010000 4.510000 ( 4.513809) 



Results Analysis

What to do with it? It’s good that your equipment can work faster or slower, so do not take the word for computers for it. As you can see, another thing is that if we do not perform these operations on a scale of millions of operations, it will not matter which method you use with respect to processing power (although note that, for example, most solutions for cloud computing provides very little computing power, and therefore millions can be hundreds or tens of thousands in such environments).

The slowest, but probably the most readable solution

In this sense, using it is clear that the slowest of them is t = Time.now; t - (t.to_i % 60).seconds t = Time.now; t - (t.to_i % 60).seconds can only be justified because .seconds is so cool there.

Not so slow and almost like a readable solution

However, since it is actually not needed at all and makes the operation twice as expensive as without it, I must say that my choice is t = Time.now; t - (t.to_i % 60) t = Time.now; t - (t.to_i % 60) . In my opinion, this is fast enough and a million times more readable than any other solutions presented here. That's why I think this is the best solution for your casual flooring, although it is significantly slower than the other three.

Awkward and not particularly slow or fast

The most voted solution on this page is Time.at((Time.now.to_f / 60).floor * 60) - this is the slowest of all the solutions on this page (before this answer) and much slower than the top 2 solutions. Using floats just to fit decimals also seems very illogical. For the rounded part, which would be fine, but rounding sounds like a “gender” to me. If something like it can be rounded or “ceiling”, that would be like t = Time.now; t - (60 - t.to_i % 60) % 60 t = Time.now; t - (60 - t.to_i % 60) % 60 or Time.at((Time.now.to_f / 60).ceil * 60) . The dual module that the to_i solution needs is a bit unpleasant, so although it is significantly faster, I would prefer the ceil method here. (Benchmarks are attached at the very end of this post)

For those who need speed

Associated (the differences are so insignificant that you cannot really announce the winner) are the top two performers in the test, where there are two to_i options that use a slightly different combination of operations and then convert the integer back to a Time object. If you are in a hurry, these are the ones you should use:

 Time.at((Time.now.to_i / 60) * 60) t = Time.now.to_i; Time.at(t - (t % 60)) 



Setting for rounding / level comparison

 Benchmark.bmbm do |x| x.report("to_f, /, ceil, * and Time.at") { 1_000_000.times { Time.at((Time.now.to_f / 60).ceil * 60) } } x.report("to_i, %, -, %, + and Time.at") { 1_000_000.times { t = Time.now; t + (60 - t.to_i % 60) % 60 } } end 

Results for rounding / level comparison tests

 Rehearsal ---------------------------------------------------------------- to_f, /, ceil, * and Time.at 4.410000 0.040000 4.450000 ( 4.446320) to_i, %, -, %, + and Time.at 3.910000 0.020000 3.930000 ( 3.939048) ------------------------------------------------------- total: 8.380000sec user system total real to_f, /, ceil, * and Time.at 4.420000 0.030000 4.450000 ( 4.454173) to_i, %, -, %, + and Time.at 3.860000 0.010000 3.870000 ( 3.884866) 
+3
Jun 11 '14 at 8:37
source share

Chuck's answer, while elegant, will cause you problems if you try to compare the values ​​obtained in this way; usecs are not nullified.

Shalmanese's answer will take care of this, or Chuck may be modified as:

 t = Time.new truncated_t = Time.at(t.to_i - t.sec - t.min % 15 * 60) 
+2
Jan 31 '11 at 21:25
source share

Ryan McGury’s solution did not work for time zones that weren’t half an hour. For example, Kathmandu is +5: 45, so rounding to 30.minutes leads to incorrect results. This should work:

 class ActiveSupport::TimeWithZone def floor(seconds = 60) return self if seconds.zero? Time.at(((self - self.utc_offset).to_f / seconds).floor * seconds).in_time_zone + self.utc_offset end def ceil(seconds = 60) return self if seconds.zero? Time.at(((self - self.utc_offset).to_f / seconds).ceil * seconds).in_time_zone + self.utc_offset end # returns whichever (out of #floor and #ceil) is closer to the current time def closest(seconds = 60) down, up = floor(seconds), ceil(seconds) ((self - down).abs > (self - up).abs) ? up : down end end 

And tests:

 class TimeHelperTest < ActionDispatch::IntegrationTest test "floor" do t = Time.now.change(min: 14) assert_equal Time.now.change(min: 10), t.floor(5.minutes) assert_equal Time.now.change(min: 0), t.floor(30.minutes) end test "ceil" do t = Time.now.change(min: 16) assert_equal Time.now.change(min: 20), t.ceil(5.minutes) assert_equal Time.now.change(min: 30), t.ceil(30.minutes) end test "closest" do t = Time.now.change(min: 18) assert_equal Time.now.change(min: 20), t.closest(5.minutes) assert_equal Time.now.change(min: 30), t.closest(30.minutes) assert_equal Time.now.change(min: 0), t.closest(60.minutes) end test "works in time zones that are off the half hour" do Time.zone = "Kathmandu" #2.1.0p0 :028 > Time.zone.now # => Tue, 30 Sep 2014 06:46:12 NPT +05:45 # doing .round(30.minutes) here would give 06:45 under the old method t = Time.zone.now.change(min: 30) assert_equal Time.zone.now.change(min: 30), t.closest(30.minutes) t = Time.zone.now.change(min: 0) assert_equal Time.zone.now.change(min: 0), t.closest(30.minutes) end end 
+2
Sep 30 '14 at 1:39
source share

Your current rating with

 min / 15 * 15 

only truncates min, therefore

 15 => 15 16 => 15 .. 29 => 15 30 => 30 

This is not rounding.

You can approximate rounding with an error

 (( min + 7.5 ) / 15).to_i * 15 

Or using internal elements:

 ( min.to_f / 15 ).round * 15 
0
Jan 16 '09 at 2:24
source share



All Articles