Get person's age in Ruby

Ruby on-RailsRuby

Ruby on-Rails Problem Overview


I'd like to get a person's age from its birthday. now - birthday / 365 doesn't work, because some years have 366 days. I came up with the following code:

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

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

Is there a more Ruby'ish way to calculate age?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

I know I'm late to the party here, but the accepted answer will break horribly when trying to work out the age of someone born on the 29th February on a leap year. This is because the call to birthday.to_date.change(:year => now.year) creates an invalid date.

I used the following code instead:

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

Solution 2 - Ruby on-Rails

I've found this solution to work well and be readable for other people:

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

Easy and you don't need to worry about handling leap year and such.

Solution 3 - Ruby on-Rails

Use this:

def age
  now = Time.now.utc.to_date
  now.year - birthday.year - (birthday.to_date.change(:year => now.year) > now ? 1 : 0)
end

Solution 4 - Ruby on-Rails

One liner in Ruby on Rails (ActiveSupport). Handles leap years, leap seconds and all.

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

Logic from here - https://stackoverflow.com/questions/9/how-do-i-calculate-someones-age-in-c/11942#11942

Assuming both dates are in same timezone, if not call utc() before to_fs() on both.

Solution 5 - Ruby on-Rails

(Date.today.strftime('%Y%m%d').to_i - dob.strftime('%Y%m%d').to_i) / 10000

Solution 6 - Ruby on-Rails

My suggestion:

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

The trick is that the minus operation with Time returns seconds

Solution 7 - Ruby on-Rails

The answers so far are kinda weird. Your original attempt was pretty close to the right way to do this:

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

You will get a fractional result, so feel free to convert the result to an integer with to_i. This is a better solution because it correctly treats the date difference as a time period measured in days (or seconds in the case of the related Time class) since the event. Then a simple division by the number of days in a year gives you the age. When calculating age in years this way, as long as you retain the original DOB value, no allowance needs to be made for leap years.

Solution 8 - Ruby on-Rails

This answer is the best, upvote it instead.


I like @philnash's solution, but the conditional could be compacter. What that boolean expression does is comparing [month, day] pairs using lexicographic order, so one could just use ruby's string comparison instead:

def age(dob)
  now = Date.today
  now.year - dob.year - (now.strftime('%m%d') < dob.strftime('%m%d') ? 1 : 0)
end

Solution 9 - Ruby on-Rails

I like this one:

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

Solution 10 - Ruby on-Rails

