24

This question was here for other languages, so let here be one for Ruby.

How do I calculate number of complete years that have passed from a given date? As you probably have guessed, that's to calculate person's age automatically. The closest one is distance_of_time_in_words Rails helper, so the following template

Jack is <%= distance_of_time_in_words (Time.now, Time.local(1950,03,22)) %> old.

yields

Jack is over 59 years old.

But I need more precise function that yields just number. Is there one?

If there exists some kind of Ruby on Rails helper function for this, this is OK, although pure Ruby solution would be better.

Edit: the gist of the question is that a non-approximate solution is needed. At the 2nd of March Jack should be 59 years old and the next day he should be 60 years old. Leap years and such should be taken into account.

P Shved
  • 96,026
  • 17
  • 121
  • 165

13 Answers13

41

Do you want age as people typically understand it, or are you looking for a precise measure of time elapsed? If the former, there is no need to worry about leap years and other complications. You simply need to compute a difference in years and reduce it if the person has not had a birthday yet this year. If the latter, you can convert seconds elapsed into years, as other answers have suggested.

def age_in_completed_years (bd, d)
    # Difference in years, less one if you have not had a birthday this year.
    a = d.year - bd.year
    a = a - 1 if (
         bd.month >  d.month or 
        (bd.month >= d.month and bd.day > d.day)
    )
    a
end

birthdate = Date.new(2000, 12, 15)
today     = Date.new(2009, 12, 14)

puts age_in_completed_years(birthdate, today)
FMc
  • 41,963
  • 13
  • 79
  • 132
  • 1
    It seems that not have you only answered the question, but also formulated the question in a better way :-) – P Shved Dec 15 '09 at 00:35
  • 1
    +1, but what if I was born on, say Date.new(1980, 2, 29) ? ;-) – Mike Woodhouse Dec 15 '09 at 08:54
  • 1
    @Mike Bummer -- no birthday for you. Good point. Fixed the code. – FMc Dec 15 '09 at 12:43
  • Somehow you knew that the &*^$@ leap-year problem wasn't going away that quietly... – Telemachus Dec 15 '09 at 12:43
  • @Telemachus Indeed! Old Man Leap Year is a persistent bastard. – FMc Dec 15 '09 at 13:58
  • Would be nice if there was just a `Timespan` class! Question: shouldn't `bd.day > d.day` be `>=` instead? I consider myself a year older on my birthday, not the day after my birthday. – Josh M. Sep 20 '13 at 11:59
  • @JoshM. Good question: no, the code works he other way around. First it gives you credit for the year (via the simple difference between the years); then it deducts 1 if you **haven't** had your birthday yet. As written, the code prints 8, but if you change the `today` value to Dec 15, you'll get 9. – FMc Sep 20 '13 at 13:59
6
require 'date'

def years_since(dt)
    delta = (Date.today - Date.parse(dt)) / 365
    delta.to_i
end
sh-beta
  • 3,809
  • 7
  • 27
  • 32
5

I have a gem/plugin called dotiw that has a distance_of_time_in_words_hash that will return a hash like: { :years => 59, :months => 11, :days => 27 }. From that you could work out if it's near a certain limit.

Ryan Bigg
  • 106,965
  • 23
  • 235
  • 261
5

I came up with the following, based on a similar reasoning as @FMc First, compute the diff between today's year and birthday's year. Then, sum it to birthday and check the resulting date: if it's greater than today, decrease diff by 1. To be used in Rails apps as it relies on ActiveSupport's years method

def age(birthday, today)
  diff = today.year - birthday.year
  (birthday + diff.years > today ) ? (diff - 1) : diff
end
4

An approach that handles leap years

Whenever you're calculating elapsed years since a date, you have to decide how to handle leap year. Here is my approach, which I think is very readable, and is able to take leap years in stride without using any "special case" logic.

