Obter a idade da pessoa em Ruby

125

Eu gostaria de ter a idade de uma pessoa a partir do aniversário. now - birthday / 365não funciona, porque alguns anos têm 366 dias. Eu vim com o seguinte código:

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

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

Existe uma maneira mais ruby ​​de calcular a idade?

Mantas
fonte
6
Eu gosto dessa pergunta porque destaca a ideia de que existem maneiras "mais Ruby" e "menos Ruby" de fazer as coisas. É importante não apenas estar logicamente correto (o que você poderia estar copiando a resposta C #), mas também estilisticamente correto. E a resposta de Adinochestva faz bom uso do idioma Ruby.
James A. Rosen

Respostas:

410

Sei que estou atrasado para a festa aqui, mas a resposta aceita quebrará terrivelmente ao tentar descobrir a idade de alguém nascido no dia 29 de fevereiro em um ano bissexto. Isso ocorre porque a chamada para birthday.to_date.change(:year => now.year)cria uma data inválida.

Eu usei o seguinte código:

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
fonte
4
Utilize este, não aquele que não consegue lidar com os anos bissextos marcado-check
bgcode
Por que você retorna 0 || 1 em vez de true|| false?
0112
1
@ alex0112 Porque o resultado (0 ou 1) dessa condicional admitidamente confusa é subtraído da diferença de anos entre agora e a data de nascimento. Pretende-se descobrir se a pessoa teve seu aniversário ainda este ano e, se não, ela tem 1 ano a menos do que a diferença entre os anos.
philnash
2
@andrej now = Date.today funciona, mas observe que não lida com problemas com o fuso horário. No Rails Date.today retorna uma data com base no fuso horário do sistema. O ActiveRecord retorna um horário com base no fuso horário configurado pelo Google Apps. Se o fuso horário do sistema for diferente do fuso horário do aplicativo, você compararia efetivamente o tempo de dois fusos horários diferentes, o que não seria muito preciso.
Onwuemene favorito
Esta é realmente a solução mais simples?
Marco Prins 25/05
50

Encontrei esta solução para funcionar bem e ser legível para outras pessoas:

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

Fácil e você não precisa se preocupar em lidar com o ano bissexto e tal.

PJ.
fonte
3
Isso requer o Rails (por idade.ano), mas pode ser feito para não exigir o Rails se você fizer algo parecido Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Chuck
Você está certo - desculpe, eu assumi o Rails como a pergunta foi marcada com ele. Mas sim, facilmente modificável apenas para Ruby.
PJ.
1
Eu escolhi essa no começo porque é a mais bonita, mas na produção muitas vezes está errada, por razões que eu não entendo. O acima, usando Time.now.utc.to_date, parece estar funcionando melhor.
21711 Kevin
@Kevin Interessante. Eu nunca tive um problema com isso sozinho, mas isso não significa que não há um. Você poderia me dar um exemplo específico? Eu gostaria de saber se há algum erro. Obrigado.
PJ.
2
@ sigvei - esse é um recurso, não um bug;) Na maioria dos países, incluindo os EUA, o dia 28 é legalmente considerado seu aniversário em um ano sem salto, se você é um bebê saltador. A pessoa seria de fato considerada 10.
PJ.
33

Usa isto:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end
Sadegh
fonte
41
Isso é interrompido se birthday.to_date for um ano bissexto e o ano atual não. Não é uma ocorrência grande, mas está me causando problemas.
#
1
Voto negativo para incentivar a resposta do philnash.
Nick Sonneveld
2
Outro motivo para preferir a resposta do philnash é que ele funciona com o velho e simples Ruby, enquanto a resposta aceita só funciona rails/activesupport.
sheldonh
16

Um liner no Ruby on Rails (ActiveSupport). Lida com anos bissextos, segundos bissextos e tudo.

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

Lógica daqui - Calcular idade em C #

Supondo que as duas datas estejam no mesmo fuso horário, se não ligar utc()antes to_s()em ambas.

