2014-10-16 3 views
4

Как это возможно?(Time.now.utc.to_date + 1.month + 15.days)! = (Time.now.utc.to_date + 15.days + 1.month)

Time.now.utc.to_date + 1.month + 15.days #=> Mon, 01 Dec 2014 
Time.now.utc.to_date + 15.days + 1.month #=> Sun, 30 Nov 2014 

Кто-нибудь видел это?

/редактировать

Я предполагаю, что я задал этот вопрос неправильный путь. Как вы, ребята, объясните это?

Time.now.utc.to_date + (15.days + 1.month) #=> Mon, 08 Dec 2014 
Time.now.utc.to_date + (1.month + 15.days) #=> Tue, 09 Dec 2014 

(15.days + 1.month) #=> 3888000 
(1.month + 15.days) #=> 3888000 
+0

(A) Начните с начала, обратившись на север. Поверните на 90 градусов, а затем перейдите на 100 м. (B) Начните с начала, повернув на север. Идите вперед 100 м, а затем поверните на 90 градусов. Как возможно, что вы получаете разные результаты? Не все в этом мире является коммутативным. – sawa

ответ

6

Сперва посмотрим, пожалуйста, Integer#month, он возвращает экземпляр ActiveSupport::Duration. На консоли рельсы:

~/rails/rfinan (1296000):1 > elapsed = 1.month 
=> 2592000 
~/rails/rfinan (1296000):1 > elapsed.value 
=> 2592000 
~/rails/rfinan (1296000):1 > elapsed.parts 
=> [[:months,1]] 
~/rails/rfinan (1296000):1 > elapsed.is_a? ActiveSupport::Duration 
=> true 

Это время для метода: ActiveSupport::Duration#+

~/rails/rfinan (1296000):1 > sum1 = 1.month + 15.days 
=> 3888000 
~/rails/rfinan (1296000):1 > sum2 = 15.days + 1.month 
=> 3888000 
~/rails/rfinan (1296000):1 > sum1.value 
=> 3888000 
~/rails/rfinan (1296000):1 > sum1.parts 
=> [[:months,1],[:days,15]] 
~/rails/rfinan (1296000):1 > sum2.value 
=> 3888000 
~/rails/rfinan (1296000):1 > sum2.parts 
=> [[:days,15],[:months,1]] 
~/rails/rfinan (1296000):1 > sum1 == sum2 
=> true 
~/rails/rfinan (1296000):1 > sum1.value == sum2.value 
=> true 
~/rails/rfinan (1296000):1 > sum1.parts == sum2.parts 
=> false 

Теперь Date#+, версия ActiveSupport.

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

Это означает, что: если я посылаю: + к экземпляру Date с экземпляром ActiveSupport :: Продолжительность в качестве параметра, он вызывает ActiveSupport::Duration#since, а последний вызывает ActiveSupport::Duration#sum, что впрыскивает над экземпляром даты и вызывает Date#advance на каждой из частей, например продолжительность:

def sum(sign, time = ::Time.current) #:nodoc: 
    parts.inject(time) do |t,(type,number)| 
     if t.acts_like?(:time) || t.acts_like?(:date) 
     if type == :seconds 
      t.since(sign * number) 
     else 
      t.advance(type => sign * number) 
     end 
     else 
     raise ::ArgumentError, "expected a time or date, got #{time.inspect}" 
     end 
    end 
    end 

Remmember sum1.parts = sum2.parts ?, сумма отправки заранее к экземпляру даты упорядоченным. Посмотрим, что означает Date#advance

def advance(options) 
    options = options.dup 
    d = self 
    d = d >> options.delete(:years) * 12 if options[:years] 
    d = d >> options.delete(:months)  if options[:months] 
    d = d + options.delete(:weeks) * 7 if options[:weeks] 
    d = d + options.delete(:days)  if options[:days] 
    d 
end 

Когда авансовый month: 1 Получать он вызывает Date#>> из STDLIB, что работа diferently из ActiveSupport :: Продолжительность # +. На irb:

~ (main) > Date.new(2014,10,31) >> 1 
=> #<Date: 2014-11-30 ((2456992j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 2 
=> #<Date: 2014-12-31 ((2457023j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 3 
=> #<Date: 2015-01-31 ((2457054j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 4 
=> #<Date: 2015-02-28 ((2457082j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 5 
=> #<Date: 2015-03-31 ((2457113j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 12 
=> #<Date: 2015-10-31 ((2457327j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 1200 
=> #<Date: 2114-10-31 ((2493486j,0s,0n),+0s,2299161j)> 
~ (main) > Date.new(2014,10,31) >> 12000 
=> #<Date: 3014-10-31 ((2822204j,0s,0n),+0s,2299161j)> 

