2009-05-26 5 views
3

Я пытаюсь создать эффективный метод обрезания объектов Ruby Time в соответствии с данным разрешением.Усечение объектов Ruby Time эффективно

class Time 
    def truncate resolution 
    t = to_a 
    case resolution 
    when :min 
     t[0] = 0 
    when :hour 
     t[0] = t[1] = 0 
    when :day 
     t[0] = t[1] = 0 
     t[2] = 1 
    when :month 
     t[0] = t[1] = 0 
     t[2] = t[3] = 1 
    when :week 
     t[0] = t[1] = 0 
     t[2] = 1 
     t[3] -= t[6] - 1 
    when :year 
     t[0] = t[1] = 0 
     t[2] = t[3] = t[4] = 1 
    end 

    Time.local *t 
    end 
end 

Кто-нибудь знает более быструю версию, которая выполняет ту же задачу?

ответ

2

Благодаря вам обоим , Поскольку я не использую Rails, я скопировал код для выполнения теста производительности.

require 'benchmark' 

class Time 
    def truncate resolution 
    t = to_a 
    case resolution 
    when :min 
     t[0] = 0 
    when :hour 
     t[0] = t[1] = 0 
    when :day 
     t[0] = t[1] = t[2] = 0 
    when :week 
     t[0] = t[1] = t[2] = 0 
     t[3] -= t[6] - 1 
    when :month 
     t[0] = t[1] = t[2] = 0 
     t[3] = 1 
    when :year 
     t[0] = t[1] = t[2] = 0 
     t[3] = t[4] = 1 
    end 

    Time.local *t 
    end 

    def truncate2 resolution 
    opts = {} 

    case resolution 
    when :min 
     opts[:sec] = 0 
    when :hour 
     opts[:sec] = opts[:min] = 0 
    when :day 
     opts[:sec] = opts[:min] = opts[:hour] = 0 
    when :week 
     opts[:sec] = opts[:min] = opts[:hour] = 0 
     opts[:day] = wday - 1 if wday != 1 
    when :month 
     opts[:sec] = opts[:min] = opts[:hour] = 0 
     opts[:day] = 1 
    when :year 
     opts[:sec] = opts[:min] = opts[:hour] = 0 
     opts[:day] = opts[:month] = 1 
    end 

    change opts 
    end 

    def truncate3 resolution 
    t = to_a 
    if resolution == :week 
     t[0] = t[1] = 0 
     t[2] = 1 
     t[3] -= t[6] - 1 
    else 
     len = [:sec, :min, :hour, :day, :month, :year].index(resolution) 
     t.fill(0, 0, len) 
     t.fill(1, 3, len-3) 
    end 

    Time.local *t 
    end  

    def change opts 
    Time.local(
     opts[:year] || year, 
     opts[:month] || month, 
     opts[:day] || day, 
     opts[:hour] || hour, 
     opts[:min] || (opts[:hour] ? 0 : min), 
     opts[:sec] || ((opts[:hour] || opts[:min]) ? 0 : sec), 
    ) 
    end 
end 

Resolutions = [:sec, :min, :hour, :day, :week, :month, :year] 

# Correctness check. 
puts Resolutions.map { |r| "#{r}: #{Time.now.truncate r}" } << "\n" 
puts Resolutions.map { |r| "#{r}: #{Time.now.truncate2 r}" } << "\n" 
puts Resolutions.map { |r| "#{r}: #{Time.now.truncate3 r}" } << "\n" 

n = 100000 
now = Time.now 

Benchmark.bm(10) do |x| 
    x.report("truncate") { n.times { Resolutions.each { |r| now.truncate r } } } 
    x.report("truncate2") { n.times { Resolutions.each { |r| now.truncate2 r } } } 
    x.report("truncate3") { n.times { Resolutions.each { |r| now.truncate3 r } } } 
end 

Benchmark.bm(10) do |x| 
    Resolutions.each do |unit| 
    x.report("#{unit}") { n.times { now.truncate unit } } 
    x.report("#{unit}2") { n.times { now.truncate2 unit } } 
    x.report("#{unit}3") { n.times { now.truncate3 unit } } 
    end 
end 

Вот результаты:

sec: 2009-05-26 13:44:20 -0700 
min: 2009-05-26 13:44:00 -0700 
hour: 2009-05-26 13:00:00 -0700 
day: 2009-05-26 00:00:00 -0700 
week: 2009-05-25 00:00:00 -0700 
month: 2009-05-01 00:00:00 -0700 
year: 2008-12-31 23:00:00 -0800 

