2009-05-26 8 views
3

Ich versuche, eine effiziente Methode zum Abschneiden von Ruby-Time-Objekten nach einer bestimmten Auflösung zu finden.Ruby Time-Objekte effizient abschneiden

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 

Kennt jemand eine schnellere Version, die die gleiche Aufgabe erfüllt?

Antwort

2

Dank euch beiden . Da ich Rails nicht verwende, habe ich den Code kopiert, um einen Leistungsbenchmark zu erstellen.

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 

Hier sind die Ergebnisse:

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) 

Scheint wie meine erste Abschneide Version noch am effizientesten ist, mit Ausnahme des Jahres Fall. Es gibt eine kleine Eigenart im Jahr für truncate und truncate3 obwohl. Es wird angezeigt, wie

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

statt

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

Irgendwelche Ideen, warum?

+0

Das Problem mit dem Jahr Fall zu verwenden, kann die Zeit sein. local mit 10 args ist überspezifiziert, und ich bin mir nicht sicher, wie die Zeitbibliothek mit Konflikten umgeht: YDAY, WDAY sind redundant und isDst kann mit "PDT" vs "PST" nicht synchron sein. Eine Möglichkeit, den Fehler loszuwerden, ist 'Time.local t.reverse [4,6]' – AShelly

+0

Diese Antwort ist genau das, wonach ich gesucht habe. Vielen Dank. Ich habe 'Time.local (* t)' auf 'self.class.gm (* t)' um UTC zu verwenden und wenn es eine Unterklasse von 'Time' ist, wird es seine eigene Klasse benutzen. – animatedgif

6

Dies wurde bereits in der ActiveSupport-Bibliothek von Rails Time extensions implementiert. Siehe die change Methode sowie die verschiedenen at_beginning_of_* Methoden.

1

Wenn Sie nicht wollen Active wie Greg schon sagt, ich glaube, Sie so etwas wie dieses

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 

Weiß nicht tun könnte, wenn es schneller obwohl ist ...

1

Sie müssen nicht Rails verwenden, um die ActiveSupport-Goodies zu verwenden. Ich lief in genau dieses Problem zusätzlich zu wollen Sachen wie 1.day oder 3.minutes oder 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

Ich sehe keine "Anfang_von" Methoden für weniger als einen Tag. Die von Greg Campbell angesprochene "Change" -Methode ist vielseitiger. – Kelvin