Ottieni l'età della persona in Ruby


125

Vorrei avere l'età di una persona dal suo compleanno. now - birthday / 365non funziona, perché alcuni anni hanno 366 giorni. Mi è venuto in mente il seguente codice:

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

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

Esiste un modo più rubino per calcolare l'età?


6
Mi piace questa domanda perché evidenzia l'idea che ci sono modi "più rubini" e "meno rubini" di fare le cose. È importante non solo essere logicamente corretti (come si potrebbe essere copiando la risposta C #), ma anche stilisticamente corretti. E la risposta di Adinochestva fa buon uso del linguaggio di Ruby.
James A. Rosen,

Risposte:


410

So di essere in ritardo alla festa qui, ma la risposta accettata si interromperà in modo orribile quando si cerca di capire l'età di una persona nata il 29 febbraio in un anno bisestile. Questo perché la chiamata a birthday.to_date.change(:year => now.year)crea una data non valida.

Ho usato invece il seguente codice:

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
Usa questo, non quello contrassegnato da un segno di spunta che non può gestire gli anni bisestili
bgcode

Perché restituisci 0 || 1 anziché true|| false?
0112

1
@ alex0112 Perché il risultato (0 o 1) di quel condizionale certamente confuso viene sottratto dalla differenza di anni tra oggi e la data di nascita. Ha lo scopo di scoprire se la persona ha avuto il suo compleanno ancora quest'anno e, in caso contrario, ha 1 anno in meno rispetto alla differenza tra gli anni.
Philnash,

2
@andrej now = Date.today funziona ma nota che non gestisce i problemi con Timezone. In Rails Date.today restituisce una data basata sul fuso orario del sistema. ActiveRecord restituisce un orario in base al fuso orario configurato nelle app. Se il fuso orario del sistema è diverso dal fuso orario dell'applicazione, si confronterebbe effettivamente il tempo rispetto a due fusi orari diversi che non sarebbero molto precisi.
Preferito Onwuemene il

Questa è davvero la soluzione più semplice?
Marco Prins,

50

Ho trovato questa soluzione per funzionare bene ed essere leggibile per altre persone:

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

Facile e non devi preoccuparti di gestire l'anno bisestile e così via.


3
Ciò richiede Rails (per age.years), ma potrebbe essere fatto per non richiedere Rails se hai fatto qualcosa del genere Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck,

Hai ragione, mi spiace di aver assunto Rails perché la domanda era stata taggata. Ma sì, facilmente modificabile solo per Ruby.
PJ.

1
All'inizio l'avevo scelto perché era il più bello, ma in produzione è spesso sbagliato, per ragioni che non capisco. Quello sopra usando Time.now.utc.to_date sembra funzionare meglio.
Kevin,

@Kevin Interessante. Non ho mai avuto problemi con questo, ma ciò non significa che non ce ne sia uno. Potresti darmi un esempio specifico? Vorrei sapere se c'è un bug. Grazie.
PJ.

2
@sigvei - questa è una caratteristica, non un bug;) Nella maggior parte dei paesi, compresi gli Stati Uniti, il 28 ° anno è legalmente considerato il tuo compleanno in un anno non bisestile se sei un bambino bisestile. La persona sarebbe effettivamente considerata 10.
PJ.

33

Usa questo:

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

41
Questo si interrompe se birthday.to_date è un anno bisestile e l'anno in corso no. Non è un grosso evento, ma mi sta causando problemi.
Philnash,

1
Downvoting per incoraggiare la risposta di Philnash.
Nick Sonneveld,

2
Un altro motivo per preferire la risposta di Philnash è che funziona con il semplice vecchio Ruby, mentre la risposta accettata funziona solo con rails/activesupport.
sheldonh,

16

Una fodera in Ruby on Rails (ActiveSupport). Gestisce anni bisestili, secondi saltati e tutto il resto.

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

Logica da qui - Calcola l'età in C #

Supponendo che entrambe le date siano nello stesso fuso orario, se non si chiamano utc()prima to_s()su entrambi.


1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_ifunziona anche
Grant Hutchins il

