Получить возраст человека в Ruby

125

Хочу узнать возраст человека с момента его рождения. now - birthday / 365не работает, потому что в некоторых годах 366 дней. Я придумал следующий код:

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

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

Есть ли более рубиновый способ вычисления возраста?

Манты
источник
6
Мне нравится этот вопрос, потому что он подчеркивает идею о том, что есть «больше Ruby» и «меньше Ruby» способов делать что-то. Важно быть не только логически правильным (что можно сделать, скопировав ответ C #), но и стилистически правильным. И ответ Adinochestva хорошо использует идиому Ruby.
Джеймс А. Розен,

Ответы:

410

Я знаю, что опаздываю на вечеринку, но принятый ответ ужасно сломается, когда я попытаюсь определить возраст человека, родившегося 29 февраля в високосном году. Это потому, что вызов birthday.to_date.change(:year => now.year)создает недопустимую дату.

Вместо этого я использовал следующий код:

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
источник
4
Используйте это, а не тот, который отмечен галочкой, который не
поддерживает
Почему вы возвращаете 0 || 1 вместо true|| false?
0112,
1
@ alex0112 Потому что результат (0 или 1) этого заведомо запутанного условного выражения вычитается из разницы в годах между настоящим моментом и датой рождения. Он предназначен для того, чтобы узнать, был ли у человека день рождения еще в этом году, а если нет, то ему на 1 год меньше, чем разница между годами.
philnash
2
@andrej now = Date.today работает, но обратите внимание, что он не обрабатывает проблемы с часовым поясом. В Rails Date.today возвращает дату, основанную на часовом поясе системы. ActiveRecord возвращает время на основе часового пояса, настроенного в ваших приложениях. Если системный часовой пояс отличается от часового пояса вашего приложения, вы эффективно сравниваете время из двух разных часовых поясов, что не будет очень точным.
Любимый Onwuemene
Это действительно самое простое решение?
Марко Принс,
50

Я обнаружил, что это решение хорошо работает и читается другими людьми:

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

Легко, и вам не нужно беспокоиться об обработке високосного года и тому подобного.

PJ.
источник
3
Для этого требуется Rails (для age.years), но можно сделать так, чтобы Rails не требовался, если вы сделали что-то подобное Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday.
Чак
Вы правы - извините, я предположил, что Rails помечен этим вопросом. Но да, легко модифицируется только для Ruby.
PJ.
1
Сначала я выбрал этот, потому что он самый красивый, но в производстве он часто ошибается по непонятным мне причинам. Вышеупомянутый, использующий Time.now.utc.to_date, кажется, работает лучше.
Кевин
@Kevin Интересно. У меня никогда не было проблем с этим, но это не значит, что их нет. Не могли бы вы привести конкретный пример? Я хотел бы знать, есть ли ошибка. Спасибо.
PJ.
2
@sigvei - это особенность, а не ошибка;) В большинстве стран, включая США, 28-е число по закону считается вашим днем ​​рождения в невисокосный год, если вы високосный ребенок. Человек действительно будет считаться 10.
PJ.
33

Использовать это:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end
Sadegh
источник
41
Это прерывается, если birthday.to_date является високосным годом, а текущий год - нет. Небольшое событие, но оно доставляло мне проблемы.
Philnash 01
1
Голосование против ответа Филнаша.
Ник Сонневельд
2
Еще одна причина предпочесть ответ Филнаша заключается в том , что он работает с простым старым Ruby, в то время как принятый ответ работает только с rails/activesupport.
Sheldonh 02
16

Один лайнер в Ruby on Rails (ActiveSupport). Обрабатывает високосные годы, високосные секунды и все такое.

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

Логика отсюда - вычислить возраст на C #

Предполагая, что обе даты находятся в одном часовом поясе, если не звонить utc()раньше to_s()на обоих.

