Отримайте вік людини в Рубі


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 #), але і стилістично правильним. І відповідь Адіночества добре використовує ідіому Рубі.
Джеймс А. Росен

Відповіді:


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
Скористайтеся цим, а не відміченим позначкою, який не може впоратися з високосними роками
bgcode

Чому ви повертаєте 0 || 1 замість true|| false?
0112

1
@ alex0112 Оскільки результат (0 або 1) того, що насправді плутає умовне, віднімається від різниці в роках між датою та датою народження. Він призначений для з'ясування того, чи був у цього дня народження у цього року, а якщо ні, то їм на 1 рік менше, ніж різниця між роками.
philnash

2
@andrej зараз = Date.today працює, але зауважте, що він не обробляє проблеми з Timezone. У 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
Ця помилка виходить, якщо Birth.to_date - високосний рік, а поточний - ні. Не велике явище, але це викликає у мене проблеми.
філнаш

1
Нахил, щоб заохотити відповідь філана.
Нік Соневельд

2
Ще одна причина, яка надає перевагу відповіді філнаша , полягає в тому, що вона працює з простою старою Рубі, тоді як прийнята відповідь працює лише з rails/activesupport.
sheldonh

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, Рубі все одно не підтримує "стрибків секунд"). :-)
Vikrant Chaudhary

1
Не впевнений, чому я отримую суперечки з цього приводу. Не хочете пояснити, шановні низовики?
Vikrant Chaudhary

@vikrantChaudhary Я не знаю, це чудова відповідь. Тестується і працює.
Гектор Ордонез

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. Це краще рішення, оскільки воно правильно трактує різницю дат як часовий період, виміряний у днях (або секундах у випадку відповідного часового класу) після події. Тоді простий поділ на кількість днів у році дає тобі вік. Підраховуючи вік у роках таким чином, доки ви зберігаєте початкове значення DOB, не потрібно робити надбавки за високосні роки.


день народження = Time.mktime (1960,5,5) дає мені поза межі діапазону (проблеми епохи?)
Ендрю Грімм

Так, продовжуйте епоху. Я оновив відповідь, щоб вирішити цю проблему.
Боб Аман

birthday = DateTime.now - 1.yearдає мені вік у 0. На жаль, ділення на 365,25 є дещо неточним.
Самір Тальвар

Ви не можете відняти 1. рік подібного від об’єкта DateTime. 1.роки розраховуються на кількість секунд за рік. Об'єкти DateTime працюють на основі днів. Наприклад: (DateTime.now - 365.25). Тривалість ("% D") Що стосується точності, якщо ви дійсно просто маєте справу з днями народження, це досить точно. Справа в тому, що люди вже досить неточні, коли мова йде про віки. Ми народжуємось у точний момент часу, але зазвичай не даємо точну годину, хвилину та секунду свого народження, коли записуємо наш DOB. Мій аргумент тут полягає в тому, що ви дійсно не хочете робити цей розрахунок вручну.
Боб Аман

1
Це не працює для людей, народжених до 1900 року. Наприклад, повідомляється, що Гертруда Бейнс у день народження в 2009 році становила 114,9979 років.
Ендрю Грімм

6

Моя пропозиція:

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

Хитрість полягає в тому, що мінусова операція з часом повертає секунди


Це майже правильно. Випускні роки означають, що рік насправді триває 365,25 днів. Це також означає, що в кращому випадку цей спосіб може не збільшити ваш вік до 18 годин від дня народження.
Райан Люе

5

Мені подобається цей:

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

Якщо ви вважаєте, що це розумний претендент на питання 3yo, на яке вже є 10 інших відповідей, вам слід зазначити більше причин, ніж особисті переваги. Інакше ви не отримаєте багато уваги
Джон Дворак

1
це ламається , коли один рік є високосним , а інший ні
ARTM

Якщо минулого року феб отримав 29 днів, цей розрахунок не вдасться
phil88530

5

Ця відповідь найкраща, замість неї виправдайте.


Мені подобається рішення @ philnash, але умовне може бути більш компактним. Що виражає булевий вираз - це порівняння пар [місяць, день], використовуючи лексикографічний порядок , так що можна просто використовувати порівняння рядків рубіна:

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?
Райан Люе

вау, це так симпатичніше. вам слід відповісти і зібрати нагороди!
artm

хм, насправді я бачу, що ця відповідь була ще до моєї справи
artm

О, зараз я теж ... -_- '
Райан Люе

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

Щойно зрозумів, що це той самий
ансер

1

Оскільки Рубін на рейках позначено, то dotiw камінь перекриває рейки вбудованого distance_of_times_in_words і забезпечують distance_of_times_in_words_hash , які можуть бути використані для визначення віку. Випускні роки обробляються штрафом за частину років, хоча майте на увазі, що 29 лютого впливає на частину днів, яка вимагає розуміння, чи потрібен цей рівень деталізації. Крім того, якщо вам не подобається, як dotiw змінює формат distance_of_time_in_words, використовуйте параметр: negue для повернення до початкового формату.

Додати доти в 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 і todayє 2014/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

👍🏼


Тільки фій, ваша відповідь логічно така ж, як і у філанах. Його відповідь набагато чіткіша, але він не покладається на Рейки.
Мантас

@Mantas Philnash сказав, що використовував цей метод у проекті Rails. У вас були рейки в тегах. Його метод має ще два порівняння, ніж мій. Останній рядок його методу важко зрозуміти.
Cruz Nunez

Вибачте, але код philnash набагато чистіший за ваш. Також його код - простий метод. Ваша залежність від значення "dob". Що може бути не зовсім зрозумілим для нових користувачів. І навіть не дуже допомагає коду. Так, ваш останній зразок позбавляється від нього. Але philnash's - це просто ідеальний код-код, простий-дурний.
Мантас

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

Для обліку високосних років (і припускаючи наявність активної підтримки):

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

Це використовуватиме клас Дати для збільшення місяця, який стосуватиметься 28 днів та високосного року тощо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.