ActiveSupport 如何计算月总和? [英] How does ActiveSupport do month sums?

查看:51
本文介绍了ActiveSupport 如何计算月总和?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很高兴也很惊讶地发现 ActiveSupport 以我想要的方式计算月数.无论所讨论的月份中有多少天,将 1.month 添加到特定的 Time 将使您与 1.month 处于同一月的同一天代码>时间.

I was pleased and surprised to find that ActiveSupport does month sums in the way I wanted it to. Regardless of how many days are in the months in question, adding 1.month to a particular Time will land you on the same day-of-the-month as the Time.

> Time.utc(2012,2,1)
=> Wed Feb 01 00:00:00 UTC 2012
> Time.utc(2012,2,1) + 1.month
=> Thu Mar 01 00:00:00 UTC 2012

activesupport提供的Fixnum中的months方法没有给出线索:

the months method in Fixnum provided by activesupport does not give clues:

def months
  ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end

Time中遵循+方法...

def plus_with_duration(other) #:nodoc:
  if ActiveSupport::Duration === other
    other.since(self)
  else
    plus_without_duration(other)
  end
end

...引导我们到 Fixnum...

...leads us to since in Fixnum...

def since(time = ::Time.current)
  time + self
end

...这让我们无处可去.

...which leads us nowhere.

ActiveSupport(或其他东西)如何/在哪里进行巧妙的月份数学计算,而不是仅仅增加 30 天?

How/where is ActiveSupport (or something else) doing clever month math instead of just adding 30 days?

推荐答案

这是一个很好的问题.简短的回答是 1.month 是一个 ActiveSupport::Duration 对象(正如您已经看到的)并且它的身份以两种不同的方式定义:

That's a really good question. The short answer is that 1.month is an ActiveSupport::Duration object (as you already saw) and its identity is defined in two different ways:

  • as 30.days(以防您需要/尝试将其转换为秒数),
  • 作为 1 个月(以防您尝试将此持续时间添加到日期).
  • as 30.days (in case you need/try to convert it to a number of seconds), and
  • as 1 month (in case you try to add this duration to a date).

通过检查它的parts方法可以看到它仍然知道它相当于1个月:

You can see that it still knows that it is equivalent to 1 month by inspecting its parts method:

main > 1.month.parts
=> [[:months, 1]]

一旦你看到它仍然知道现在正好是 1 个月的证据,那么像 Time.utc(2012,2,1) + 1.month 这样的计算如何给出正确的结果就变得不那么神秘了对于没有正好 29 天的月份,以及为什么它给出的结果与 Time.utc(2012,2,1) + 30.days 给出的结果不同.

Once you see proof that it still knows that it's exactly 1 month, it's less mysterious how calculations like Time.utc(2012,2,1) + 1.month can give the correct result even for months that don't have exactly 29 days, and why it gives a different result than Time.utc(2012,2,1) + 30.days gives.

ActiveSupport::Duration 如何隐藏真实身份?

How do ActiveSupport::Duration conceal their true identity?

对我来说真正的谜团是它如何如此完美地隐藏其真实身份.我们知道它是一个 ActiveSupport::Duration 对象,但是很难让它承认它是!

The real mystery for me was how it hides its real identity so well. We know that it is a ActiveSupport::Duration object, yet it's very difficult to get it to admit that it is!

当您在控制台中检查它时(我正在使用 Pry),它看起来完全像(并声称 )一个普通的 Fixnum 对象:

When you inspect it in a console (I'm using Pry), it looks exactly like (and claims to be) a normal Fixnum object:

main > one_month = 1.month
=> 2592000

main > one_month.class
=> Fixnum

它甚至声称等同于 30.days(或 2592000.seconds),我们已经证明这是不正确的(至少不是在所有情况下)):

It even claims to be equivalent to 30.days (or 2592000.seconds), which we've shown to be not true (at least not in all cases):

main > one_month = 1.month
=> 2592000

main > thirty_days = 30.days
=> 2592000

main > one_month == thirty_days
=> true

main > one_month == 2592000
=> true

因此,要确定对象是否为 ActiveSupport::Duration,您不能依赖 class 方法.相反,您必须直截了当地问它:您是还是不是 ActiveSupport::Duration 的实例?"面对如此直接的质问,被质问的对象只好坦白:

So to find out whether an object is a ActiveSupport::Duration or not, you can't rely on the class method. Instead, you' have to ask it point-blank: "Are you or are you not an instance of ActiveSupport::Duration?" Confronted with such a direct question, the object in question will have no choice but to confess the truth:

main > one_month.is_a? ActiveSupport::Duration
=> true

另一方面,单纯的 Fixnum 对象必须低下头并承认它们不是:

Mere Fixnum objects, on the other hand, must hang their heads and admit that they are not:

