【问题标题】:Truncating Ruby Time objects efficiently有效地截断 Ruby Time 对象
【发布时间】:2009-05-26 18:54:38
【问题描述】:

我正在尝试提出一种根据给定分辨率截断 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

有谁知道实现相同任务的更快版本?

【问题讨论】:

    标签: ruby date time truncate


    【解决方案1】:

    这已经在 Rails 的 ActiveSupport 库的Time extensions 中为您实现。查看change 方法,以及各种at_beginning_of_* 方法。

    【讨论】:

      【解决方案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)
      

      似乎我的第一个截断版本仍然是最有效的,除了年份情况。不过,truncatetruncate3 在这一年有一个小怪癖。它显示为

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

      而不是

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

      有什么想法吗?

      【讨论】:

      • year case 的问题可能是带有 10 个参数的 Time.local 被过度指定,我不确定 Time 库如何处理冲突:YDAY、WDAY 是多余的,而 isDst 可能是与“PDT”与“PST”不同步。摆脱错误的一种方法是执行Time.local t.reverse[4,6]
      • 这个答案正是我想要的。谢谢你。我将Time.local(*t) 切换到self.class.gm(*t) 以使用UTC,如果它是Time 的子类,它将使用自己的类。
      【解决方案3】:

      如果您不想要 Greg 建议的 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
      

      不知道是不是更快...

      【讨论】:

        【解决方案4】:

        您不必使用 Rails 来使用 ActiveSupport 好东西。除了想使用 1.day3.minutesTime.now.beginning__of__day 之类的东西之外,我还遇到了这个确切的问题p>

        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>
        

        【讨论】:

        • 我在不到一天的时间内看不到任何“beginning_of”方法。 Greg Campbell 的回答中提到的“改变”方法更加通用。
        猜你喜欢
        • 2017-08-24
        • 2010-10-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-13
        • 1970-01-01
        • 2019-07-04
        • 1970-01-01
        相关资源
        最近更新 更多