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

时间:2009-01-16 01:59:34

标签: ruby

有一种简单的方法可以将时间缩短到最近的15分钟吗?

这就是我目前正在做的事情。有没有更简单的方法呢?

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

12 个答案:

答案 0 :(得分:122)

你说“向下”,所以我不确定你是否真的在寻找圆形或地板,但这里是两个代码。如果您将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参数。如果您不想要这种依赖关系,请改用15 * 60

答案 1 :(得分:20)

我不太熟悉ruby的语法,但你可以使用modulo将 down 舍入到最接近的15分钟。 (即x - (x modulo 15))。我猜想语法会像

t.min - ( t.min % 15)

这将使您的可能值集合为0,15,30和45.假设0 <= t.min <= 59。

答案 2 :(得分:20)

我想我会发布另一个解决方案,提供向上和向下舍入到最接近的秒数。哦,这并没有像其他一些解决方案那样改变时区。

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代替。

基于此解决方案,可以轻松实现地板和天花板。

答案 3 :(得分:9)

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

t = Time.new
rounded_t = t-t.sec-t.min%15*60

答案 4 :(得分:8)

我写了Rounding gem来处理这类案件。

简化时间就像在时间上调用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使用Rational数字来避免舍入错误。您可以通过提供偏移round_to(1.week, Time.parse("2015-5-4 00:00:00 UTC"))来舍入到最近的星期一。我们在生产中使用它。

我写了一篇blog post来解释更多内容。希望你觉得它有用。

答案 5 :(得分:7)

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

这会将你的时间缩短到最后的15分钟。您可以将15.minutes更改为每个时间刻度。

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

答案 6 :(得分:5)

你可以这样做:

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

答案 7 :(得分:4)

# 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

答案 8 :(得分:3)

前言

这里有很多解决方案,我开始怀疑它们的效率(效率可能不是这个问题中最重要的方面)。我从这里拿了一些,扔了几个我自己的。 (N.B.虽然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中最慢的{I}只能因为。秒在那里很酷。

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

然而,由于它实际上根本不需要,并且操作的成本是没有它的两倍,我不得不说我的选择是t = Time.now; t - (t.to_i % 60)。在我看来,它足够快,比这里提出的任何其他解决方案的可读性高出百万倍。这就是为什么我认为它是满足您休闲地板需求的最佳解决方案,尽管它比其他三种解决方案要慢得多。

尴尬而不是特别慢或快

此页面Time.at((Time.now.to_f / 60).floor * 60)上投票最多的解决方案是此页面上所有解决方案中最慢的(在此答案之前)并且明显慢于前2个解决方案。使用浮点数只是为了能够将小数点去掉也似乎非常不合逻辑。对于可能没问题的圆角部分,但是四舍五入的声音就像&#34;地板&#34;对我来说。如果它的任何对应物可能是四舍五入或“#34; ceiling&#34;”,那将是t = Time.now; t - (60 - t.to_i % 60) % 60Time.at((Time.now.to_f / 60).ceil * 60)之类的东西。 to_i解决方案需要的双模数看起来有点讨厌,所以即使它明显更快,我也不喜欢ceil方法。 (本文最后附上的基准)

对于需要速度的人

绑定(差异是如此微不足道,以至于你无法真正宣布获胜者)在测试中排名前两位的表演者,其中两个to_i变体使用稍微不同的操作组合,然后将整数转换回Time对象。如果你匆忙这些是你应该使用的那些:

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

舍入/ ceil基准测试的设置

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

舍入/ ceil基准测试的结果

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)

答案 9 :(得分:2)

Chuck的答案虽然优雅,但如果你试图比较以这种方式得出的值,你会遇到麻烦; usecs没有归零。

Shalmanese的回答照顾到了这一点,或者Chuck's可以修改为:

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

答案 10 :(得分:2)

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

答案 11 :(得分:1)

您当前的评估使用

min / 15 * 15 

只是截断min,所以

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

这不是'四舍五入'。

您可以使用

进行粗略近似舍入
(( min + 7.5 ) / 15).to_i * 15 

或者,使用内部:

( min.to_f / 15 ).round * 15