Obtenir l'âge de la personne en rubis


125

J'aimerais connaître l'âge d'une personne à partir de son anniversaire. now - birthday / 365ne fonctionne pas, car certaines années ont 366 jours. J'ai trouvé le code suivant:

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

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

Existe-t-il une manière plus rubis de calculer l'âge?


6
J'aime cette question car elle met en évidence l'idée qu'il existe des façons de faire "plus de rubis" et "moins de rubis". Il est important non seulement d'être logiquement correct (ce que vous pourriez être en copiant la réponse C #), mais aussi stylistiquement correct. Et la réponse d'Adinochestva fait bon usage de l'idiome Ruby.
James A. Rosen

Réponses:


410

Je sais que je suis en retard à la fête ici, mais la réponse acceptée se brisera horriblement en essayant de déterminer l'âge d'une personne née le 29 février lors d'une année bissextile. C'est parce que l'appel à birthday.to_date.change(:year => now.year)crée une date non valide.

J'ai utilisé le code suivant à la place:

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

4
Utilisez ceci, pas celui
coché

Pourquoi renvoyez-vous 0 || 1 au lieu de true|| false?
0112

1
@ alex0112 Parce que le résultat (0 ou 1) de ce conditionnel, certes déroutant, est soustrait de la différence en années entre maintenant et la date de naissance. Il s'agit de savoir si la personne a déjà fêté son anniversaire cette année et sinon, elle a 1 an de moins que la différence entre les années.
philnash

2
@andrej now = Date.today fonctionne mais notez qu'il ne gère pas les problèmes avec le fuseau horaire. Dans Rails Date.today renvoie une date basée sur le fuseau horaire du système. ActiveRecord renvoie une heure basée sur le fuseau horaire configuré par vos applications. Si le fuseau horaire du système est différent de celui de votre application, vous compareriez effectivement l'heure de deux fuseaux horaires différents, ce qui ne serait pas très précis.
Favori Onwuemene

Est-ce vraiment la solution la plus simple?
Marco Prins

50

J'ai trouvé que cette solution fonctionnait bien et était lisible pour d'autres personnes:

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

Facile et vous n'avez pas à vous soucier de la gestion des années bissextiles et autres.


3
Cela nécessite Rails (pour age.years), mais pourrait ne pas nécessiter Rails si vous faisiez quelque chose comme Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck

Vous avez raison - désolé, j'ai supposé Rails car la question était taguée avec. Mais oui, facilement modifié pour Ruby uniquement.
PJ.

1
J'avais choisi celui-ci au début car c'est le plus joli, mais en production, c'est souvent faux, pour des raisons que je ne comprends pas. Celui ci-dessus utilisant Time.now.utc.to_date semble fonctionner mieux.
Kevin

@Kevin Intéressant. Je n'ai jamais eu de problème avec ça moi-même, mais cela ne veut pas dire qu'il n'y en a pas. Pouvez-vous me donner un exemple précis? J'aimerais savoir s'il y a un bug. Merci.
PJ.

2
@sigvei - c'est une fonctionnalité, pas un bug;) Dans la plupart des pays, y compris les États-Unis, le 28 est légalement considéré comme votre anniversaire dans une année non bissextile si vous êtes un bébé bissextile. La personne serait en effet considérée comme 10.
PJ.

33

Utilisez ceci:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end

41
Cela s'arrête si birthday.to_date est une année bissextile et que l'année en cours ne l'est pas. Ce n'est pas un gros événement, mais cela me pose des problèmes.
philnash

1
Downvoter pour encourager la réponse de philnash.
Nick Sonneveld

2
Une autre raison de préférer la réponse de philnash est qu'elle fonctionne avec le vieux Ruby, alors que la réponse acceptée ne fonctionne qu'avec rails/activesupport.
sheldonh

16

Une doublure en Ruby on Rails (ActiveSupport). Gère les années bissextiles, les secondes bissextiles et tout.

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

Logique à partir d'ici - Calculer l'âge en C #

En supposant que les deux dates sont dans le même fuseau horaire, sinon appeler utc()avant to_s()les deux.


1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_ifonctionne aussi
Grant Hutchins

FWIW, mais ma garantie sur les «secondes intercalaires» sera invalidée. ;-) (FWIW partie 2, Ruby ne supporte pas les "secondes intercalaires" de toute façon). :-)
Vikrant Chaudhary

