在Ruby中获取人的年龄


125

我想知道一个人的生日。now - birthday / 365不起作用,因为有些年份有366天。我想出了以下代码:

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

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

还有更Ruby的方法来计算年龄吗?


6
我喜欢这个问题,因为它突出了这样的想法,即存在“更多的Ruby”和“更少的Ruby”的处理方式。重要的是不仅要逻辑上正确(可以通过复制C#答案来做到),而且在风格上也要正确。Adinochestva的答案很好地利用了Ruby惯用语。
James A. Rosen

Answers:


410

我知道我在这里参加聚会迟到了,但是当尝试计算February年2月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
使用此选项,而不使用不能处理leap年的选中标记
bgcode 2013年

为什么返回0 || 1代替true|| false
1412年

1
@ alex0112因为该条件的结果(0或1)是从现在到生日之间的年差中减去的。目的是查明该人今年是否还过生日,如果还没有生日,则比两岁之间的年龄小一岁。
philnash 2014年

2
@andrej now = Date.today有效,但请注意,它不能处理时区问题。在Rails中,Date.today返回基于系统时区的日期。ActiveRecord根据您的Apps配置的时区返回时间。如果系统时区与您的应用程序时区不同,则您将有效地比较来自两个不同时区的时间,这可能不是很准确。
最喜欢的Onwuemene

这真的是最简单的解决方案吗?
Marco Prins

50

我发现此解决方案很好用,并且对其他人可读:

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

简单易行,您无需担心如何处理leap年。


3
这需要Rails(针对age.years),但是如果您执行了类似的操作,则可以不要求Rails Date.today.month < birthday.month or Date.today.month == birthday.month && Date.today.mday < birthday.mday
查克(Chuck)

您说的没错-对不起,我以为Rails是问题所在。但是可以,仅针对Ruby即可轻松修改。
PJ。

1
我之所以选择它是因为它是最漂亮的,但是在生产中,它经常是错误的,原因是我不了解。上面使用Time.now.utc.to_date的似乎工作得更好。
凯文

@凯文有趣。我自己从来没有遇到过问题,但这并不意味着没有人。你能举一个具体的例子吗?我想知道是否有错误。谢谢。
PJ。

2
@sigvei-这是一个功能,而不是错误;)在包括美国在内的大多数国家/地区中,如果您是跳跳婴儿,则合法地将28岁定为非your年的生日。这个人的确会被认为是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是a年,而当前年份不是a年,则会中断。发生的次数不大,但是这一直在给我带来麻烦。
philnash 2010年

1
投票以鼓励philnash回答。
Nick Sonneveld

2
选择philnash答案的另一个原因是,它适用于普通的旧Ruby,而可接受的答案仅适用于rails/activesupport
sheldonh,2015年

16

Ruby on Rails(ActiveSupport)中的一个内衬。处理leap年,leap秒等等。

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也有效
Grant Hutchins

FWIW,但随后我对“ le秒”的保证将失效。;-)(FWIW第2部分,Ruby始终不支持“ le秒”)。:-)
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个月,则这基本上可以计算出天数差异。如果只保留年份部分,这没有什么区别。
乔纳森·阿拉德'18

6

到目前为止的答案有点奇怪。您最初的尝试非常接近执行此操作的正确方法:

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

您将得到分数结果,请随时使用将结果转换为整数to_i。这是一个更好的解决方案,因为它可以将日期差正确地视为自事件发生以来以天(或秒,在相关的Time类中为秒)度量的时间段。然后简单地除以一年中的天数即可得出年龄。以这种方式计算年龄时,只要保留原始的DOB值,就无需为leap年做任何准备。


生日= Time.mktime(1960,5,5)超出了我的范围(时代问题?)
Andrew Grimm,2009年

是的,去时代问题。我已经更新了答案以解决此问题。
鲍勃·阿曼

birthday = DateTime.now - 1.year给我一个0岁的年龄。不幸的是,除以365.25有点不精确。
萨米尔·塔尔瓦尔

您不能从DateTime对象中减去1.year。1.year解析为一年中的秒数。DateTime对象基于天进行操作。例如:(DateTime.now-365.25).strftime(“%D”)至于精度,如果您真的只是在处理生日,那么它就非常精确。事实是,人们已经很不精确了。我们出生的时间恰好是正确的时刻,但是当我们写下DOB时,我们通常不会给出确切的出生时间,分钟和秒钟。我的论点是您真的不想手动进行此计算。
鲍勃·阿曼

1
这不适用于1900年以前出生的人。例如,据报道格特鲁德·贝恩斯(Gertrude Baines)在2009
安德鲁·格林

6

我的建议:

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

诀窍是,带Time的减号运算会返回秒


这几乎是正确的。years年意味着一年实际上是365.25天。这也意味着,这种方法充其量只能在您生日18个小时之前增加您的年龄。
瑞安·吕

5

我喜欢这一个:

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

如果您认为这是已经有10个其他答案的3yo问题的合理竞争者,则除了个人偏爱之外,您还应包括更多原因。否则,您不会受到太多关注
John Dvorak

1
当一年是a年而另一年不是a年时,这会中断
artm

如果去年2月有29天,则此计算将失败
phil88530

5

这个答案是最好的,请赞成。


我喜欢@philnash的解决方案,但条件可以更紧凑。布尔表达式的作用是使用字典顺序比较[month,day]对,因此可以只使用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
瑞安·吕

哇,真是太整齐了 您应该回答并收集奖励!
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

刚刚意识到是相同的
答案

1

因为Ruby on Rails 带有标签,所以dotiw gem覆盖了Rails内置的distance_of_times_in_words并提供了distance_of_times_in_words_hash可用于确定年龄。尽管应注意2月29日确实会对天数部分产生影响,但请务必了解understanding年部分的fine年,以便了解是否需要该详细程度。另外,如果您不喜欢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的答案,但IMO更容易理解。

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

假设我们正在使用rails,age在模型上调用方法,并且模型具有date数据库column dob。这与其他答案不同,因为此方法使用字符串来确定我们是否在今年的生日之前。

例如,如果dob是2004/2/28和2014/2/28 todayage则将是2014 - 200410。浮点数将为02280229b4bday将是"0228" < "0229"true。最后,我们将减去1age和获得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

👍🏼


只是,您的答案在逻辑上与philnash的相同。他的答案更加简洁,尽管不依赖Rails。
Mantas

@Mantas Philnash说他在Rails项目中使用了这种方法。标签中有滑轨。他的方法比我的方法有两个以上的比较。他的方法的最后一行很难理解。
Cruz Nunez

抱歉,但是philnash的代码比您的代码干净得多。而且,他的代码是一种简单的方法。您的依赖于具有“ dob”值。对于新用户而言,可能还不清楚。甚至对代码没有多大帮助。是的,您的最后一个样本摆脱了它。但是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
Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)

-1

要考虑leap年(并假设存在主动支持):

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天和leap年等。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.