Obtener la edad de la persona en Ruby

125

Me gustaría saber la edad de una persona desde su cumpleaños. now - birthday / 365no funciona, porque algunos años tienen 366 días. Se me ocurrió el siguiente código:

now = Date.today
year = now.year - birth_date.year

if (date+year.year) > now
  year = year - 1
end

¿Hay alguna manera más rubí de calcular la edad?

Mantas
fuente
66
Me gusta esta pregunta porque resalta la idea de que hay "más Ruby" y "menos Ruby" para hacer las cosas. Es importante no solo ser lógicamente correcto (que podría ser copiando la respuesta de C #), sino también estilísticamente correcto. Y la respuesta de Adinochestva hace buen uso del lenguaje de Ruby.
James A. Rosen

Respuestas:

410

Sé que llego tarde a la fiesta aquí, pero la respuesta aceptada se romperá horriblemente al tratar de determinar la edad de alguien nacido el 29 de febrero en un año bisiesto. Esto se debe a que la llamada a birthday.to_date.change(:year => now.year)crea una fecha no válida.

Usé el siguiente código en su lugar:

require 'date'

def age(dob)
  now = Time.now.utc.to_date
  now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
philnash
fuente
44
Use esto, no el marcado con marca que no puede manejar los años bisiestos
bgcode
¿Por qué devuelves 0 || 1 en lugar de true|| false?
0112
1
@ alex0112 Porque el resultado (0 o 1) de ese condicional ciertamente confuso se resta de la diferencia en años entre ahora y la fecha de nacimiento. Se pretende averiguar si la persona ya cumplió años este año y, de lo contrario, tiene 1 año menos que la diferencia entre años.
philnash
2
@andrej now = Date.today funciona pero tenga en cuenta que no maneja los problemas con Timezone. En Rails Date.today devuelve una fecha basada en la zona horaria del sistema. ActiveRecord devuelve una hora basada en la zona horaria configurada de su aplicación. Si la zona horaria del sistema es diferente de la zona horaria de su aplicación, estaría comparando efectivamente el tiempo de dos zonas horarias diferentes que no serían muy precisas.
Favorito Onwuemene
¿Es esta realmente la solución más simple?
Marco Prins
50

He encontrado que esta solución funciona bien y es legible para otras personas:

    age = Date.today.year - birthday.year
    age -= 1 if Date.today < birthday + age.years #for days before birthday

Fácil y no necesita preocuparse por manejar el año bisiesto y demás.

PJ.
fuente
3
Esto requiere Rails (por edad, años), pero se podría hacer que no requiera Rails si hiciste algo así Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck
Tienes razón, lo siento, asumí Rails ya que la pregunta estaba etiquetada. Pero sí, se modifica fácilmente solo para Ruby.
PJ.
1
Al principio había elegido este porque es el más bonito, pero en producción, con frecuencia está mal, por razones que no entiendo. El anterior usando Time.now.utc.to_date parece estar funcionando mejor.
Kevin
@Kevin Interesante. Nunca tuve un problema con esto, pero eso no significa que no haya ninguno. ¿Me podría dar un ejemplo específico? Me gustaría saber si hay un error. Gracias.
PJ.
2
@sigvei: esa es una característica, no un error;) En la mayoría de los países, incluido EE. UU., el día 28 se considera legalmente su cumpleaños en un año no bisiesto si es un bebé bisiesto. La persona de hecho sería considerada 10.
PJ.
33

Utilizar este:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end
Sadegh
fuente
41
Esto se rompe si birthday.to_date es un año bisiesto y el año actual no lo es. No es un gran acontecimiento, pero me ha estado causando problemas.
philnash
1
Votación negativa para alentar la respuesta de philnash.
Nick Sonneveld
2
Otra razón para preferir la respuesta de philnash es que funciona con el viejo Ruby, mientras que la respuesta aceptada solo funciona rails/activesupport.
sheldonh
16

Un trazador de líneas en Ruby on Rails (ActiveSupport). Maneja años bisiestos, segundos bisiestos y todo.

def age(birthday)
  (Time.now.to_s(:number).to_i - birthday.to_time.to_s(:number).to_i)/10e9.to_i
end

Lógica desde aquí: calcular la edad en C #

Asumiendo que ambas fechas están en la misma zona horaria, si no, llame utc()antes to_s()en ambas.

