Encontre o número de meses entre duas datas em Ruby on Rails


95

Tenho dois objetos DateTime do Ruby on Rails. Como saber o número de meses entre eles? (Tendo em mente que eles podem pertencer a anos diferentes)


5
Foi questionado e respondido aqui: stackoverflow.com/questions/5887756/…
Tim Baas

Obrigado por apontar isso. Eu procurei, mas não tropecei nele.
phoenixwizard


Prefiro o link em números. Fiz pelo método referido por Massimiliano Peluso
phoenixwizard

Só dei para sua referência .. Obrigado de qualquer maneira ..
sangeethkumar

Respostas:


179
(date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)

mais informações em http://www.ruby-forum.com/topic/72120


25
Esta solução não considera dias. O número de meses entre 28/4/2000 e 1/5/2000 não é 1 mês, mas 0.
dgilperez

15
Eu preciso de um sem considerar os dias, então isso funciona para mim. +1
WattsInABox

1
Apenas outra variante para esta ótima resposta:(12 * (date2.year - date1.year) + date2.month - date1.month).abs if date1 && date2
Stepan Zakharov

Ele está mostrando o número incorreto de meses se tentarmos buscar meses entre 2 anos, ou seja, 20/05/2017 a 20/05/2018. A saída mostra 1 mês.
Curious Developer

1
@CuriousDeveloper Ele está mostrando os meses corretos, ou seja, 12
ts

40

Uma resposta mais precisa consideraria dias distantes.

Por exemplo, se você considerar que a distância do mês de 28/4/2000e 1/5/2000é em 0vez de 1, então você pode usar:

(date2.year - date1.year) * 12 + date2.month - date1.month - (date2.day >= date1.day ? 0 : 1)

1
por exemplo, para calcular o número de vezes que alguém recebeu seu salário, sempre no 22º dia dos meses
Matias

15

Experimente

((date2.to_time - date1.to_time)/1.month.second).to_i

irb>Time.at("2014-10-01".to_time - "2014-12-01".to_time).month => 10 (Desisto de formatação ... não consigo descobrir)
Toby Joiner

Mesmo se você usar o objeto Date, o comentário de @ toby-joiner ainda estará correto. O motivo é que <outubro> - <dezembro> é -2 meses, mas Time.at (-2 meses) .mês dá o mês equivalente a -2 (ou seja, -2% 12 # => 10). Seu método sugerido funcionará se os dois meses se seguirem e tiverem menos de um ano de intervalo, mas falhará se os dois meses estiverem na ordem inversa (por exemplo, outubro - dezembro) ou se os dois meses tiverem mais de um ano de intervalo.
elreimundo 09/01/2015

2
Gosto da ideia por trás disso, mas é incorreto se o intervalo de datas está ficando muito longo. No meu exemplo Date.new(2014, 10, 31), Date.new(1997, 4, 30), recebi 213 em vez de 210 meses. A razão para isso é que 1.month.secondsé o número de segundos em 30 dias e não a média de segundos em um mês. Downvoting para que outros fiquem livres de erros.
Joe Eifert

1
isso não funcionará se você escolher um mês que não seja 30 dias
dima

2
este método não é muito preciso.
vagabundo

9

Você pode reformular a pergunta como "quantos primeiros dias existem entre o início dos meses das datas" e, em seguida, usar transformações de dados de estilo funcional:

(date1.beginning_of_month...date2.beginning_of_month).select { |date| date.day == 1 }.size

E para voltar à pergunta original, você poderia somar os dias (como está fazendo com o primeiro) e tirar a média das somas.
Joe Eifert

9

Supondo que ambos sejam datas: ((date2 - date1).to_f / 365 * 12).round simples.


Solução muito boa! Limpo, simples e até mesmo funciona ao longo dos anos - ou seja, o intervalo de meses entre fevereiro de 2018 e dezembro de 2017.
jeffdill2

1
Precisa levar horas, minutos e segundos em consideração ((date2 - date1).to_f / 60 / 60 / 24 / 365 * 12).round
scarver2

1
@ scarver2 O número obtido com o seu cálculo está muito errado. Tudo o que estamos fazendo é pegar dias e converter em meses, não é superpreciso porque assume 30,4167 dias por mês, mas está bem próximo e depois arredonda assim mesmo.
Andreykul

3
start_date = Date.today
end_date   = Date.today+90
months = (end_date.month+end_date.year*12) - (start_date.month+start_date.year*12)

//months = 3

2

Outra solução que encontrei (construída a partir de uma solução já postada aqui) é se você quiser que o resultado inclua frações de um mês. Por exemplo, a distância é 1.2 months.

((date2.to_time - date1.to_time)/1.month.second).round(1) #Tenth of a month Ex: 1.2
((date2.to_time - date1.to_time)/1.month.second).round(2) #Hundreth, ex: 1.23 months
etc...

1
tente espalhar com o chão se quiser mostrar apenas os meses realmente decorridos
Matthias

2
def difference_in_months(date1, date2)
  month_count = (date2.year == date1.year) ? (date2.month - date1.month) : (12 - date1.month + date2.month)
  month_count = (date2.year == date1.year) ? (month_count + 1) : (((date2.year - date1.year - 1 ) * 12) + (month_count + 1))
  month_count
end

1
Seria útil se você pudesse entrar em detalhes? Geralmente, as respostas do SO incluem uma explicação sobre o que o código faz e por que funciona como uma solução
Entidade Única,

1

Eu precisava do número exato de meses (incluindo decimais) entre duas datas e escrevi o seguinte método para isso.

def months_difference(period_start, period_end)
  period_end = period_end + 1.day
  months = (period_end.year - period_start.year) * 12 + period_end.month - period_start.month - (period_end.day >= period_start.day ? 0 : 1)
  remains = period_end - (period_start + months.month)

  (months + remains/period_end.end_of_month.day).to_f.round(2)
end

Se comparar, digamos de 26 de setembro a 26 de setembro (mesmo dia), calculo como 1 dia. Se você não precisa disso, pode remover a primeira linha do método:period_end = period_end + 1.day

Ele passa nas seguintes especificações:

expect(months_difference(Date.new(2017, 8, 1), Date.new(2017, 8, 31))).to eq 1.0
expect(months_difference(Date.new(2017, 8, 1), Date.new(2017, 8, 30))).to eq 0.97
expect(months_difference(Date.new(2017, 8, 1), Date.new(2017, 10, 31))).to eq 3.0
# Overlapping february (28 days) still counts Feb as a full month
expect(months_difference(Date.new(2017, 1, 1), Date.new(2017, 3, 31))).to eq 3.0
expect(months_difference(Date.new(2017, 2, 10), Date.new(2017, 3, 9))).to eq 1.0
# Leap year
expect(months_difference(Date.new(2016, 2, 1), Date.new(2016, 2, 29))).to eq 1.0

btw ele depende do ActiveSupport de Rails
mtrolle

1

Aqui está uma variante de força bruta:

date1 = '2016-01-05'.to_date
date2 = '2017-02-27'.to_date
months = 0

months += 1 while (date2 << (count+1)) >= date1
puts months # => 13

date2 deve ser sempre maior que date1


0

Aqui está outro método. Isso ajudará a calcular o número de meses inteiros entre duas datas

def months_difference(date_time_start, date_time_end)
  curr_months = 0
  while (date_time_start + curr_months.months) < date_time_end
    curr_months += 1
  end
  curr_months -= 1 if (date_time_start + curr_months.months) > date_time_end
  curr_months.negative? ? 0 : curr_months
end
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.