Викрант Чаудхари
источник
1
(Date.today.to_s(:number).to_i - birthday.to_date.to_s(:number).to_i)/1e4.to_iтоже работает
Грант Хатчинс
FWIW, но тогда моя гарантия на "дополнительные секунды" будет аннулирована. ;-) (FWIW, часть 2, Ruby в любом случае не поддерживает "дополнительные секунды"). :-)
Викрант Чаудхари
1
Не уверен, почему я получаю отрицательные голоса по этому поводу. Не хотите объяснять, дорогие противники?
Викрант Чаудхари
@vikrantChaudhary Я не знаю, это отличный ответ. Проверено и работает.
Hector Ordonez
9
(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000
pguardiario
источник
Таким образом, это по существу вычисляет разницу в днях, если каждый месяц длился 100 дней, а каждый год длился 100 месяцев. Что не имеет значения, если вы сохраните только часть года.
Джонатан Аллард
6

Ответы пока довольно странные. Ваша первоначальная попытка была довольно близка к правильному способу сделать это:

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

Вы получите дробный результат, поэтому можете преобразовать результат в целое число с помощью to_i. Это лучшее решение, поскольку оно правильно обрабатывает разницу дат как период времени, измеряемый в днях (или секундах в случае связанного класса Time) с момента события. Тогда простое деление на количество дней в году даст вам возраст. При таком расчете возраста в годах, пока вы сохраняете исходное значение DOB, не нужно делать поправку на високосные годы.

Боб Аман
источник
birthday = Time.mktime (1960,5,5) выдает меня за пределы диапазона (проблемы эпохи?)
Эндрю Гримм
Да, иди, давай, эпохи. Я обновил ответ, чтобы решить эту проблему.
Боб Аман,
birthday = DateTime.now - 1.yearдает мне возраст 0. К сожалению, деление на 365,25 немного неточно.
Самир Талвар
Вы не можете вычесть 1 год таким образом из объекта DateTime. 1. год означает количество секунд в году. Объекты DateTime работают по дням. Например: (DateTime.now - 365.25) .strftime ("% D") Что касается точности, если вы действительно имеете дело только с днями рождения, она достаточно точная. Дело в том, что люди уже довольно неточны, когда дело касается возраста. Мы рождаемся в точный момент времени, но обычно мы не указываем точный час, минуту и ​​секунду нашего рождения, когда записываем нашу дату рождения. Мой аргумент заключается в том, что вы действительно не хотите выполнять этот расчет вручную.
Боб Аман,
1
Это не работает для людей, родившихся до 1900 года. Например, согласно сообщениям, в день рождения в 2009 году Гертруда Бейнс имела возраст 114,9979 лет.
Эндрю Гримм,
6

Мое предложение:

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

Хитрость в том, что операция минус с Time возвращает секунды.

jlebrijo
источник
Это почти верно. Високосные годы означают, что на самом деле год длится 365,25 дня. Это также означает, что в лучшем случае этот метод может не увеличить ваш возраст до 18 часов вашего дня рождения.
Ryan Lue
5

Мне нравится этот:

now = Date.current
age = now.year - dob.year
age -= 1 if now.yday < dob.yday
tpalm
источник
Если вы считаете, что это разумный кандидат на трехлетний вопрос, на который уже есть 10 других ответов, вам следует указать больше причин, чем личные предпочтения. В противном случае вы не привлечете к себе особого внимания
Джон Дворжак
1
это прерывается, когда один год високосный, а другой - нет
artm
Если в прошлом году фев было 29 дней, этот расчет не удастся
phil88530
5

Это лучший ответ , проголосуйте за него.


Мне нравится решение @philnash, но условное выражение может быть более компактным. Это логическое выражение сравнивает пары [месяц, день] в лексикографическом порядке , так что вместо этого можно просто использовать сравнение строк в ruby:

def age(dob)
  now = Date.today
  now.year - dob.year - (now.strftime('%m%d') < dob.strftime('%m%d') ? 1 : 0)
end
ARTM
источник
1
О чем (Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000?
Ryan Lue
вау, это намного аккуратнее. ответь и забери награду!
artm
хм, на самом деле я вижу, что этот ответ был уже даже раньше моего
artm
О, теперь я тоже ... -_- '
Ryan Lue
4

Это преобразование этого ответа (он получил много голосов):

# 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

Это определенно уникально по своему подходу, но имеет смысл, когда вы понимаете, что он делает:

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
источник
Только что понял, что это тот же самый anser
br3nt 02
1

Поскольку Ruby on Rails помечен тегами, гем dotiw переопределяет встроенное в Rails distance_of_times_in_words и предоставляет distance_of_times_in_words_hash который может использоваться для определения возраста. Високосные годы обрабатываются нормально для части лет, хотя имейте в виду, что 29 февраля действительно влияет на часть дней, которая требует понимания, нужен ли такой уровень детализации. Кроме того, если вам не нравится, как dotiw изменяет формат distance_of_time_in_words, используйте параметр: vague, чтобы вернуться к исходному формату.

Добавьте dotiw в Gemfile:

gem 'dotiw'

В командной строке:

bundle

Включите DateHelper в соответствующую модель, чтобы получить доступ к distance_of_time_in_words и distance_of_time_in_words_hash. В этом примере модель - «Пользователь», а поле дня рождения - «день рождения».

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

Добавьте этот метод к той же модели.

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

Использование:

u = User.new("birthday(1i)" => "2011", "birthday(2i)" => "10", "birthday(3i)" => "23")
u.age
mindriot
источник
1

Я считаю, что это функционально эквивалентно ответу @ philnash, но ИМО легче понять.

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

Использование:

> BirthDate.new(Date.parse('1988-02-29')).time_ago_in_years
 => 31 
Джейсон Светт
источник
0

Следующее, кажется, работает (но я был бы признателен, если бы это было проверено).

age = now.year - bday.year
age -= 1 if now.to_a[7] < bday.to_a[7]
testr
источник
0

Если вам наплевать на день или два, это будет короче и не требует пояснений.

(Time.now - Time.gm(1986, 1, 27).to_i).year - 1970
Хакунин
источник
0

Хорошо, что насчет этого:

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

Предполагается, что мы используем рельсы, вызываем ageметод в модели, а модель имеет столбец базы данных даты dob. Это отличается от других ответов, потому что этот метод использует строки, чтобы определить, дожили ли мы до дня рождения в этом году.

Например, если dobэто 2004/2/28 и today2014/2/28, ageбудет 2014 - 2004или 10. Поплавки будут 0228и 0229. b4bdayбудет "0228" < "0229"или true. Наконец, мы будем вычитать 1из ageи получить 9.

Это был бы нормальный способ сравнить два раза.

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

Это работает так же, но b4bdayлиния слишком длинная. 2016Год также не требуется. Результатом было сравнение строк в начале.

Вы также можете сделать это

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

Если вы не используете рельсы, попробуйте это

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

👍🏼

Круз Нуньес
источник
К вашему сведению, ваш ответ логически такой же, как и у Филнаша. Его ответ намного чище и не полагается на Rails.
Mantas
@Mantas Philnash сказал, что использовал этот метод в проекте Rails. У вас в тегах были рельсы. Его метод имеет на два сравнения больше, чем мой. Последнюю строчку его метода трудно понять.
Круз Нуньес,
извините, но код Филнаша намного чище, чем ваш. Кроме того, его код - простой метод. Ваш зависит от значения "доб". Что может быть не так понятно новым пользователям. И даже не сильно помогает коду. Да, ваш самый последний образец избавился от этого. Но Philnash - это просто идеальный простой и глупый фрагмент кода.
Mantas
0

Я думаю, что намного лучше не считать месяцы, потому что вы можете получить точный день года, используя 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

Придумал вариант этого решения для Rails

def age(dob)
    now = Date.today
    age = now.year - dob.year
    age -= 1 if dob > now.years_ago(age)
    age
end
derosm2
источник
0

DateHelper можно использовать только для получения лет

puts time_ago_in_words '1999-08-22'

почти 20 лет

hsul4n
источник
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

Чтобы учесть високосные годы (и при условии наличия activesupport):

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_sinceправильно изменит дату, чтобы учесть невисокосные годы (когда день рождения 02-29).

Виталий Кушнер
источник
-1

Вот мое решение, которое также позволяет рассчитать возраст на определенную дату:

def age on = Date.today
  (_ = on.year - birthday.year) - (on < birthday.since(_.years) ? 1 : 0)
end
hurikhan77
источник
-2

Мне тоже приходилось сталкиваться с этим, но месяцами. Стало слишком сложно. Самый простой способ, который я мог придумать, был:

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

Вы можете сделать то же самое с 12 месяцами:

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

Это будет использовать класс Date для увеличения месяца, который будет иметь дело с 28 днями, високосным годом и т. Д.

Муктаким Ахмед
источник