Понятно, что дата # >> не добавляет дней, добавляет месяцы и сохраняет номер дня. если день недействителен для целевого месяца, он исправляет его. Добавление фиксированного количества месяцев не фиксирует количество добавленных дней, так как зависит от даты начала.

Теперь можно сказать, что Date # + не совпадает с ActiveSupport :: Duration # +, и мы знаем почему.

Anwer - это дата # +, вызываемая с помощью ActiveSupport :: Срок действия (например, длительность) не заботится о duration.value, он использует duration.parts, которые различаются в каждом случае.

+0

'ActiveSupport :: Длительность # parts' была ключом здесь. Я думал, что продолжительность - это всего лишь несколько секунд. Большое спасибо!:) –

5

Октябрь имеет 31 день, ноябрь нет. Это означает, что это немного зависит от того, как вы вычисляете 31-й + 1 месяц.

Для первого примера:

  • теперь + 1 месяц = ​​16 ноября
  • 16-ноября + 15 дней = 1-Dec

Для второго примера:

  • Теперь + 15 дней = 31-окт.
  • 31-окт. + 1 месяц = ​​30-нояб.
4

Октябрь имеет 31 дней. Когда вы добавляете 15 дней до 16 октября, вы получаете 31 октября. Добавление месяца ведет вас к той же дате в следующем месяце - 31 ноября, но нет 31 ноября, так что вам нужно до 30 ноября.

Если вместо этого вы добавляете месяц первый, который несет вам до 16 ноября Затем добавляя 15 дней проводит вас дек 01.

+0

Отличное имя для ответа на связанные с календарем вопросы (Julian Calendar) –

2

Когда вы делаете:

(15.days + 1.month) #=> 3888000 
(1.month + 15.days) #=> 3888000 

вы не действуете даты, вы действуете секунды (Rails Numeric < Object). Для того, чтобы доказать, давайте преобразуем его обратно в дни:

> 3888000/60/60/24 
=> 45 

45 = 30 + 15. Итак, мы знаем, что при работе секунд, или дней, компилятор интерпретирует 1.month как 30 дней по умолчанию при работе ЦИФРЫ. См Числовые ссылки: http://api.rubyonrails.org/classes/Numeric.html#method-i-seconds

Как вы можете видеть в приведенной выше ссылке, при работе с Даты ЦИФРЫ, рельсы вызывает advance(options) метод, который отвечает за выполнение правильных операций Дата.См заблаговременного определения на GitHub: https://github.com/rails/rails/blob/ffc273577a795bb41068bfc2a1bb575ec51a9712/activesupport/lib/active_support/core_ext/time/calculations.rb#L99

Кроме того, при работе с датой использования Time.now.utc.to_date + (1.month + 15.days) функция +() будет на самом деле называть advance(options) метод как это:

(Time.now.utc.to_date.advance(month:1)).advance(days:15) #fistCase 

при использовании Time.now.utc.to_date + (15.days + 1.month), что будет называться так:

(Time.now.utc.to_date.advance(days:15)).advance(month:1) #secondCase 

Итак, давайте тест #firstCase:

oct16 = Date.new(2014, 10, 16) 
> oct16 + (1.month + 15.days) 
=> Mon, 01 Dec 2014 
> (oct16.advance(months:1)).advance(days:15) 
=> Mon, 01 Dec 2014 

Заключение #firstCase, оно требует заранее (за месяц: 1), в результате ноябрь-16, то он вызывает .advance (дни: 15) на ноябрь-16 и идет в Dez-01

проверки Давайте #secondCase:

> oct16 + (15.days + 1.month) 
=> Sun, 30 Nov 2014 
> (oct16.advance(days:15)).advance(months:1) 
=> Sun, 30 Nov 2014 

заключение #secondCase, оно называет заранее (дни: 15), в результате чего в октябре-31, чем это требует заранее (месяца: 1) на последний результат, который даст нам 31 ноября, но подождите! Ноя-31 не существует! Поэтому переводчик достаточно умен, чтобы понять это, поскольку вы были в последний день месяца (октябрь-31), когда вы добавляете 1.month или advance (months: 1), вы просите его взять вас на последний день следующего месяца, в этом случае - 30 ноября.

Это соглашение.