Trimming Ruby Time Objects Effectively

I am trying to find an efficient method to crop Ruby Time objects according to a given resolution.

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

Does anyone know a faster version that achieves the same task?

+3
source share
4 answers

Thanks to both of you. Since I do not use Rails, I copied the code to run a performance test.

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

Here are the results:

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)

It seems that my first version of truncation is still the most effective, except for the case of the year. There is little fad for truncateand this year truncate3. It is displayed as

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

but not

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

Any ideas why?

+2
source

ActiveSupport Rails . . change, at_beginning_of_*.

+6

If you don't want Greg to offer ActiveSupport, I think you could do something like this

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

I don’t know if it’s faster ...

+1
source

You do not need to use Rails to take advantage of ActiveSupport. I ran into this exact problem in addition to wanting to use things like 1.day or 3.minutes or 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>
+1
source

Source: https://habr.com/ru/post/1709119/


All Articles