Vikrant Chaudhary
fuente
1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_itambién funciona
Grant Hutchins
FWIW, pero luego se invalidará mi garantía de "segundos bisiestos". ;-) (FWIW parte 2, Ruby no admite "segundos intercalares" de todos modos). :-)
Vikrant Chaudhary
1
No estoy seguro de por qué estoy recibiendo votos negativos sobre esto. ¿Te importaría explicar, queridos downvoters?
Vikrant Chaudhary
@vikrantChaudhary No lo sé, es una gran respuesta. Probado y funciona.
Héctor Ordóñez
9
(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000
pguardiario
fuente
Entonces, esto esencialmente calcula la diferencia en días si cada mes duraba 100 días y cada año duraba 100 meses. Lo que no hace la diferencia si solo mantiene la parte del año.
Jonathan Allard
6

Las respuestas hasta ahora son un poco raras. Su intento original estuvo muy cerca de la forma correcta de hacer esto:

birthday = DateTime.new(1900, 1, 1)
age = (DateTime.now - birthday) / 365.25 # or (1.year / 1.day)

Obtendrá un resultado fraccionario, así que siéntase libre de convertir el resultado a un entero con to_i. Esta es una mejor solución porque trata correctamente la diferencia de fecha como un período de tiempo medido en días (o segundos en el caso de la clase de tiempo relacionada) desde el evento. Luego, una división simple por la cantidad de días en un año te da la edad. Al calcular la edad en años de esta manera, siempre que conserve el valor DOB original, no es necesario tener en cuenta los años bisiestos.

Bob Aman
fuente
cumpleaños = Time.mktime (1960,5,5) me da fuera de rango (¿problemas de época?)
Andrew Grimm
Sí, ve a los problemas de época. He actualizado la respuesta para resolver esto.
Bob Aman
birthday = DateTime.now - 1.yearme da una edad de 0. Desafortunadamente, dividir entre 365.25 es un poco impreciso.
Samir Talwar
No puede restar 1.año así de un objeto DateTime. 1. año resuelve el número de segundos en un año. Los objetos DateTime operan en función de los días. Por ejemplo: (DateTime.now - 365.25) .strftime ("% D") En cuanto a la precisión, si realmente solo se trata de cumpleaños, es bastante preciso. El hecho es que la gente ya es bastante imprecisa cuando se trata de edades. Nacemos en un momento preciso en el tiempo, pero generalmente no damos la hora, minuto y segundo exactos de nuestro nacimiento cuando anotamos nuestro DOB. Mi argumento aquí es que realmente no quieres hacer este cálculo manualmente.
Bob Aman
1
Esto no funciona para las personas nacidas antes de 1900. Por ejemplo, se informa que Gertrude Baines tenía una edad de 114.9979 en su cumpleaños en 2009.
Andrew Grimm
6

Mi sugerencia:

def age(birthday)
    ((Time.now - birthday.to_time)/(60*60*24*365)).floor
end

El truco es que la operación menos con Tiempo devuelve segundos

jlebrijo
fuente
Esto es casi correcto. Los años bisiestos significan que un año es en realidad 365,25 días. Eso también significa que, en el mejor de los casos, este método podría no aumentar su edad hasta las 18 horas de su cumpleaños.
Ryan Lue
5

Me gusta este:

now = Date.current
age = now.year - dob.year
age -= 1 if now.yday < dob.yday
tpalm
fuente
Si cree que este es un contendiente razonable a una pregunta de 3 años que ya tiene otras 10 respuestas, debe incluir más razones que preferencias personales. De lo contrario, no recibirá mucha atención
John Dvorak
1
esto se rompe cuando un año es bisiesto y otro no lo es
artm
Si el último año de febrero tiene 29 días, este cálculo fallará
phil88530
5

Esta respuesta es la mejor, vota en su lugar.


Me gusta la solución de @ philnash, pero el condicional podría ser más compacto. Lo que hace esa expresión booleana es comparar pares [mes, día] usando el orden lexicográfico , por lo que uno podría usar la comparación de cadenas de rubí en su lugar:

def age(dob)
  now = Date.today
  now.year - dob.year - (now.strftime('%m%d') < dob.strftime('%m%d') ? 1 : 0)
end
artm
fuente
1
¿Qué hay de (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
Ryan Lue
wow, eso es mucho más ordenado. ¡deberías responder y recoger las recompensas!
artm
hmm, en realidad veo había esta respuesta ya incluso antes que la mía
ARTM
Oh, ahora yo también ... -_- '
Ryan Lue
4

Esta es una conversión de esta respuesta (ha recibido muchos votos):

# convert dates to yyyymmdd format
today = (Date.current.year * 100 + Date.current.month) * 100 + Date.today.day
dob = (dob.year * 100 + dob.month) * 100 + dob.day
# NOTE: could also use `.strftime('%Y%m%d').to_i`

# convert to age in years
years_old = (today - dob) / 10000

Definitivamente es único en su enfoque, pero tiene mucho sentido cuando te das cuenta de lo que hace:

today = 20140702 # 2 July 2014

# person born this time last year is a 1 year old
years = (today - 20130702) / 10000

# person born a year ago tomorrow is still only 0 years old
years = (today - 20130703) / 10000

# person born today is 0
years = (today - 20140702) / 10000  # person born today is 0 years old

# person born in a leap year (eg. 1984) comparing with non-leap year
years = (20140228 - 19840229) / 10000 # 29 - a full year hasn't yet elapsed even though some leap year babies think it has, technically this is the last day of the previous year
years = (20140301 - 19840229) / 10000 # 30

# person born in a leap year (eg. 1984) comparing with leap year (eg. 2016)
years = (20160229 - 19840229) / 10000 # 32
br3nt
fuente
Sólo se dio cuenta de esto es el mismo Anser
br3nt
1

Debido a que Ruby on Rails está etiquetado, la gema dotiw anula la distancia incorporada de Rails distance_of_times_in_words y proporciona distance_of_times_in_words_hash que se puede usar para determinar la edad. Los años bisiestos se manejan bien para la parte de años, aunque tenga en cuenta que el 29 de febrero tiene un efecto en la parte de días que garantiza la comprensión si se necesita ese nivel de detalle. Además, si no le gusta cómo dotiw cambia el formato de distance_of_time_in_words, use la opción: vague para volver al formato original.

Agregue dotiw al Gemfile:

gem 'dotiw'

En la línea de comando:

bundle

Incluya el DateHelper en el modelo apropiado para obtener acceso a distance_of_time_in_words y distance_of_time_in_words_hash. En este ejemplo, el modelo es 'Usuario' y el campo de cumpleaños es 'cumpleaños'.

class User < ActiveRecord::Base
  include ActionView::Helpers::DateHelper

Agregue este método a ese mismo modelo.

def age
  return nil if self.birthday.nil?
  date_today = Date.today
  age = distance_of_time_in_words_hash(date_today, self.birthday).fetch("years", 0)
  age *= -1 if self.birthday > date_today
  return age
end

Uso:

u = User.new("birthday(1i)" => "2011", "birthday(2i)" => "10", "birthday(3i)" => "23")
u.age
mindriot
fuente
1

Creo que esto es funcionalmente equivalente a la respuesta de @ philnash, pero la OMI es más fácil de entender.

class BirthDate
  def initialize(birth_date)
    @birth_date = birth_date
    @now = Time.now.utc.to_date
  end

  def time_ago_in_years
    if today_is_before_birthday_in_same_year?
      age_based_on_years - 1
    else
      age_based_on_years
    end
  end

  private

  def age_based_on_years
    @now.year - @birth_date.year
  end

  def today_is_before_birthday_in_same_year?
    (@now.month < @birth_date.month) || ((@now.month == @birth_date.month) && (@now.day < @birth_date.day))
  end
end

Uso:

> BirthDate.new(Date.parse('1988-02-29')).time_ago_in_years
 => 31 
Jason Swett
fuente
0

Lo siguiente parece funcionar (pero agradecería si estuviera marcado).

age = now.year - bday.year
age -= 1 if now.to_a[7] < bday.to_a[7]
testr
fuente
0

Si no te importa un día o dos, esto sería más corto y bastante claro.

(Time.now - Time.gm(1986, 1, 27).to_i).year - 1970
hakunin
fuente
0

Ok, ¿qué pasa con esto?

def age
  return unless dob
  t = Date.today
  age = t.year - dob.year
  b4bday = t.strftime('%m%d') < dob.strftime('%m%d')
  age - (b4bday ? 1 : 0)
end

Esto supone que estamos usando rieles, llamando al agemétodo en un modelo, y el modelo tiene una columna de base de datos de fechas dob. Esto es diferente de otras respuestas porque este método usa cadenas para determinar si estamos antes del cumpleaños de este año.

Por ejemplo, si dobes 2004/2/28 y todayes 2014/2/28, ageserá 2014 - 2004o 10. Las carrozas serán 0228y 0229. b4bdayserá "0228" < "0229"o true. Por último, vamos a restar 1de agey obtener 9.

Esta sería la forma normal de comparar las dos veces.

def age
  return unless dob
  t = Date.today
  age = today.year - dob.year
  b4bday = Date.new(2016, t.month, t.day) < Date.new(2016, dob.month, dob.day)
  age - (b4bday ? 1 : 0)
end

Esto funciona igual, pero la b4bdaylínea es demasiado larga. El 2016año también es innecesario. La comparación de cadenas al principio fue el resultado.

También puedes hacer esto

Date::DATE_FORMATS[:md] = '%m%d'

def age
  return unless dob
  t = Date.today
  age = t.year - dob.year
  b4bday = t.to_s(:md) < dob.to_s(:md)
  age - (b4bday ? 1 : 0)
end

Si no estás usando rieles, prueba esto

def age(dob)
  t = Time.now
  age = t.year - dob.year
  b4bday = t.strftime('%m%d') < dob.strftime('%m%d')
  age - (b4bday ? 1 : 0)
end

👍🏼

Cruz Nunez
fuente
Solo para tu información, tu respuesta es lógicamente la misma que la de philnash. Sin embargo, su respuesta es mucho más limpia y no depende de Rails.
Mantas
@Mantas Philnash dijo que usó ese método en un proyecto de Rails. Tenías rieles en las etiquetas. Su método tiene dos comparaciones más que la mía. La última línea de su método es difícil de entender.
Cruz Núñez
lo siento, pero el código de philnash es mucho más limpio que el tuyo. Además, su código es un método simple. El tuyo depende de tener un valor "dob". Lo que puede no ser tan claro para los nuevos usuarios. Y ni siquiera ayuda mucho al código. Sí, su última muestra se deshace de ella. Pero philnash es simplemente un código perfecto para mantenerlo simple y estúpido.
Mantas
0

Creo que es mucho mejor no contar meses, porque puedes obtener el día exacto de un año usando Time.zone.now.yday.

def age
  years  = Time.zone.now.year - birthday.year
  y_days = Time.zone.now.yday - birthday.yday

  y_days < 0 ? years - 1 : years
end
Oleksiy Nosov
fuente
0

Se le ocurrió una variación de Rails de esta solución

def age(dob)
    now = Date.today
    age = now.year - dob.year
    age -= 1 if dob > now.years_ago(age)
    age
end
derosm2
fuente
0

DateHelper puede usarse solo para obtener años

puts time_ago_in_words '1999-08-22'

casi 20 años

hsul4n
fuente
0
  def computed_age
    if birth_date.present?
      current_time.year - birth_date.year - (age_by_bday || check_if_newborn ? 0 : 1)
    else
      age.presence || 0
    end
  end


  private

  def current_time
    Time.now.utc.to_date
  end

  def age_by_bday
    current_time.month > birth_date.month
  end

  def check_if_newborn
    (current_time.month == birth_date.month && current_time.day >= birth_date.day)
  end```
Sandesh Bodake
fuente
-1
  def birthday(user)
    today = Date.today
    new = user.birthday.to_date.change(:year => today.year)
    user = user.birthday
    if Date.civil_to_jd(today.year, today.month, today.day) >= Date.civil_to_jd(new.year, new.month, new.day)
      age = today.year - user.year
    else
      age = (today.year - user.year) -1
    end
    age
  end
Brian
fuente
-1
Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)
Tim Van Ginderen
fuente
-1

Para contabilizar los años bisiestos (y suponiendo una presencia activa de apoyo):

def age
  return unless birthday
  now = Time.now.utc.to_date
  years = now.year - birthday.year
  years - (birthday.years_since(years) > now ? 1 : 0)
end

years_sincemodificará correctamente la fecha para tener en cuenta los años no bisiestos (cuando es el cumpleaños 02-29).

Vitaly Kushner
fuente
-1

Aquí está mi solución que también permite calcular la edad en una fecha específica:

def age on = Date.today
  (_ = on.year - birthday.year) - (on < birthday.since(_.years) ? 1 : 0)
end
hurikhan77
fuente
-2

Tuve que lidiar con esto también, pero durante meses. Se volvió demasiado complicado. La forma más simple en que podía pensar era:

def month_number(today = Date.today)
  n = 0
  while (dob >> n+1) <= today
    n += 1
  end
  n
end

Podrías hacer lo mismo con 12 meses:

def age(today = Date.today)
  n = 0
  while (dob >> n+12) <= today
    n += 1
  end
  n
end

Esto usará la clase Fecha para incrementar el mes, que tratará con 28 días y año bisiesto, etc.

Mooktakim Ahmed
fuente