main > 2592000.is_a? ActiveSupport::Duration
=> false

您还可以通过检查它是否响应 :parts 来将其与常规 Fixnum 区分开来:

You can also tell it apart from regular Fixnums by checking if it responds to :parts:

main > one_month.parts
=> [[:months, 1]]

main > 2592000.parts
NoMethodError: undefined method `parts' for 2592000:Fixnum
from (pry):60:in `__pry__'

拥有一系列零件很棒

拥有一组部件的好处在于,它允许您将持续时间定义为单位的混合,如下所示:

The cool thing about having an array of parts is that it allows you to have duration defined as a mix of units, like this:

main > (one_month + 5.days).parts
=> [[:months, 1], [:days, 5]]

这使它能够准确计算以下内容:

This allows it to accurate calculate such things as:

main > Time.utc(2012,2,1) + (one_month + 5.days)
=> 2012-03-06 00:00:00 UTC

...如果它只是存储秒,那么它不能能够正确计算 作为它的值.如果我们首先将 1.month 转换为它的等效"秒数或天数,您可以亲眼看到这一点:

... which it would not be able to calculate correctly if it simply stored only a number of days or seconds as its value. You can see this for yourself if we first convert 1.month to its "equivalent" number of seconds or days:

main > Time.utc(2012,2,1) + (one_month + 5.days).to_i
=> 2012-03-07 00:00:00 UTC

main > Time.utc(2012,2,1) + (30.days + 5.days)
=> 2012-03-07 00:00:00 UTC

ActiveSupport::Duration 如何工作?(血腥的实现细节)

How does ActiveSupport::Duration work? (Gory implementation details)

ActiveSupport::Duration 实际上被定义为(在 gems/activesupport-3.2.13/lib/active_support/duration.rb 中)作为 BasicObject 的子类,根据 docs,可以使用用于创建独立于 Ruby 对象层次结构的对象层次结构、代理对象(如 Delegator 类)或其他必须避免来自 Ruby 方法和类的命名空间污染的用途."

ActiveSupport::Duration is actually defined (in gems/activesupport-3.2.13/lib/active_support/duration.rb) as a subclass of BasicObject, which according to the docs, "can be used for creating object hierarchies independent of Ruby's object hierarchy, proxy objects like the Delegator class, or other uses where namespace pollution from Ruby's methods and classes must be avoided."

ActiveSupport::Duration 使用 method_missing 将方法委托给它的 @value 变量.

ActiveSupport::Duration uses method_missing to delegate methods to its @value variable.

额外问题:有谁知道为什么 ActiveSupport::Duration 对象声称不响应 :parts 即使它实际上确实,为什么parts方法没有列在方法列表中?

Bonus question: Does anyone know why an ActiveSupport::Duration object claims to not respond to :parts even though it actually does, and why the parts method isn't listed in the methods list?

main > 1.month.respond_to? :parts
=> false

main > 1.month.methods.include? :parts
=> false

main > 1.month.methods.include? :since
=> true

Answer:因为BasicObject没有定义respond_to?方法,发送respond_to?>ActiveSupport::Duration 对象最终会调用它的 method_missing 方法,如下所示:

Answer: Because BasicObject does not define a respond_to? method, sending respond_to? to an ActiveSupport::Duration object will end up calling its method_missing method, which looks like this:

def method_missing(method, *args, &block) #:nodoc:
  value.send(method, *args, &block)
end

1.month.value 只是 Fixnum 2592000,所以它实际上最终调用了 2592000.respond_to?:parts,当然是false.

1.month.value is simply the Fixnum 2592000, so it effectively ends up calling 2592000.respond_to? :parts, which of course is false.

不过,这很容易解决,只需在 ActiveSupport::Duration 类中添加一个 respond_to? 方法:

This would be easy to solve, though, by simply adding a respond_to? method to the ActiveSupport::Duration class:

main > ActiveSupport::Duration.class_eval do
           def respond_to?(name, include_private = false)
               [:value, :parts].include?(name) or
               value.respond_to?(name, include_private) or
               super
             end
         end
=> nil

main > 1.month.respond_to? :parts
=> true

为什么 methods 错误地省略了 :parts 方法的解释是相同的:因为 methods 消息只是被委托给 value,它当然没有parts方法.我们可以像添加我们自己的 methods 方法一样轻松地修复这个错误:

The explanation for why methods incorrectly omits the :parts method is the same: because the methods message simply gets delegated to value, which of course does not have a parts method. We could fix this bug as easily as adding our own methods method:

main > ActiveSupport::Duration.class_eval do
         def methods(*args)
           [:value, :parts] | super
         end
       end
=> nil

main >  1.month.methods.include? :parts
=> true

这篇关于ActiveSupport 如何计算月总和?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