sec: 2009-05-26 13:44:20 -0700 
min: 2009-05-26 13:44:00 -0700 
hour: 2009-05-26 13:00:00 -0700 
day: 2009-05-26 00:00:00 -0700 
week: 2009-05-01 00:00:00 -0700 
month: 2009-05-01 00:00:00 -0700 
year: 2009-01-01 00:00:00 -0800 

sec: 2009-05-26 13:44:20 -0700 
min: 2009-05-26 13:44:00 -0700 
hour: 2009-05-26 13:00:00 -0700 
day: 2009-05-26 00:00:00 -0700 
week: 2009-05-25 01:00:00 -0700 
month: 2009-05-01 00:00:00 -0700 
year: 2008-12-31 23:00:00 -0800 

       user  system  total  real 
truncate 5.910000 0.020000 5.930000 ( 5.947453) 
truncate2 6.180000 0.020000 6.200000 ( 6.232918) 
truncate3 6.150000 0.020000 6.170000 ( 6.253931) 
       user  system  total  real 
sec   0.720000 0.000000 0.720000 ( 0.749040) 
sec2  0.830000 0.010000 0.840000 ( 0.863029) 
sec3  0.800000 0.000000 0.800000 ( 0.820477) 
min   0.700000 0.000000 0.700000 ( 0.709238) 
min2  0.860000 0.010000 0.870000 ( 0.860168) 
min3  0.770000 0.000000 0.770000 ( 0.795734) 
hour  0.680000 0.000000 0.680000 ( 0.705306) 
hour2  0.850000 0.010000 0.860000 ( 0.867235) 
hour3  0.740000 0.000000 0.740000 ( 0.746338) 
day   0.720000 0.000000 0.720000 ( 0.724324) 
day2  0.890000 0.010000 0.900000 ( 0.894312) 
day3  0.780000 0.000000 0.780000 ( 0.788007) 
week  0.730000 0.000000 0.730000 ( 0.736604) 
week2  0.910000 0.000000 0.910000 ( 0.910925) 
week3  0.600000 0.000000 0.600000 ( 0.611683) 
month  0.720000 0.000000 0.720000 ( 0.719515) 
month2  0.880000 0.010000 0.890000 ( 0.888045) 
month3  0.780000 0.000000 0.780000 ( 0.789726) 
year  1.540000 0.010000 1.550000 ( 1.565335) 
year2  0.830000 0.000000 0.830000 ( 0.849737) 
year3  1.600000 0.010000 1.610000 ( 1.644958) 

Похоже, моя первая версия усечения по-прежнему наиболее эффективная, за исключением случая, года. В этом году есть небольшая причуда для truncate и truncate3. Он отображается как

2008-12-31 23:00:00 -0800 

вместо

2009-01-01 00:00:00 -0700 

Любые идеи, почему?

+0

Вопрос с год случае может оказаться, что время. локальный с 10 аргументами превышен, и я не уверен, как библиотека времени имеет дело с конфликтами: YDAY, WDAY являются избыточными, а isDst может быть не синхронизирован с «PDT» и «PST». Один из способов избавиться от ошибки - это сделать 'Time.local t.reverse [4,6]' – AShelly

+0

Этот ответ именно то, что я искал. Спасибо. Я переключил 'Time.local (* t)' на 'self.class.gm (* t)' для использования UTC, и если это подкласс 'Time', он будет использовать свой собственный класс. – animatedgif

6

Это уже реализовано для вас в библиотеке ActiveSupport от Rails Time extensions. См. Метод change, а также различные методы at_beginning_of_*.

1

Если вы не хотите ActiveSupport, как говорит Грег, я думаю, что вы могли бы сделать что-то вроде этого

t = to_a 
if resolution==:week 
    t[0] = t[1] = 0 
    t[2] = 1 
    t[3] -= t[6] - 1 
else 
    len = [:sec, :min, :hour, :day, :month, :year].index(resolution) 
    t.fill(0, 0,len) 
    t.fill(1, 3,len-3) 
end 
Time.local *t 

Не знаю, если это быстрее, хотя ...

1

Вам не нужно использовать Rails для использования ActiveSupport. Я столкнулся с этой конкретной проблемы в дополнение к желанию использовать такие вещи, как 1.day или 3.minutes или Time.now.beginning__of__day

irb(main):001:0> require 'rubygems' 
=> true 
irb(main):002:0> require 'activesupport' 
=> true 
irb(main):003:0> 1.day 
=> 1 day 
irb(main):004:0> 3.minutes 
=> 180 seconds 
irb(main):005:0> Time.now.beginning_of_day 
=> Sun Jun 28 00:00:00 -0400 2009 
irb(main):006:0> 
+0

Я не вижу никаких методов «begin_of» для чего-либо менее дня. Метод «изменения», упомянутый в ответе Грега Кэмпбелла, более универсален. – Kelvin

Смежные вопросы