如何在 Ruby 中将时间缩短到最接近的15分钟?

有没有一种简单的方法把时间缩短到最近的15分钟?

这就是我现在正在做的,有没有更简单的方法?

t = Time.new
rounded_t = Time.local(t.year, t.month, t.day, t.hour, t.min/15*15)
34426 次浏览

你可以这样做:

Time.at(t.to_i/(15*60)*(15*60))

我不是很熟悉 Ruby 的语法,但是你可以使用模数把 放下调整到最近的15分钟。(即 x-(x 模15))。我猜语法应该是这样的

t.min - ( t.min % 15)

这将使您的一组可能值为0、15、30和45。

你目前的评估

min / 15 * 15

只是截断了最小值,所以

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

不是“四舍五入”。

您可以使用

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

或者,使用内部结构:

( min.to_f / 15 ).round * 15

你说的是“四舍五入”所以我不确定你是在找四舍五入还是地板但这里有两者兼顾的代码。我认为如果将 round_offfloor方法添加到 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

注意: ActiveSupport 仅对于漂亮的 15.minutes参数是必需的。

因为 Ruby 允许 Times 上的算术(以秒为单位) ,所以你可以这样做:

t = Time.new
rounded_t = t-t.sec-t.min%15*60
# 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

在 x.minutes 特性的示例中使用了 ActiveSupport,您可以改用15 * 60。

方法基于此解决方案可以方便地实现 floor 和 ceil。

Chuck 的回答虽然优雅,但是如果你试图用这种方法比较得到的值,那么你就会遇到麻烦; 这些用法并没有被归零。

Shalmanese 的回答可以解决这个问题,或者 Chuck 的回答可以修改为:

t = Time.new
truncated_t = Time.at(t.to_i - t.sec - t.min % 15 * 60)

我找到了一个非常可读的解决方案;

这将四舍五入您的时间到最后一轮15分钟。您可以改变 15.minutes的每一个时间刻度可能。

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

前言

这里有相当多的解决方案,我开始怀疑它们的效率(你的效率可能不是这个问题中最重要的方面)。我从这里拿了一些,加了几个我自己的。(注意,尽管 OP 要求四舍五入到最接近的15分钟,但为了更简单的样本,我只用1分钟/60分钟做了比较和样本)。

设置

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

结果

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)

分析结果

这是怎么回事?这东西在你的硬件上可能工作得更快或者更慢,所以不要把我的电脑当真。正如你所看到的,另一件事情是,除非我们在数百万次操作的规模上进行这些操作,否则就处理能力而言,你使用哪种方法不会产生太大的差异(但是,请注意,例如,大多数云计算解决方案提供的处理能力非常小,因此在这样的环境下,数百万可能是数十万或数万)。

速度最慢,但可能是最具可读性的解决方案

从这个意义上来说,使用其中最慢的 t = Time.now; t - (t.to_i % 60).seconds是合理的,只是因为。第二秒在里面太酷了。

没有那么慢,几乎是可读的解决方案

然而,由于它实际上根本不需要,并使操作比没有它贵两倍,我不得不说,我的选择是 t = Time.now; t - (t.to_i % 60)。在我看来,它的速度足够快,可读性比这里提出的任何其他解决方案都要高百万倍。这就是为什么我认为这是最好的解决方案,为您休闲地板的需要,虽然它是一个明显慢于其他三个。

尴尬的,不是特别慢或特别快的

本页投票最多的解决方案 Time.at((Time.now.to_f / 60).floor * 60)是本页所有解决方案中速度最慢的(在此答案之前) ,明显慢于前2个解决方案。使用 float 仅仅是为了能够将小数位移去,这似乎也是非常不合逻辑的。对于舍入的部分,这是可以的,但舍入听起来像“地板”对我来说。如果有什么对应的可能是四舍五入或“天花板”,这将是像 t = Time.now; t - (60 - t.to_i % 60) % 60Time.at((Time.now.to_f / 60).ceil * 60)的东西。To _ i 解决方案在这里需要的双模看起来有点讨厌,所以即使它明显更快,在这里我还是更喜欢 ceil 方法。(基准附于本文末尾)

给那些需要速度的人

平手(差异是如此微不足道,你不能真正宣布一个赢家)在测试中的前两个表现,其中两个到 _ i 变量使用略有不同的操作组合,然后将整数转换回时间对象。如果你赶时间,下面这些是你应该使用的:

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

四舍五入/最高基准的设置

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

四舍五入/最高基准测试结果

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)

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

我写 圆形宝石就是为了处理这种案子。

四舍五入一个时间变得像按时调用 floor_to一样简单。也支持向上舍入和向最近的舍入(ceil_toround_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

保留原始时间的时区(本例中为 UTC)。你不需要加载 ActiveSupport ーー你可以编写 floor_to(15*60),它会工作得很好。Gem 使用 理性数字来避免舍入错误。通过提供偏移量 round_to(1.week, Time.parse("2015-5-4 00:00:00 UTC")),可以四舍五入到最近的星期一。我们在生产中使用它。

我写了一个 博客文章解释更多。希望你觉得它有帮助。

如果您正在使用 ActiveSupport,那么您可以使用它的一些 time 助手。

t = Time.now
t.at_beginning_of_hour + (t.min - t.min % 15).minutes