FWIW, ma la mia garanzia sui "secondi bisestili" sarà invalidata. ;-) (FWIW parte 2, Ruby non supporta comunque i "secondi saltati"). :-)
Vikrant Chaudhary,

1
Non sono sicuro del motivo per cui sto ricevendo voti negativi su questo. Ti interessa spiegare, cari downvoter?
Vikrant Chaudhary,

@vikrantChaudhary Non lo so, è un'ottima risposta. Testato e funziona.
Hector Ordonez,

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

Quindi questo essenzialmente calcola la differenza in giorni se ogni mese durava 100 giorni e ogni anno durava 100 mesi. Il che non fa differenza se mantieni solo la parte dell'anno.
Jonathan Allard,

6

Le risposte finora sono piuttosto strane. Il tuo tentativo originale era abbastanza vicino al modo giusto per farlo:

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

Otterrai un risultato frazionario, quindi sentiti libero di convertire il risultato in un numero intero con to_i. Questa è una soluzione migliore perché tratta correttamente la differenza di data come un periodo di tempo misurato in giorni (o secondi nel caso della relativa classe temporale) dall'evento. Quindi una semplice divisione per il numero di giorni in un anno ti dà l'età. Quando si calcola l'età in anni in questo modo, purché si mantenga il valore DOB originale, non è necessario prevedere alcuna indennità per gli anni bisestili.


compleanno = Time.mktime (1960,5,5) mi dà fuori portata (problemi di epoca?)
Andrew Grimm,

Sì, vai vai problemi di epoca. Ho aggiornato la risposta per risolverlo.
Bob Aman,

birthday = DateTime.now - 1.yearmi dà un'età di 0. Purtroppo, dividere per 365,25 è un po 'impreciso.
Samir Talwar,

Non è possibile sottrarre 1.year così da un oggetto DateTime. 1.year si risolve al numero di secondi in un anno. Gli oggetti DateTime funzionano in base ai giorni. Ad esempio: (DateTime.now - 365.25) .strftime ("% D") Per quanto riguarda la precisione, se hai davvero a che fare con i compleanni, è molto preciso. Il fatto è che le persone sono già abbastanza imprecise quando si parla di età. Siamo nati in un preciso momento, ma di solito non forniamo l'ora esatta, i minuti e i secondi della nostra nascita quando scriviamo il nostro DOB. La mia tesi qui è che non vuoi davvero fare questo calcolo manualmente.
Bob Aman,

1
Questo non funziona per le persone nate prima del 1900. Ad esempio, Gertrude Baines ha un'età di 114.9979 anni per il suo compleanno nel 2009.
Andrew Grimm,

6

Il mio consiglio:

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

Il trucco è che l'operazione meno con Time restituisce secondi


Questo è quasi giusto. Gli anni bisestili indicano che un anno dura in realtà 365,25 giorni. Ciò significa anche che, nella migliore delle ipotesi, questo metodo potrebbe non aumentare la tua età fino a 18 ore prima del tuo compleanno.
Ryan Lue,

5

Mi piace questa:

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

Se ritieni che questo sia un ragionevole contendente a una domanda di 3 anni che ha già altre 10 risposte, dovresti includere più motivi che preferenze personali. Altrimenti, non otterrai molta attenzione
John Dvorak,

1
questo si interrompe quando un anno è un anno bisestile e un altro no
artm

Se l'ultimo anno ha 29 giorni, questo calcolo fallirà
phil88530

5

Questa risposta è la migliore, invece votala.


Mi piace la soluzione di @ philnash, ma il condizionale potrebbe essere complicato. Quello che fa quell'espressione booleana è confrontare coppie [mese, giorno] usando l' ordine lessicografico , quindi si potrebbe semplicemente usare il confronto di stringhe di ruby:

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

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

wow, è molto più ordinato. dovresti dare una risposta e raccogliere i premi!
artm

hmm, in realtà vedo che c'era già questa risposta ancor prima della mia
artm

Oh, ora lo faccio anche io ... -_- '
Ryan Lue il

4

Questa è una conversione di questa risposta (ha ricevuto molti voti):

# 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

