I'm pretty sure that this is intentional. A duration contains a list
of parts which represent the time increments, it also contains an
integer value which is the number of seconds in the duration. But that
value for things like month and year isn't what's used when adding a
duration to a Time, since the number of seconds in a month or a year
depends on the particular month( whichc could be 28, 29, 30, or 31
days) or year (365 or 366 days).
When you add two durations, it combines the list of parts, so:
2.months has one part [:months, 2]
but
(1.month + 1.month) has two parts [:months, 1], [:months, 1]
Now duration.from_now gets Time.now and then iterates over the parts
effectively (simplified the code to get the gist)
parts.inject(Time.now) do |t,(type,number)|
t.advance(type => sign * number)
end
So,
2.months.from_now when Time.now is during August 31, ends up as
Time.now.advance(:months => 2) and since October has 31 days you get there.
but
(1.months + 1.months).from_now ends up as:
Time.now.advance(:months => 1).advance(:months => 1)
The first gets us to September 30 since September only has 30 days,
and that's how time.advance is defined, then the next advance gets us
to October 30.
You need to look at the code to understand this, Duration is a bit
tricky since it acts as a proxy to it's value, it's implemented as a
subclass of BasicObject and forwards anything it doesn't have a method
for to the value, so it reports it's class as Fixnum, or BigInteger,
and it also defines == to return true if the two values are equal
ignoring the parts.