1
Je ne sais pas pourquoi je reçois des votes négatifs à ce sujet. Voulez-vous expliquer, chers downvoters?
Vikrant Chaudhary

@vikrantChaudhary Je ne sais pas, c'est une excellente réponse. Testé et fonctionne.
Hector Ordonez

9
(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000

Donc, cela calcule essentiellement la différence en jours si chaque mois était de 100 jours et chaque année de 100 mois. Ce qui ne fait aucune différence si vous ne gardez que la partie année.
Jonathan Allard

6

Les réponses jusqu'à présent sont un peu bizarres. Votre tentative initiale était assez proche de la bonne façon de procéder:

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

Vous obtiendrez un résultat fractionnaire, alors n'hésitez pas à convertir le résultat en entier avec to_i. C'est une meilleure solution car elle traite correctement la différence de date comme une période de temps mesurée en jours (ou en secondes dans le cas de la classe Time associée) depuis l'événement. Ensuite, une simple division par le nombre de jours dans une année vous donne l'âge. Lors du calcul de l'âge en années de cette façon, tant que vous conservez la valeur DOB d'origine, aucune allocation n'est nécessaire pour les années bissextiles.


anniversaire = Time.mktime (1960,5,5) me donne hors de portée (problèmes d'époque?)
Andrew Grimm

Ouais, allez les problèmes d'époque. J'ai mis à jour la réponse pour résoudre ce problème.
Bob Aman

birthday = DateTime.now - 1.yearme donne un âge de 0. Malheureusement, diviser par 365,25 est un peu imprécis.
Samir Talwar

Vous ne pouvez pas soustraire 1 an comme ça à un objet DateTime. 1.année correspond au nombre de secondes par an. Les objets DateTime fonctionnent en fonction des jours. Par exemple: (DateTime.now - 365.25) .strftime ("% D") En ce qui concerne la précision, si vous ne traitez que des anniversaires, c'est très précis. Le fait est que les gens sont déjà assez imprécis en ce qui concerne les âges. Nous sommes nés à un moment précis dans le temps, mais nous ne donnons généralement pas l'heure, la minute et la seconde exactes de notre naissance lorsque nous notons notre date de naissance. Mon argument ici est que vous ne voulez vraiment pas faire ce calcul manuellement.
Bob Aman

1
Cela ne fonctionne pas pour les personnes nées avant 1900. Par exemple, Gertrude Baines aurait un âge de 114,9979 le jour de son anniversaire en 2009.
Andrew Grimm

6

Ma suggestion:

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

L'astuce est que l'opération moins avec Time renvoie des secondes


C'est presque vrai. Les années bissextiles signifient qu'une année dure en fait 365,25 jours. Cela signifie également qu'au mieux, cette méthode pourrait ne pas augmenter votre âge avant 18 heures après votre anniversaire.
Ryan Lue

5

J'aime celui la:

now = Date.current
age = now.year - dob.year
age -= 1 if now.yday < dob.yday

Si vous pensez qu'il s'agit d'un candidat raisonnable à une question de 3 ans qui a déjà 10 autres réponses, vous devez inclure plus de raisons que de préférence personnelle. Sinon, vous n'obtiendrez pas beaucoup d'attention
John Dvorak

1
cela se brise lorsqu'une année est une année bissextile et une autre ne l'est pas
artm

Si la dernière année de février a 29 jours, ce calcul échouera
phil88530

5

Cette réponse est la meilleure, votez-la plutôt.


J'aime la solution de @ philnash, mais le conditionnel pourrait être plus compact. Cette expression booléenne compare les paires [mois, jour] en utilisant l' ordre lexicographique , donc on pourrait simplement utiliser la comparaison de chaînes de ruby ​​à la place:

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

1
Et quoi (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
Ryan Lue

wow, c'est tellement plus soigné. vous devriez faire une réponse et collecter les récompenses!
artm

hmm, en fait, je vois qu'il y avait déjà cette réponse avant la mienne
artm

Oh, maintenant je le fais aussi ... -_- '
Ryan Lue

4

Ceci est une conversion de cette réponse (elle a reçu beaucoup de votes):

# 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

C'est vraiment unique dans son approche, mais cela prend tout son sens lorsque vous réalisez ce qu'il fait:

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

Il suffit de réaliser c'est le même Anser
br3nt

1

Comme Ruby on Rails est balisé, le gem dotiw remplace le paramètre distance_of_times_in_words intégré à Rails et fournit distance_of_times_in_words_hash qui peut être utilisé pour déterminer l'âge. Les années bissextiles sont traitées correctement pour la partie des années, mais sachez que le 29 février a un effet sur la partie des jours qui justifie de comprendre si ce niveau de détail est nécessaire. De plus, si vous n'aimez pas la façon dont dotiw modifie le format de distance_of_time_in_words, utilisez l'option: vague pour revenir au format d'origine.

Ajoutez dotiw au Gemfile:

gem 'dotiw'

Sur la ligne de commande:

bundle

Incluez le DateHelper dans le modèle approprié pour accéder à distance_of_time_in_words et distance_of_time_in_words_hash. Dans cet exemple, le modèle est «Utilisateur» et le champ anniversaire est «anniversaire».

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

Ajoutez cette méthode à ce même modèle.

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

Usage:

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

1

Je pense que c'est fonctionnellement équivalent à la réponse de @ philnash, mais l'OMI est plus facilement compréhensible.

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

Usage:

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

0

Ce qui suit semble fonctionner (mais je l'apprécierais s'il était vérifié).

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

0

Si vous ne vous souciez pas d'un jour ou deux, ce serait plus court et assez explicite.

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

0

Ok et ça:

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

Cela suppose que nous utilisons des rails, appelons la ageméthode sur un modèle, et que le modèle a une colonne de base de données de dates dob. Ceci est différent des autres réponses car cette méthode utilise des chaînes pour déterminer si nous sommes avant l'anniversaire de cette année.

Par exemple, si dobest 2004/2/28 et today2014/2/28, agesera 2014 - 2004ou 10. Les flotteurs seront 0228et 0229. b4bdaysera "0228" < "0229"ou true. Enfin, nous déduisons 1de ageet obtenir 9.

Ce serait la manière normale de comparer les deux temps.

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

Cela fonctionne de la même manière, mais la b4bdayligne est trop longue. L' 2016année est également inutile. La comparaison de chaînes au début était le résultat.

Vous pouvez également le faire

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 vous n'utilisez pas de rails, essayez ceci

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

👍🏼


Juste pour info, votre réponse est logiquement la même que celle de philnash. Sa réponse est beaucoup plus claire et ne repose pas sur les rails.
Mantas

@Mantas Philnash a déclaré qu'il avait utilisé cette méthode dans un projet Rails. Vous aviez des rails dans les balises. Sa méthode a deux comparaisons de plus que la mienne. La dernière ligne de sa méthode est difficile à comprendre.
Cruz Nunez

désolé, mais le code de philnash est beaucoup plus propre que le vôtre. De plus, son code est une méthode simple. Les vôtres dépendent de la valeur "dob". Ce qui n'est peut-être pas si clair pour les nouveaux utilisateurs. Et n'aide même pas beaucoup le code. Oui, votre tout dernier échantillon s'en débarrasse. Mais philnash est juste un morceau de code parfait, simple et stupide.
Mantas

0

Je pense qu'il est préférable de ne pas compter les mois, car vous pouvez obtenir le jour exact de l'année en utilisant 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

0

Est venu avec une variante Rails de cette solution

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

0

DateHelper peut être utilisé pour obtenir des années uniquement

puts time_ago_in_words '1999-08-22'

presque 20 ans


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```

-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

-1
Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)

-1

Pour tenir compte des années bissextiles (et en supposant une présence active de soutien):

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_sincemodifiera correctement la date pour prendre en compte les années non bissextiles (date de l'anniversaire 02-29).


-1

Voici ma solution qui permet également de calculer l'âge à une date précise:

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

-2

J'ai dû faire face à ça aussi, mais pendant des mois. Est devenu beaucoup trop compliqué. Le moyen le plus simple auquel je pouvais penser était:

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

Vous pourriez faire la même chose avec 12 mois:

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

Cela utilisera la classe Date pour incrémenter le mois, ce qui traitera 28 jours et une année bissextile, etc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.