【问题标题】:ActiveSupport::Duration incorrectly calculates interval?ActiveSupport::Duration 错误地计算间隔?
【发布时间】:2020-01-23 23:19:30
【问题描述】:

我有一种非常奇怪的感觉,即 ActiveSupport::Duration 计算的持续时间不正确。这是我拥有的代码的精髓

require 'time'
require 'active_support/duration'
require 'active_support/gem_version'
a = Time.parse('2044-11-18 01:00:00 -0600')
b = Time.parse('2045-03-05 04:00:00 -0600')
ActiveSupport::Duration.build(b - a).inspect
ActiveSupport.gem_version

这就是我得到的

[30] pry(main)> require 'time'
=> false
[31] pry(main)> require 'active_support/duration'
=> false
[32] pry(main)> require 'active_support/gem_version'
=> false
[33] pry(main)> a = Time.parse('2044-11-18 01:00:00 -0600')
=> 2044-11-18 01:00:00 -0600
[34] pry(main)> b = Time.parse('2045-03-05 04:00:00 -0600')
=> 2045-03-05 04:00:00 -0600
[35] pry(main)> ActiveSupport::Duration.build(b - a).inspect
=> "3 months, 2 weeks, 1 day, 19 hours, 32 minutes, and 42.0 seconds"
[36] pry(main)> ActiveSupport.gem_version
=> Gem::Version.new("6.0.1")

我用 PostgreSQL 交叉检查了结果

select justify_interval('2045-03-05 04:00:00 -0600'::timestamp - '2044-11-18 01:00:00 -0600'::timestamp)

并得到3 mons 17 days 03:00:00(或 107 天零 3 小时)。还有一个与 PostgreSQL 一致的web site that gives 结果(虽然网页上说 107 天是 3 个月和 15 天)。

我错过了什么吗?分钟和秒是从哪里来的?有没有更好的 Ruby/Rails 间隔计算器?

更新 distance_of_time_in_words 退货 4 个月!

更新 2 我最终得到了稍微修改的向导解决方案以生成文本

  def nice_duration(seconds)
    parts = duration_in_whms(seconds)
    out = []
    I18n.with_options(scope: 'datetime.distance_in_words') do |locale|
      out.push locale.t(:x_days, count: parts[:days]) if parts.key?(:days)
      out.push locale.t(:x_hours, count: parts[:hours]) if parts.key?(:hours)
      out.push locale.t(:x_minutes, count: parts[:minutes]) if parts.key?(:minutes)
    end
    out.join ' '
  end

  private

  def duration_in_whms(seconds)
    parts_and_seconds_in_part = {:days => 86400, :hours => 3600, :minutes => 60}
    result = {}
    remainder = seconds
    parts_and_seconds_in_part.each do |k, v|
      out = (remainder / v).to_i
      result[k] = out if out.positive?
      remainder -= out * v
    end
    result.merge(seconds: remainder)
  end

显然,如果没有about,Action View 的本地化就没有几个小时。所以我还必须在我的语言环境中添加相应的翻译

en:
  datetime:
    distance_in_words:
      x_hours:
        one:   "1 hour"
        other: "%{count} hours"

【问题讨论】:

    标签: ruby-on-rails ruby activesupport


    【解决方案1】:

    ActiveSupport::Duration 使用以下常量和算法计算它的值(我在下面添加了关于它在做什么的解释,但这里是 link 到源代码)。正如您在下面看到的,SECONDS_PER_YEAR 常量是公历中的平均秒数(然后用于定义SECONDS_PER_MONTH)。正是因为这个,SECONDS_PER_YEAR 和 SECONDS_PER_MONTH 的“平均定义”,你得到了意想不到的小时、分钟和秒。它被定义为平均值,因为月份和年份不是标准的固定时间量。

    SECONDS_PER_MINUTE = 60
    SECONDS_PER_HOUR   = 3600
    SECONDS_PER_DAY    = 86400
    SECONDS_PER_WEEK   = 604800
    SECONDS_PER_MONTH  = 2629746 # This is 1/12 of a Gregorian year
    SECONDS_PER_YEAR   = 31556952 # The length of a Gregorian year = 365.2425 days
    
    # You pass ActiveSupport::Duration the number of seconds (b-a) = 9255600.0 seconds
    
    remainder_seconds = 9255600.0
    
    # Figure out how many years fit into the seconds using integer division.
    years = (remainder_seconds/SECONDS_PER_YEAR).to_i # => 0
    # Subtract the amount of years from the remaining_seconds
    remainder_seconds -= years * SECONDS_PER_YEAR # => 9255600.0
    
    months = (remainder_seconds/SECONDS_PER_MONTH).to_i # => 3
    remainder_seconds -= months * SECONDS_PER_MONTH # => 1366362.0
    
    weeks = (remainder_seconds/SECONDS_PER_WEEK).to_i # => 2
    remainder_seconds -= weeks * SECONDS_PER_WEEK # => 156762.0
    
    days = (remainder_seconds/SECONDS_PER_DAY).to_i # => 1
    remainder_seconds -= days * SECONDS_PER_DAY # => 70362.0   
    
    hours = (remainder_seconds/SECONDS_PER_HOUR).to_i # => 19
    remainder_seconds -= hours * SECONDS_PER_HOUR # => 1962.0
    
    minutes = (remainder_seconds/SECONDS_PER_MINUTE).to_i # => 32
    remainder_seconds -= minutes * SECONDS_PER_MINUTE # => 42
    
    seconds = remainder_seconds # => 42
    
    puts "#{years} years, #{months} months, #{weeks} weeks, #{days} days, #{hours} hours, #{minutes} minutes, #{seconds} seconds"
    # 0 years, 3 months, 2 weeks, 1 days, 19 hours, 32 minutes, 42.0 seconds
    

    为避免您遇到的问题,我建议仅以周、日、小时、分钟和秒表示时间(基本上不包括月份和年份)。

    如果您不使用平均值,一个月内的秒数会很复杂,因为您需要分别计算每个月的 28、29、30 和 31 天。同样,对于这一年,如果不使用平均值,则需要考虑闰/非闰。

    我不确定是否有任何 gem 可以为您执行此操作,但是我可以提供一个函数来帮助您计算下面的天、小时、分钟和秒的持续时间。

    def duration_in_whms(seconds)
      parts_and_seconds_in_part = {:weeks => 604800, :days => 86400, :hours => 3600, :minutes => 60}
      result = {}
      remainder = seconds
      parts_and_seconds_in_part.each do |k, v|
        result[k] = (remainder/v).to_i
        remainder -= result[k]*v
      end
      result.merge(seconds: remainder)
    end
    
    duration_in_whms(9255600) => # {:weeks=>15, :days=>2, :hours=>3, :minutes=>0, :seconds=>0.0}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-18
      • 1970-01-01
      • 1970-01-01
      • 2012-08-14
      • 1970-01-01
      相关资源
      最近更新 更多