Vikrant Chaudhary
fonte
1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_itambém funciona
Grant Hutchins
FWIW, mas a minha garantia em "segundos bissextos" será invalidada. ;-) (FWIW parte 2, Ruby não suporta "segundos bissextos" de qualquer maneira). :-)
Vikrant Chaudhary
1
Não sei por que estou recebendo votos negativos sobre isso. Gostaria de explicar, queridos downvoters?
Vikrant Chaudhary
@vikrantChaudhary Não sei, é uma ótima resposta. Testado e funciona.
Hector Ordonez
9
(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000
pguardiario
fonte
Portanto, isso basicamente calcula a diferença de dias se cada mês durou 100 dias e cada ano durou 100 meses. O que não faz diferença se você apenas mantiver a parte do ano.
Jonathan Allard
6

As respostas até agora são meio estranhas. Sua tentativa original foi bem próxima da maneira correta de fazer isso:

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

Você obterá um resultado fracionário; sinta-se à vontade para converter o resultado em um número inteiro com to_i. Esta é uma solução melhor porque trata corretamente a diferença de data como um período medido em dias (ou segundos no caso da classe Time relacionada) desde o evento. Então, uma divisão simples pelo número de dias em um ano fornece a idade. Ao calcular a idade em anos dessa maneira, contanto que você mantenha o valor original do DOB, não é necessário conceder nenhuma permissão para os anos bissextos.

Bob Aman
fonte
aniversário = Time.mktime (1960,5,5) dá-me fora de alcance (problemas Epoch?)
Andrew Grimm
Sim, vá em frente questões da época. Atualizei a resposta para resolver isso.
227 Bob Aman
birthday = DateTime.now - 1.yeardá-me uma idade de 0. Infelizmente, dividir por 365.25 é um pouco impreciso.
Samir Talwar
Você não pode subtrair 1. ano como esse de um objeto DateTime. 1.ano resolve para o número de segundos em um ano. Os objetos DateTime operam com base em dias. Por exemplo: (DateTime.now - 365.25) .strftime ("% D") Quanto à precisão, se você realmente está apenas lidando com aniversários, é bastante preciso. O fato é que as pessoas já são bastante imprecisas quando se trata de idades. Nascemos em um momento preciso, mas geralmente não damos a hora exata, minuto e segundo de nosso nascimento quando anotamos nosso DOB. Meu argumento aqui é que você realmente não deseja fazer esse cálculo manualmente.
226 Bob Aman
1
Isso não funciona para as pessoas nascidas antes de 1900. Por exemplo Gertrude Baines é relatado como tendo uma idade de 114,9979 em seu aniversário, em 2009.
Andrew Grimm
6

Minha sugestão:

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

O truque é que a operação de menos com Time retorna segundos

jlebrijo
fonte
Isso está quase certo. Anos bissextos significam que um ano tem 365,25 dias. Isso também significa que, na melhor das hipóteses, esse método pode não aumentar sua idade até as 18 horas do aniversário.
Ryan Lue
5

Eu gosto deste:

now = Date.current
age = now.year - dob.year
age -= 1 if now.yday < dob.yday
tpalm
fonte
Se você acha que este é um candidato razoável a uma pergunta de 3 anos que já tem 10 outras respostas, inclua mais motivos do que preferências pessoais. Caso contrário, você não terá muita atenção
John Dvorak
1
Isso quebra quando um ano é um ano bissexto e outro não
artm
Se última yr fevereiro está com 29 dias, este cálculo irá falhar
phil88530
5

Esta resposta é a melhor, faça um voto positivo.


Eu gosto da solução do @ philnash, mas o condicional pode ser compactor. O que essa expressão booleana faz é comparar pares [mês, dia] usando ordem lexicográfica , para que você possa usar a comparação de cadeias de ruby:

def age(dob)
  now = Date.today
  now.year - dob.year - (now.strftime('%m%d') < dob.strftime('%m%d') ? 1 : 0)
end
artm
fonte
1
Que tal (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
precisa
uau, isso é muito mais arrumado. você deve responder e receber as recompensas!
Art
hmm, na verdade eu ver que havia esta resposta já mesmo antes do meu
artm
Ah, agora eu também ... -_- '
Ryan Lue
4

Esta é uma conversão desta resposta (recebeu muitos 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 único em sua abordagem, mas faz todo o sentido quando você percebe o que faz:

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
fonte
Só percebi isso é o mesmo anser
br3nt
1

Como o Ruby on Rails é marcado, a gema dotiw substitui o distance_of_times_in_words embutido no Rails e fornece o distance_of_times_in_words_hash interno que pode ser usado para determinar a idade. Os anos bissextos são bem tratados para a parte dos anos, embora esteja ciente de que o dia 29 de fevereiro afeta a parte dos dias que justifica o entendimento se esse nível de detalhe é necessário. Além disso, se você não gostar de como o dotiw altera o formato de distance_of_time_in_words, use a opção: vaga para reverter para o formato original.

Adicione dotiw ao Gemfile:

gem 'dotiw'

Na linha de comando:

bundle

Inclua o DateHelper no modelo apropriado para obter acesso a distance_of_time_in_words e distance_of_time_in_words_hash. Neste exemplo, o modelo é 'Usuário' e o campo de aniversário é 'aniversário.

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

Adicione este método ao mesmo 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
fonte
1

Acredito que isso seja funcionalmente equivalente à resposta do @ philnash, mas o IMO é mais facilmente compreensível.

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
fonte
0

O seguinte parece funcionar (mas eu apreciaria se estivesse marcado).

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

Se você não se importa com um dia ou dois, isso seria mais curto e bastante auto-explicativo.

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

Ok, e quanto a isso:

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

Isso pressupõe que estamos usando trilhos, chamando o agemétodo em um modelo, e o modelo possui uma coluna de banco de dados de datas dob. Isso é diferente de outras respostas, porque esse método usa cadeias para determinar se estamos antes do aniversário deste ano.

Por exemplo, se dobfor 2004/2/28 e todayfor 2014/2/28, ageserá 2014 - 2004ou 10. Os carros alegóricos serão 0228e 0229. b4bdayserá "0228" < "0229"ou true. Finalmente, vamos subtrair 1a partir agee obter 9.

Essa seria a maneira normal de comparar as duas vezes.

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

Isso funciona da mesma forma, mas a b4bdaylinha é muito longa. O 2016ano também é desnecessário. A comparação de strings no início foi o resultado.

Você também pode fazer isso

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 você não estiver usando trilhos, tente isso

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
fonte
Apenas para sua informação, sua resposta é logicamente a mesma de philnash. Sua resposta é muito mais limpa e não depende do Rails.
Mantas
@Mantas Philnash disse que usou esse método em um projeto Rails. Você tinha trilhos nas tags. O método dele tem mais duas comparações que o meu. A última linha de seu método é difícil de entender.
Cruz Nunez
desculpe, mas o código do philnash é muito mais limpo que o seu. Além disso, seu código é um método simples. O seu depende do valor "dob". O que pode não ser tão claro para os novos usuários. E nem ajuda muito o código. Sim, sua última amostra se livra dela. Mas o philnash's é apenas um pedaço de código perfeito, simples e simples de guardar.
Mantas
0

Eu acho que é muito melhor não contar meses, porque você pode obter o dia exato do ano 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
fonte
0

Criamos uma variação Rails dessa solução

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

O DateHelper pode ser usado para obter apenas anos

puts time_ago_in_words '1999-08-22'

quase 20 anos

hsul4n
fonte
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
fonte
-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
fonte
-1
Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)
Tim Van Ginderen
fonte
-1

Para explicar os anos bissextos (e assumindo a presença do suporte ativo):

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á corretamente a data para levar em conta os anos que não são bissextos (quando o aniversário for 02-29).

Vitaly Kushner
fonte
-1

Aqui está minha solução, que também permite calcular a idade em uma data específica:

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

Eu tive que lidar com isso também, mas por meses. Tornou-se muito complicado. A maneira mais simples de pensar era:

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

Você poderia fazer o mesmo com 12 meses:

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

Isso usará a classe Date para incrementar o mês, que lidará com 28 dias e ano bissexto etc.

Mooktakim Ahmed
fonte