你说的是“四舍五入”所以我不确定你是在找四舍五入还是地板但这里有两者兼顾的代码。我认为如果将 round_off和 floor方法添加到 Time 类中,这样的内容读起来会非常好。附加的好处是,您可以更容易地通过任何时间分区进行舍入。
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
# this is an extension of Ryan McGeary's 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
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
从这个意义上来说,使用其中最慢的 t = Time.now; t - (t.to_i % 60).seconds是合理的,只是因为。第二秒在里面太酷了。
没有那么慢,几乎是可读的解决方案
然而,由于它实际上根本不需要,并使操作比没有它贵两倍,我不得不说,我的选择是 t = Time.now; t - (t.to_i % 60)。在我看来,它的速度足够快,可读性比这里提出的任何其他解决方案都要高百万倍。这就是为什么我认为这是最好的解决方案,为您休闲地板的需要,虽然它是一个明显慢于其他三个。
Ryan McGeary 的解决方案不适用于不在半小时内的时区。例如,加德满都是 + 5:45,所以四舍五入到30。分钟得到了错误的结果。这应该会奏效:
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
还有测试:
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