È decisamente unico nel suo approccio ma ha perfettamente senso quando ti rendi conto di ciò che fa:

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

Ho appena realizzato che questa è la stessa
risposta

1

Poiché Ruby on Rails è taggato, la gemma dotiw sovrascrive la distanza integrata di Rails_of_times_in_words e fornisce distance_of_times_in_words_hash che può essere utilizzata per determinare l'età. Gli anni bisestili vengono gestiti correttamente per la parte relativa agli anni, sebbene sappiate che il 29 febbraio influisce sulla parte relativa ai giorni che merita di capire se è necessario quel livello di dettaglio. Inoltre, se non ti piace il modo in cui dotiw modifica il formato di distance_of_time_in_words, usa l'opzione: vague per ripristinare il formato originale.

Aggiungi dotiw al Gemfile:

gem 'dotiw'

Sulla riga di comando:

bundle

Includere DateHelper nel modello appropriato per ottenere l'accesso a distance_of_time_in_words e distance_of_time_in_words_hash. In questo esempio il modello è "Utente" e il campo compleanno è "Compleanno".

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

Aggiungi questo metodo allo stesso modello.

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

1

Credo che questo sia funzionalmente equivalente alla risposta di @ philnash, ma IMO più facilmente comprensibile.

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 

0

Quanto segue sembra funzionare (ma lo apprezzerei se fosse controllato).

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

0

Se non ti interessa un giorno o due, questo sarebbe più breve e piuttosto autoesplicativo.

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

0

Ok che dire di questo:

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

Ciò presuppone che stiamo utilizzando le rotaie, chiamando il agemetodo su un modello e che il modello abbia una colonna del database della data dob. Questo è diverso dalle altre risposte perché questo metodo usa le stringhe per determinare se siamo prima del compleanno di quest'anno.

Ad esempio, se dobè 2004/2/28 ed todayè 2014/2/28, agesarà 2014 - 2004o 10. I galleggianti saranno 0228e 0229. b4bdaysarà "0228" < "0229"o true. Infine, si sottrae 1da agee ottenere 9.

Questo sarebbe il modo normale di confrontare le due volte.

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

Funziona allo stesso modo, ma la b4bdaylinea è troppo lunga. Anche l' 2016anno non è necessario. Il confronto delle stringhe all'inizio era il risultato.

Puoi anche farlo

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

Se non stai usando le rotaie, prova questo

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

👍🏼


A proposito, la tua risposta è logicamente la stessa di quella di Philnash. La sua risposta è molto più chiara e non si basa su Rails.
Mantas,

@Mantas Philnash ha detto che ha usato quel metodo in un progetto Rails. Avevi dei binari nei tag. Il suo metodo ha due confronti in più rispetto al mio. L'ultima riga del suo metodo è difficile da capire.
Cruz Nunez,

scusa, ma il codice di philnash è molto più pulito del tuo. Inoltre, il suo codice è un metodo semplice. Il tuo dipende dal valore "dob". Che potrebbe non essere così chiaro per i nuovi utenti. E nemmeno aiuta molto il codice. Sì, il tuo ultimo campione se ne sbarazza. Ma Philnash's è semplicemente un perfetto pezzo di codice da tenere semplicemente semplice.
Mantas,

0

Penso che sia molto meglio non contare i mesi, perché puoi ottenere il giorno esatto dell'anno 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

0

È venuto con una variante di Rails di questa soluzione

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

0

DateHelper può essere utilizzato solo per anni

puts time_ago_in_words '1999-08-22'

quasi 20 anni


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

Per tenere conto degli anni bisestili (e ipotizzando la presenza di supporto attivo):

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_sincemodificherà correttamente la data per tener conto degli anni non bisestili (quando compie gli anni 02-29).


-1

Ecco la mia soluzione che consente anche di calcolare l'età ad una data specifica:

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

-2

Ho dovuto occuparmene anche io, ma per mesi. È diventato troppo complicato. Il modo più semplice a cui potevo pensare era:

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

Potresti fare lo stesso con 12 mesi:

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

Questo utilizzerà la classe Date per incrementare il mese, che avrà a che fare con 28 giorni e l'anno bisestile ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.