Как округлить время до ближайших 15 минут в Ruby?
есть ли простой способ округлить время до ближайших 15 минут?
Это то, что я сейчас делаю. Есть ли более простой способ сделать это?
t = Time.new
rounded_t = Time.local(t.year, t.month, t.day, t.hour, t.min/15*15)
12 ответов:
Вы сказали "округлить вниз", так что я не уверен, если вы на самом деле ищете круглый или пол, но вот код, чтобы сделать оба. Я думаю, что что-то вроде этого читается очень хорошо, если вы добавляете
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
Примечание: ActiveSupport был необходим только для довольно
Я не очень хорошо знаком с синтаксисом ruby, но вы можете округлить вниз до ближайших 15 минут по модулю. (т. е. x - (x по модулю 15)). Я бы предположил, что синтаксис будет что-то вроде
t.min - ( t.min % 15)
это сделает ваш набор возможных значений 0, 15, 30 и 45. Предполагая 0
Я думал, что опубликую другое решение, которое обеспечивает округление вверх и вниз до ближайшего количества секунд. О, и это не меняет часовой пояс, как некоторые другие решения.
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 был использован в примерах для функции x.minutes. Вы можете использовать 15 * 60 вместо.
методы floor и ceil могут быть легко реализованы на основе этого решения.
Так как Ruby позволяет арифметику (в секундах) на раз, вы можете просто сделать это:
t = Time.new rounded_t = t-t.sec-t.min%15*60
Я написал округление драгоценного камня для обработки такого рода случаев.
округление времени становится таким же простым, как вызов
floor_to
на время. Округление и округление до ближайшего также поддерживаются (ceil_to
и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
часовой пояс исходного времени сохраняется (UTC в данном случае). Вам не нужно ActiveSupport загружен-вы можете написать
floor_to(15*60)
и это будет работать нормально. Камень использует рациональный номера, чтобы избежать округления ошибки. Вы можете округлить до ближайшего понедельника, предоставив смещениеround_to(1.week, Time.parse("2015-5-4 00:00:00 UTC"))
. Мы используем его в производстве.Я написал блоге это объясняет больше. Надеюсь, вы найдете это полезным.
Я нашел очень четкий решения;
это округлит ваше время до последних округленных 15 минут. Вы можете изменить
15.minutes
для каждого возможного масштаба времени.Time.at(Time.now.to_i - (Time.now.to_i % 15.minutes))
# 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
предисловие
здесь довольно много решений, и я начал задаваться вопросом об их эффективности (ты эффективность, вероятно, не самый важный аспект в этой проблеме). Я взял немного отсюда и добавил пару своих. (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
может быть оправдано только потому, что .секунды там такие классные.не так медленно и почти как читаемое решение
однако, так как это на самом деле не нужно вообще и делает операцию более чем в два раза дороже, чем без него я должен сказать, что мой выбор
t = Time.now; t - (t.to_i % 60)
. На мой взгляд, это достаточно быстро и в миллион раз более читабельно, чем любое другое решение, представленное здесь. Именно поэтому я думаю, что это лучшее решение для вас случайные потребности напольных покрытий, хотя это значительно медленнее, чем три других.неудобно и не особенно медленно или быстро
самое проголосованное решение на этой странице
Time.at((Time.now.to_f / 60).floor * 60)
это самый медленный все решения на этой странице (до этого ответа) и значительно медленнее, чем верхние 2 решения. Использование поплавков только для того, чтобы иметь возможность убрать десятичные знаки, также кажется очень нелогичным. Для округления это было бы нормально, но округление вниз звучит как "настил" для меня. Во всяком случае, аналогом для него может быть округление или "потолок", который будет чем-то вродеt = Time.now; t - (60 - t.to_i % 60) % 60
илиTime.at((Time.now.to_f / 60).ceil * 60)
. Двойной модуль, который требуется для решения to_i, немного неприятно выглядит, поэтому, хотя это и так значительно быстрее, Здесь я бы предпочел метод ceil. (Бенчмарки прилагаются в самом конце этого поста)для тех, кто нуждается в скорости
привязанные (различия настолько незначительны, что вы не можете действительно объявить победителя) лучшие два исполнителя в тесте, где два варианта to_i, которые используют несколько другую комбинацию операций, а затем преобразуют целое число обратно в объект времени. Если вы в спешке это те, которые вы должны использование:
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)
ответ Чака, хотя и элегантный, приведет вас к неприятностям, если вы попытаетесь сравнить значения, полученные таким образом; usecs не обнуляются.
ответ Шалманезе заботится об этом, или Чак может быть изменен как:
t = Time.new truncated_t = Time.at(t.to_i - t.sec - t.min % 15 * 60)
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