Получить возраст человека в 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

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

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


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

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

Таким образом, это по существу вычисляет разницу в днях, если каждый месяц длился 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 возвращает секунды.


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

5

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

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

Если вы считаете, что это разумный кандидат на трехлетний вопрос, на который уже есть 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

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

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

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]

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

0

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

puts time_ago_in_words '1999-08-22'

почти 20 лет


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

Чтобы учесть високосные годы (и при условии наличия 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

-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 днями, високосным годом и т. Д.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.