This is a conversion of this answer (it's received a lot of votes):

# 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

It's definitely unique in its approach but makes perfect sense when you realise what it does:

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

Solution 11 - Ruby on-Rails

Because Ruby on Rails is tagged, the dotiw gem overrides the Rails built-in distance_of_times_in_words and provides distance_of_times_in_words_hash which can be used to determine the age. Leap years are handled fine for the years portion although be aware that Feb 29 does have an effect on the days portion that warrants understanding if that level of detail is needed. Also, if you don't like how dotiw changes the format of distance_of_time_in_words, use the :vague option to revert to the original format.

Add dotiw to the Gemfile:

gem 'dotiw'

On the command line:

bundle

Include the DateHelper in the appropriate model to gain access to distance_of_time_in_words and distance_of_time_in_words_hash. In this example the model is 'User' and the birthday field is 'birthday.

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

Add this method to that same model.

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

Usage:

u = User.new("birthday(1i)" => "2011", "birthday(2i)" => "10", "birthday(3i)" => "23")
u.age

Solution 12 - Ruby on-Rails

I believe this is functionally equivalent to @philnash's answer, but IMO more easily understandable.

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

Usage:

> BirthDate.new(Date.parse('1988-02-29')).time_ago_in_years
 => 31 

Solution 13 - Ruby on-Rails

class User
  def age
    return unless birthdate
    (Time.zone.now - birthdate.to_time) / 1.year
  end
end

Can be checked with the following test:

RSpec.describe User do
  describe "#age" do
    context "when born 29 years ago" do
      let!(:user) { create(:user, birthdate: 29.years.ago) }

      it "has an age of 29" do
        expect(user.age.round).to eq(29)
      end
    end
  end
end

Solution 14 - Ruby on-Rails

The following seems to work (but I'd appreciate it if it was checked).

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

Solution 15 - Ruby on-Rails

If you don't care about a day or two, this would be shorter and pretty self-explanitory.

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

Solution 16 - Ruby on-Rails

Ok what about this:

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

This is assuming we are using rails, calling the age method on a model, and the model has a date database column dob. This is different from other answers because this method uses strings to determine if we are before this year's birthday.

For example, if dob is 2004/2/28 and today is 2014/2/28, age will be 2014 - 2004 or 10. The floats will be 0228 and 0229. b4bday will be "0228" < "0229" or true. Finally, we will subtract 1 from age and get 9.

This would be the normal way to compare the two times.

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

This works the same, but the b4bday line is too long. The 2016 year is also unnecessary. The string comparison at the beginning was the result.

You can also do this

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

If you aren't using rails, try this

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

ļ‘ļ¼

Solution 17 - Ruby on-Rails

I think it's alot better to do not count months, because you can get exact day of a year by using 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

Solution 18 - Ruby on-Rails

Came up with a Rails variation of this solution

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

Solution 19 - Ruby on-Rails

DateHelper can be used to get years only

puts time_ago_in_words '1999-08-22'

> almost 20 years

Solution 20 - Ruby on-Rails

  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```

Solution 21 - Ruby on-Rails

  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

Solution 22 - Ruby on-Rails

Time.now.year - self.birthdate.year - (birthdate.to_date.change(:year => Time.now.year) > Time.now.to_date ? 1 : 0)

Solution 23 - Ruby on-Rails

To account for leap years (and assuming activesupport presence):

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 will correctly modify the date to take into account non-leap years (when birthday is 02-29).

Solution 24 - Ruby on-Rails

Here's my solution which also allows calculating the age at a specific date:

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

Solution 25 - Ruby on-Rails

I had to deal with this too, but for months. Became way too complicated. The simplest way I could think of was:

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

You could do the same with 12 months:

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

This will use Date class to increment the month, which will deal with 28 days and leap year etc.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMantasView Question on Stackoverflow
Solution 1 - Ruby on-RailsphilnashView Answer on Stackoverflow
Solution 2 - Ruby on-RailsPJ.View Answer on Stackoverflow
Solution 3 - Ruby on-RailsSadeghView Answer on Stackoverflow
Solution 4 - Ruby on-RailsVikrant ChaudharyView Answer on Stackoverflow
Solution 5 - Ruby on-RailspguardiarioView Answer on Stackoverflow
Solution 6 - Ruby on-RailsjlebrijoView Answer on Stackoverflow
Solution 7 - Ruby on-RailsBob AmanView Answer on Stackoverflow
Solution 8 - Ruby on-RailsartmView Answer on Stackoverflow
Solution 9 - Ruby on-RailstpalmView Answer on Stackoverflow
Solution 10 - Ruby on-Railsbr3ntView Answer on Stackoverflow
Solution 11 - Ruby on-RailsmindriotView Answer on Stackoverflow
Solution 12 - Ruby on-RailsJason SwettView Answer on Stackoverflow
Solution 13 - Ruby on-RailsDorianView Answer on Stackoverflow
Solution 14 - Ruby on-RailstestrView Answer on Stackoverflow
Solution 15 - Ruby on-RailshakuninView Answer on Stackoverflow
Solution 16 - Ruby on-RailsCruz NunezView Answer on Stackoverflow
Solution 17 - Ruby on-RailsOleksiy NosovView Answer on Stackoverflow
Solution 18 - Ruby on-Railsderosm2View Answer on Stackoverflow
Solution 19 - Ruby on-Railshsul4nView Answer on Stackoverflow
Solution 20 - Ruby on-RailsSandesh BodakeView Answer on Stackoverflow
Solution 21 - Ruby on-RailsBrianView Answer on Stackoverflow
Solution 22 - Ruby on-RailsTim Van GinderenView Answer on Stackoverflow
Solution 23 - Ruby on-RailsVitaly KushnerView Answer on Stackoverflow
Solution 24 - Ruby on-Railshurikhan77View Answer on Stackoverflow
Solution 25 - Ruby on-RailsMooktakim AhmedView Answer on Stackoverflow