ActiveSupport::Duration 计算间隔不正确?
ActiveSupport::Duration incorrectly calculates interval?
我有一种非常奇怪的感觉,我得到的 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 小时)。还有一个web site that gives结果与PostgreSQL一致(虽然网页上说107天是3个月,15天)。
我错过了什么吗?分和秒从何而来? Ruby/Rails有没有更好的区间计算器?
更新
distance_of_time_in_words
returns 4个月!
更新 2
我最终对 Wizard 的解决方案进行了稍微修改以生成文本
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
显然,Action View 的本地化没有几个小时没有 about。所以我还必须在我的语言环境中添加相应的翻译
en:
datetime:
distance_in_words:
x_hours:
one: "1 hour"
other: "%{count} hours"
ActiveSupport::Duration 使用以下常量和算法计算它的值(我在下面添加了关于它在做什么的解释,但这里是源代码的 link)。正如您在下面看到的,SECONDS_PER_YEAR
常量是 公历中的平均秒数 (然后用于定义 SECONDS_PER_MONTH
)。正因为如此,SECONDS_PER_YEAR 和 SECONDS_PER_MONTH 中的 "average definition" 会出现意外的小时、分钟和秒。它被定义为平均值,因为月和年不是标准的固定时间量。
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 天。同样,对于这一年,如果您不使用平均值,则需要计算 leap/non-leap。
我不确定是否有任何 gems 可以为您执行此操作,但是我可以提供一个函数来帮助您计算以下持续时间(以天、小时、分钟和秒为单位)。
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}
我有一种非常奇怪的感觉,我得到的 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 小时)。还有一个web site that gives结果与PostgreSQL一致(虽然网页上说107天是3个月,15天)。
我错过了什么吗?分和秒从何而来? Ruby/Rails有没有更好的区间计算器?
更新
distance_of_time_in_words
returns 4个月!
更新 2 我最终对 Wizard 的解决方案进行了稍微修改以生成文本
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
显然,Action View 的本地化没有几个小时没有 about。所以我还必须在我的语言环境中添加相应的翻译
en:
datetime:
distance_in_words:
x_hours:
one: "1 hour"
other: "%{count} hours"
ActiveSupport::Duration 使用以下常量和算法计算它的值(我在下面添加了关于它在做什么的解释,但这里是源代码的 link)。正如您在下面看到的,SECONDS_PER_YEAR
常量是 公历中的平均秒数 (然后用于定义 SECONDS_PER_MONTH
)。正因为如此,SECONDS_PER_YEAR 和 SECONDS_PER_MONTH 中的 "average definition" 会出现意外的小时、分钟和秒。它被定义为平均值,因为月和年不是标准的固定时间量。
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 天。同样,对于这一年,如果您不使用平均值,则需要计算 leap/non-leap。
我不确定是否有任何 gems 可以为您执行此操作,但是我可以提供一个函数来帮助您计算以下持续时间(以天、小时、分钟和秒为单位)。
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}