Encuentre el número de meses entre dos fechas en Ruby on Rails

95

Tengo dos objetos Ruby on Rails DateTime. ¿Cómo encontrar el número de meses entre ellos? (Teniendo en cuenta que pueden pertenecer a diferentes años)

ave fénix
fuente
5
Fue cuestionado y respondido aquí: stackoverflow.com/questions/5887756/…
Tim Baas
Gracias por mencionarlo. Busqué pero no me había topado con él.
phoenixwizard
consulte este enlace stackoverflow.com/questions/1065320/…
sangeethkumar
Prefiero el enlace en números. Lo hice por el método referido por Massimiliano Peluso
phoenixwizard
Solo se lo di como referencia .. Gracias de todos modos ..
sangeethkumar

Respuestas:

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

más información en http://www.ruby-forum.com/topic/72120

Massimiliano Peluso
fuente
25
Esta solución no considera días. El número de meses entre el 28/4/2000 y el 1/5/2000 no es 1 mes, sino 0.
dgilperez
15
Necesito uno sin considerar los días, así que esto funciona para mí. +1
WattsInABox
1
Solo otra variante de esta gran respuesta:(12 * (date2.year - date1.year) + date2.month - date1.month).abs if date1 && date2
Stepan Zakharov
Se muestra un número incorrecto de meses si intentamos obtener meses entre 2 años, es decir, 2017-05-20 a 2018-05-20. La salida muestra 1 mes.
Desarrollador curioso
1
@CuriousDeveloper Muestra los meses correctos, es decir, 12
ts
40

Una respuesta más precisa consideraría días en la distancia.

Por ejemplo, si considera que la distancia del mes desde 28/4/2000y 1/5/2000es en 0lugar de 1, puede usar:

(date2.year - date1.year) * 12 + date2.month - date1.month - (date2.day >= date1.day ? 0 : 1)
dgilperez
fuente
1
por ejemplo, para calcular el número de veces que alguien ha recibido su salario, siempre el día 22 de los meses
Matthias
15

Dale una oportunidad a

((date2.to_time - date1.to_time)/1.month.second).to_i
knotito
fuente
irb>Time.at("2014-10-01".to_time - "2014-12-01".to_time).month => 10 (Renuncio al formateo ... no puedo entenderlo)
Toby Joiner
Incluso si usa el objeto Date, el comentario de @ toby-joiner seguirá siendo correcto. La razón es que <Octubre> - <Diciembre> es -2 meses, pero Time.at (-2months) .month da el equivalente mensual de -2 (es decir, -2% 12 # => 10). El método sugerido funcionará si los dos meses se suceden y están separados por menos de un año, pero fallará si los dos meses están en orden inverso (por ejemplo, octubre - diciembre) o si los dos meses tienen más de un año de diferencia.
elreimundo
2
Me gusta la idea detrás de esto, pero es incorrecta si el intervalo de fechas se está volviendo muy largo. En mi ejemplo Date.new(2014, 10, 31), Date.new(1997, 4, 30), recibí 213 en lugar de 210 meses. La razón de esto es que 1.month.secondses la cantidad de segundos en 30 días y no el promedio de segundos en un mes. Votar en contra para que otros se mantengan libres de errores.
Joe Eifert
1
esto no funcionará si elige un mes que no sea de 30 días
dima
2
este método no es muy preciso.
vagabond
9

Puede reformular la pregunta como "cuántos primeros días hay entre el comienzo de los meses de las fechas" y luego usar transformaciones de datos de estilo funcional:

(date1.beginning_of_month...date2.beginning_of_month).select { |date| date.day == 1 }.size
shock_one
fuente
Y para volver a la pregunta original, podría sumar los días (como lo está haciendo con el primero) y tomar el promedio de las sumas.
Joe Eifert
9

Suponiendo que ambos son fechas: ((date2 - date1).to_f / 365 * 12).round simple.

Andreykul
fuente
¡Muy buena solución! Limpio, simple e incluso funciona a lo largo de los años, es decir, el rango de meses entre febrero de 2018 y diciembre de 2017.
jeffdill2
1
Necesita tomar en cuenta horas, minutos y segundos ((date2 - date1).to_f / 60 / 60 / 24 / 365 * 12).round
scarver2
1
@ scarver2 El número que obtiene de su cálculo está muy lejos. Todo lo que estamos haciendo es tomar días y convertirlos en meses, no es súper preciso porque asume 30.4167 días por mes, pero está muy cerca y luego redondea de todos modos.
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
Ankit Varshney
fuente
2

Otra solución que encontré (construida a partir de una solución publicada aquí) es si desea que el resultado incluya fracciones de mes. Por ejemplo, la distancia es 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...
Zack
fuente
1
intente hacer una carrera redonda con piso si desea mostrar solo los meses realmente transcurridos
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
Uğur Yılmaz
fuente
1
¿Sería útil si pudiera ampliar esto? En general, las respuestas de SO incluyen una explicación sobre lo que hace el código y por qué funciona como una solución
entidad única
1

Necesitaba el número exacto de meses (incluidos los decimales) entre dos fechas y escribí el siguiente método para ello.

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

Si comparamos, digamos el 26 de septiembre con el 26 de septiembre (el mismo día), lo calculo como 1 día. Si no lo necesita, puede eliminar la primera línea del método:period_end = period_end + 1.day

Pasa las siguientes especificaciones:

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
mtrolle
fuente
por cierto, se basa en ActiveSupport de Rails
mtrolle
1

Aquí hay una variante de fuerza 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 debe ser siempre mayor que date1

viejo
fuente
0

Aquí hay otro método. Esto ayudará a calcular el número de meses enteros entre dos fechas.

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
Dhanush Balachandran
fuente