def years_completed_since(start_date, end_date)

  if end_date < start_date
    raise ArgumentError.new(
      "End date supplied (#{end_date}) is before start date (#{start_date})"
    )
  end

  years_completed = end_date.year - start_date.year

  unless reached_anniversary_in_year_of(start_date, end_date)
    years_completed -= 1
  end

  years_completed
end

# No special logic required for leap day; its anniversary in a non-leap
# year is considered to have been reached on March 1.
def reached_anniversary_in_year_of(original_date, new_date)
  if new_date.month == original_date.month
    new_date.day >= original_date.day
  else
    new_date.month > original_date.month
  end
end
Nathan Long
  • 122,748
  • 97
  • 336
  • 451
2

you can use the ruby gem adroit-age

It works for leap years also..

age = AdroitAge.find_age("23/01/1990")

Update

require 'adroit-age'

dob =  Date.new(1990,1,23)
or
dob = "23/01/1990".to_date

age = dob.find_age
#=> 23
Raj Adroit
  • 3,828
  • 5
  • 31
  • 44
2

Same idea as FM but with a simplified if statement. Obviously, you could add a second argument instead of using current time.

def age(birthdate)
  now = DateTime.now
  age = now.year - birthdate.year
  age -= 1 if(now.yday < birthdate.yday)
  age
end
dingo sky
  • 1,445
  • 17
  • 15
  • 2
    `yday` is a cool funciton, thanks. Unfortunately, It will handle leap years differently, if the person is born later than in February. – P Shved Dec 17 '09 at 06:24
  • agree with Pavel. `yday` isn't useful for comparing dates that span years because of leap years. – Ben Scheirman Jan 07 '17 at 06:15
2

withing http://github.com/radar/dotiw

Jack is <%= distance_of_time_in_words (Time.now, Time.local(1950,03,22)) %> old.

produce

Jack is 60 years old
lda
  • 21
  • 1
1

I think this will always work, even for someone with a birthday near a leap day:

require 'date'

def calculate_age(start_date, end_date)
  end_date.year - start_date.year - ((end_date.month > start_date.month || (end_date.month == start_date.month && end_date.day >= start_date.day)) ? 0 : 1)
end

puts calculate_age( Date.strptime('03/02/1968', '%m/%d/%Y'), Date.strptime('03/02/2010', '%m/%d/%Y'))

The calculated age with this method in the example call above is 42, which is correct despite 1968 being a leap year and the birthday being near a leap day.

Plus, this way there is no need to create a local variable.

Glenn
  • 11
  • 1
1

d2.year - d1.year - (d2.month > d1.month || (d2.month == d1.month && d2.day >= d1.day) ? 0 : 1)

Ken Herbert
  • 5,205
  • 5
  • 28
  • 37
1

How about this:

def age_in_years(date)
  # Difference in years, less one if you have not had a birthday this year.
  today = Date.today
  age = today.year - date.year
  age = age - 1 if [date.day, date.month, today.year].join('/').to_date > Date.today
end
Jason Waldrip
  • 5,038
  • 8
  • 36
  • 59
0

How about something like:

def years_diff(from_time,to_time)
  (((to_time - from_time).abs)/ (365 * 24 * 60 * 60)).to_i
end

years_diff(Time.now,Time.local(1950,03,22)) #=> 59
years_diff(Time.now,Time.local(2009,03,22)) #=> 0
years_diff(Time.now,Time.local(2008,03,22)) #=> 1
khelll
  • 23,590
  • 15
  • 91
  • 109
  • 2
    This won't going to work when the current date is near birthday (leap years!), and for a person of more than 1460 years old it's going to yield ever increasing errors. – P Shved Dec 14 '09 at 23:47
0

To calculate number of Years and Months between two dates, I used this function

  def calculate_year_month(from_date, to_date)
    num_days = (from_date - to_date).to_i
    num_months = (num_days / 30) 
    num_years = (num_months / 12) 
    num_months = (num_months % 12) 
    return num_years.to_s + " year(s) and " + num_months.to_s + " month(s)" 
  end 
Shuaib Zahda
  • 205
  • 3
  • 11