Forum: Ruby on Rails calculate users age

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
pd (Guest)
on 2006-01-23 01:34
i know it's probably really simple, how do i work out someone's age if i
have their d.o.b. stored as a date in my db.

cheers
Joseph Chan (Guest)
on 2006-01-23 04:36
(Received via mailing list)
Depending on your database, how about doing something with the
datediff() or
similar function?    eg: for Postgresql:

select (current_timestamp-dob) / 365.25 from users

I'm sure you could
Pat M. (Guest)
on 2006-01-23 04:45
(Received via mailing list)
irb(main):001:0> require 'date'
=> true
irb(main):002:0> bday = Date.new(1985, 7, 24)
=> #<Date: 4892541/2,0,2299161>
irb(main):003:0> age = (Date.today - bday).to_i / 365
=> 20


This is ruby, so there's probably a better way.  There should be some
slick way of doing it like
bday.years.until(Date.today)  # This does not work.  But I think it'd
be cool if it did :)

Pat
Alexey V. (Guest)
on 2006-01-23 06:35
(Received via mailing list)
Two things to think about:

1. In Rails, database is for stoiring raw data, not for calculations.
There is nothing wrong in using it for calculations when you have some
good reason to do so, but by default you shouldn't do it.

2. days/365, as well as days/365.25 are both approximations (the former
is wrong more often than the latter). Sure, we can do better than that!

So, how about below?

Alex

require 'test/unit'
class BirthdayTest < Test::Unit::TestCase

  def test_simple
    assert_equal 100, age(Date.new(1905, 1, 1), Date.new(2005, 1, 1))
  end

  def test_age_born_this_year_is_zero
    assert_equal 0, age(Date.new(2005, 1, 1), Date.new(2005, 12, 31))
    assert_equal 1, age(Date.new(2005, 1, 1), Date.new(2006, 1, 1))
  end

  def test_doesnt_like_negative_age
    birthdate = Date.new(2005, 1, 2)
    day_before_birthdate = birthdate - 1
    assert_raises(RuntimeError) { age(birthdate,  day_before_birthdate)
}
  end

  def test_works_well_when_birthday_already_passed_this_year
    assert_equal 9, age(Date.new(1990, 2, 2), Date.new(2000, 2, 1))
    assert_equal 10, age(Date.new(1990, 2, 2), Date.new(2000, 2, 2))
  end

  def test_handles_feb_29
    assert_equal 4, age(Date.new(2000, 2, 29), Date.new(2005, 2, 28))
    assert_equal 5, age(Date.new(2000, 2, 29), Date.new(2005, 3, 1))
  end

  def test_handles_many_years
    assert_equal 6000, age(Date.new(-3000, 1, 1), Date.new(3000, 12,
31))
  end

end

def age(birthdate, now)
  raise "Birthdate (#{birthdate.strftime('%Y-%m-%d')}) is after now
(#{now.strftime('%Y-%m-%d')})" if birthdate > now

  had_birthday_passed_this_year = (now.month * 100 + now.day >=
birthdate.month * 100 + birthdate.day)
  if had_birthday_passed_this_year
    now.year - birthdate.year
  else
    now.year - birthdate.year - 1
  end
end
SB (Guest)
on 2006-01-23 16:47
(Received via mailing list)
Weird, I was just writing something like this in the course of
learning ruby.  I doubt you'll learn anything by just posting a random
question and expecting an answer.  You should at least give it a stab.

Hopefully, a more experienced ruby programmer will give me advice to
improve my code.


Here's my attempt:

birthday.rb code

require "time"

class Birthday

#This can be slimmed down considerably
#but I thought it might be useful to keep them all separate

def initialize(birth_month, birth_date, birth_year)
  @birth_month = birth_month
  @birth_date = birth_date
  @birth_year = birth_year
end

#This puts the birthday in a format that can be manipulated
def bday
  bday = @birth_month + "/" + @birth_date + "/" + @birth_year
  Time.parse(bday)
end

#This will round off the age
def age
  age = ((Time.now - self.bday)/60/60/24/365).to_i
end

end


If you load this from irb using irb> load 'birthday.rb'

you can put in

irb>    john = Birthday.new("1", "1", "1984")
irb>    john.age
Norman T. (Guest)
on 2006-01-23 18:06
(Received via mailing list)
Am Sonntag, den 22.01.2006, 21:33 -0700 schrieb Alexey V.:

>   end
> end

I made a small enhancement for the Date class from it:

class date
  def age(today = self.class.today)
    today.year - year - ((today.month * 100 + today.day >= month * 100 +
day) ? 0 : 1)
  end
end

If no attribute is passed, it calculates the age for today. You can pass
a Date object to calculate the age on a specific date.

--
Norman T.

http://blog.inlet-media.de
Paul B. (Guest)
on 2006-01-24 08:03
(Received via mailing list)
When I copy the text and save it into a file called BirthdayTest.rb and
then
try to run it, I get these errors.  It seems like Date.new doesn't like
having 3 arguments.  I am using Ruby 1.8.2 on Win XP.  Any idea why this
is?

Loaded suite BirthdayTest
Started
EEEEEE
Finished in 0.0 seconds.

  1) Error:
test_age_born_this_year_is_zero(BirthdayTest):
ArgumentError: wrong number of arguments (3 for 0)
    BirthdayTest.rb:9:in `initialize'
    BirthdayTest.rb:9:in `new'
    BirthdayTest.rb:9:in `test_age_born_this_year_is_zero'

  2) Error:
test_doesnt_like_negative_age(BirthdayTest):
ArgumentError: wrong number of arguments (3 for 0)
    BirthdayTest.rb:14:in `initialize'
    BirthdayTest.rb:14:in `new'
    BirthdayTest.rb:14:in `test_doesnt_like_negative_age'

  3) Error:
test_handles_feb_29(BirthdayTest):
ArgumentError: wrong number of arguments (3 for 0)
    BirthdayTest.rb:25:in `initialize'
    BirthdayTest.rb:25:in `new'
    BirthdayTest.rb:25:in `test_handles_feb_29'

  4) Error:
test_handles_many_years(BirthdayTest):
ArgumentError: wrong number of arguments (3 for 0)
    BirthdayTest.rb:30:in `initialize'
    BirthdayTest.rb:30:in `new'
    BirthdayTest.rb:30:in `test_handles_many_years'

  5) Error:
test_simple(BirthdayTest):
ArgumentError: wrong number of arguments (3 for 0)
    BirthdayTest.rb:5:in `initialize'
    BirthdayTest.rb:5:in `new'
    BirthdayTest.rb:5:in `test_simple'

  6) Error:
test_works_well_when_birthday_already_passed_this_year(BirthdayTest):
ArgumentError: wrong number of arguments (3 for 0)
    BirthdayTest.rb:20:in `initialize'
    BirthdayTest.rb:20:in `new'
    BirthdayTest.rb:20:in
`test_works_well_when_birthday_already_passed_this_year'

6 tests, 0 assertions, 0 failures, 6 errors
Alexey V. (Guest)
on 2006-01-24 08:09
(Received via mailing list)
Paul B. wrote:

> When I copy the text and save it into a file called BirthdayTest.rb
> and then try to run it, I get these errors.  It seems like Date.new
> doesn't like having 3 arguments.  I am using Ruby 1.8.2 on Win XP.
> Any idea why this is?

Hmm... worksforme (TM)

C:\eclipse\workspace\i2>ruby -v
ruby 1.8.2 (2004-12-25) [i386-mswin32]

C:\eclipse\workspace\i2>irb
irb(main):001:0> Date.new(2000, 1, 1)
=> #<Date: 4903089/2,0,2299161>

C:\>age
Loaded suite C:/age
Started
......
Finished in 0.031 seconds.

6 tests, 9 assertions, 0 failures, 0 errors

Alex
Paul B. (Guest)
on 2006-01-24 08:18
(Received via mailing list)
Weird...

E:\home\pbarry\projects\rails>ruby -v
ruby 1.8.2 (2004-12-25) [i386-mswin32]

E:\home\pbarry\projects\rails>irb
irb(main):001:0> Date.new(2000,1,1)
ArgumentError: wrong number of arguments (3 for 0)
        from (irb):1:in `initialize'
        from (irb):1:in `new'
        from (irb):1
Norman T. (Guest)
on 2006-01-24 10:17
(Received via mailing list)
Am Dienstag, den 24.01.2006, 01:17 -0500 schrieb Paul B.:
>         from (irb):1
>

On my Ubuntu i have to require 'date', before i can use it.

irb(main):001:0> Date.new(2006,1,1)
NameError: uninitialized constant Date
        from (irb):1
irb(main):002:0> require 'date'
=> true
irb(main):003:0> Date.new(2006,1,1)
=> #<Date: 4907473/2,0,2299161>
irb(main):004:0>

Maybe this helps.

--
Norman T.

http://blog.inlet-media.de
Jonas Elfström (Guest)
on 2006-01-24 10:56
(Received via mailing list)
On 1/23/06, SB <removed_email_address@domain.invalid> wrote:
> Weird, I was just writing something like this in the course of
> learning ruby.  I doubt you'll learn anything by just posting a random
> question and expecting an answer.  You should at least give it a stab.
>
> Hopefully, a more experienced ruby programmer will give me advice to
> improve my code.
>
> Here's my attempt:
> birthday.rb code
> require "time"

I would not recommend using the class time here since it does not
handle dates before 1970-01-01:
irb> Time.parse("1970-01-01 01:00")
=> Thu Jan 01 01:00:00 W. Europe Standard Time 1970
irb> Time.parse("1969-12-31")
ArgumentError: time out of range
irb> Time.parse("1970-01-01 00:59")
ArgumentError: time out of range

Try the Date in date.rb instead.
http://www.ruby-doc.org/core/files/lib/date_rb.html

Something like this looks like it works:
born=Date.strptime("1969-07-21")
age=Date.today.year-born.year
age=age-1 if Date.today.yday<born.yday

--
Jonas
Elfström
SB (Guest)
on 2006-01-24 11:48
(Received via mailing list)
Using 'require "time"' works fine on my computer (Mac OSX).  Jan 1,
1970 is the epoch used in the Time class of ruby and might cause
trouble on other operating systems (according to the pickaxe).

However, I agree that using the Date library will probably make my
code more compact and offer more options.

Thanks for pointing this out.

Sam
Alan (Guest)
on 2006-01-24 12:57
Neatest method I found was posted by Justing in the forum, not so long
ago :
http://wrath.rubyonrails.org/pipermail/rails/2005-...

def age_at(date, dob)
  day_diff = date.day - dob.day
  month_diff = date.month - dob.month - (day_diff < 0 ? 1 : 0)
  date.year - dob.year - (month_diff < 0 ? 1 : 0)
end
Jonas Elfström (Guest)
on 2006-01-24 14:31
(Received via mailing list)
On 1/24/06, Alan <removed_email_address@domain.invalid> wrote:
> Neatest method I found was posted by Justing in the forum, not so long
> ago :
> http://wrath.rubyonrails.org/pipermail/rails/2005-...
>
> def age_at(date, dob)
>   day_diff = date.day - dob.day
>   month_diff = date.month - dob.month - (day_diff < 0 ? 1 : 0)
>   date.year - dob.year - (month_diff < 0 ? 1 : 0)
> end

Sorry, could not help myself:

def age_at(date, dob)
  date.year - dob.year - ( (date.yday-dob.yday) < 0 ? 1 : 0 )
end

And now something for the swedish readers. Check out my validation of
swedish "social security number" in Ruby:
http://plea.se/me/validatePnum.html

--
Jonas
Elfström
Paul B. (Guest)
on 2006-01-24 17:48
(Received via mailing list)
I believe this would techincally not work in leap year, right?
Paul B. (Guest)
on 2006-01-24 17:51
(Received via mailing list)
So my desktop has the problem as listed in the previous email, but it
seems
to work on my laptop fine:

C:\Documents and Settings\PBarry>ruby -v
ruby 1.8.2 (2004-12-25) [i386-mswin32]

C:\Documents and Settings\PBarry>irb
irb(main):001:0> Date.new(2000,1,1)
=> #<Date: 4903089/2,0,2299161>

But on linux, it doesn't work:

[root@paulbarry ~]# ruby -v
ruby 1.8.4 (2005-12-24) [i386-linux]
[root@paulbarry ~]# irb
irb(main):001:0> Date.new(2000,1,1)
NameError: uninitialized constant Date
        from (irb):1

Any idea what could cause these differences?  3 different environments,
three different results for the same 1 line of code.  Kind of makes me
worried that code I write on one machine will not work on another.
Jonas Elfström (Guest)
on 2006-01-24 18:28
(Received via mailing list)
On 1/24/06, Paul B. <removed_email_address@domain.invalid> wrote:
> I believe this would techincally not work in leap year, right?

Why not? yday is 1 to 366 in a leap year.
I tested a few dates in and out of leap years and it seems to work
just fine. If you find a case that breaks the functionality please
tell me. Maybe we should take it offlist...

--
/J

> > >   month_diff = date.month - dob.month - (day_diff < 0 ? 1 : 0)
> > >   date.year - dob.year - (month_diff < 0 ? 1 : 0)
> > > end
> >
> > Sorry, could not help myself:
> >
> > def age_at(date, dob)
> >   date.year - dob.year - ( (date.yday-dob.yday) < 0 ? 1 : 0 )
> > end

--
Jonas
Elfström
Jonas Elfström (Guest)
on 2006-01-24 18:34
(Received via mailing list)
On 1/24/06, Paul B. <removed_email_address@domain.invalid> wrote:
>  But on linux, it doesn't work:
>  [root@paulbarry ~]# irb
>  irb(main):001:0> Date.new(2000,1,1)
>  NameError: uninitialized constant Date
>          from (irb):1
>
>  Any idea what could cause these differences?

Somehow the date class is preloaded in some of your installations and
in some not, I leave the story behind that to the wizards. Always do:
require 'date.rb'
and you will be fine in any environment.

--
Jonas
Elfström
Paul B. (Guest)
on 2006-01-24 22:38
(Received via mailing list)
it doesn't work because Date.new(2008,3,1).yday is 61, whereas
Date.new(2007,3,1).yday
is 60.  Looks like Alekexy's age method is correct and the age_at method
that relies on yday is incorrect:

require 'test/unit'
class BirthdayTest < Test::Unit::TestCase

 def test_age_at_non_leap_year_with_leap_year_dob
   assert_equal 19, age_at(Date.new(2007, 3, 1), Date.new(1988, 3, 1))
 end

 def test_age_at_leap_year_with_leap_year_dob
   assert_equal 20, age_at(Date.new(2008, 3, 1), Date.new(1988, 3, 1))
 end

 def test_age_at_non_leap_year_with_non_leap_year_dob
   assert_equal 20, age_at(Date.new(2007, 3, 1), Date.new(1987, 3, 1))
 end

 def test_age_at_leap_year_with_non_leap_year_dob
   assert_equal 21, age_at(Date.new(2008, 3, 1), Date.new(1987, 3, 1))
 end

 def test_age_non_leap_year_with_leap_year_dob
   assert_equal 19, age(Date.new(2007, 3, 1), Date.new(1988, 3, 1))
 end

 def test_age_leap_year_with_leap_year_dob
   assert_equal 20, age(Date.new(2008, 3, 1), Date.new(1988, 3, 1))
 end

 def test_age_non_leap_year_with_non_leap_year_dob
   assert_equal 20, age(Date.new(2007, 3, 1), Date.new(1987, 3, 1))
 end

 def test_age_leap_year_with_non_leap_year_dob
   assert_equal 21, age(Date.new(2008, 3, 1), Date.new(1987, 3, 1))
 end

end

def age_at(date, dob)
  date.year - dob.year - ( (date.yday-dob.yday) < 0 ? 1 : 0 )
end

def age(now, birthdate)
 had_birthday_passed_this_year = (now.month * 100 + now.day >=
birthdate.month * 100 + birthdate.day)
  if had_birthday_passed_this_year
    now.year - birthdate.year
  else
    now.year - birthdate.year - 1
  end
end
Paul B. (Guest)
on 2006-01-24 22:47
(Received via mailing list)
This shows the errors in age_at better:

require 'test/unit'
class BirthdayTest < Test::Unit::TestCase

 def test_age_at_non_leap_year_with_leap_year_dob
   assert_equal 19, age_at(Date.new(2007, 3, 1), Date.new(1988, 3, 1))
 end

 def test_age_at_leap_year_with_non_leap_year_dob
   assert_equal 20, age_at(Date.new(2008, 2, 29), Date.new(1987, 3, 1))
 end

 def test_age_non_leap_year_with_leap_year_dob
   assert_equal 19, age(Date.new(2007, 3, 1), Date.new(1988, 3, 1))
 end

 def test_age_leap_year_with_non_leap_year_dob
   assert_equal 20, age(Date.new(2008, 2, 29), Date.new(1987, 3, 1))
 end

end

def age_at(date, dob)
  date.year - dob.year - ( (date.yday-dob.yday) < 0 ? 1 : 0 )
end

def age(now, birthdate)
 had_birthday_passed_this_year = (now.month * 100 + now.day >=
birthdate.month * 100 + birthdate.day)
  if had_birthday_passed_this_year
    now.year - birthdate.year
  else
    now.year - birthdate.year - 1
  end
end
Jonas Elfström (Guest)
on 2006-01-25 10:35
(Received via mailing list)
On 1/24/06, Paul B. <removed_email_address@domain.invalid> wrote:
> This shows the errors in age_at better:

Thanks for pointing this out! I can't imagine why I did not think of
this.

I am not in any way an endorser of trying to make code as short as
possible but out of curiosity I fixed my one-line class so that it now
correct but "unreadable":

def age_at(date, dob)
  date.year - dob.year - (date.month-dob.month < 0 ? 1 :
date.day-dob.day < 0 ? 1:0)
end

--
Jonas
Elfström
Norman T. (Guest)
on 2006-01-25 17:41
(Received via mailing list)
Am Montag, den 23.01.2006, 17:05 +0100 schrieb Norman T.:
> I made a small enhancement for the Date class from it:
>
> class date
>   def age(today = self.class.today)
>     today.year - year - ((today.month * 100 + today.day >= month * 100 +
> day) ? 0 : 1)
>   end
> end

I cleaned it a up a bit and made it more readable:

class Date
  def age(base = self.class.today)
    base.year - year - ((base.month * 100 + base.day >= month * 100 +
day) ? 0 : 1)
  end
end

Use it like that:

# to get the age for today
Date.new(1978,4,20).age

# to get the age for January, the 1st, 2020
Date.new(1978,4,20).age(Date.new(2020,1,1))

You can validate the age of a person in this simple way:

class Person < ActiveRecord::Base
  validates_inclusion_of :age, :in=>18..99

  def age
    # expecting that you have an attribute 'date_of_birth'
    # filled with a Date object
    date_of_birth.age
  end
end
--
Norman T.

http://blog.inlet-media.de
stiv (Guest)
on 2006-01-29 03:57
Here is a little bit i just put together.
I've not made it pretty and have only done some basic testing.

Context: this function is in a model which has a Date field called dob
it returns the age in "y years m months d days old" format skipping
values that are empty and pluralizing year month and day as appropriate.

ex: (Time.now = 2006/01/28)
dob               [time_ago_in_words]   [object.age_in_words]
2004-02-21 (age = 707 days)         (1 year 11 months 7 days old)
2002-01-28 (age = 1461 days)       (4 years old)
2005-08-20 (age = 161 days)         (5 months 8 days old)


===========================
	def age_in_words
		if dob == nil
		 "--"
		else
		 now = Time.now
		 ydiff = now.year - dob.year
		 mdiff = now.month - dob.month
		 ddiff = now.day - dob.day
		 if ((mdiff < 0) && (ydiff > 0))
		 	y = ydiff - 1
		 	m = mdiff + 12
		 else
		   y = ydiff
		   m = mdiff
		 end
		 if ((ddiff < 0) && (mdiff != 0))
		 	m = m - 1
		 	dim = Time.days_in_month(dob.month+m,dob.year+y)
		 	d = (dim - dob.day) + now.day
		 else
		 	d = ddiff
		 end

		end
		ys = "year"
		ms = "month"
		ds = "day"
		if (y.abs > 1)
			ys= ys.pluralize
		end
		if (m.abs > 1)
			ms = ms.pluralize
		end
		if (d.abs > 1)
			ds = ds.pluralize
		end
		year_string = (y !=  0)?"#{y} #{ys} ":""
		month_string = (m != 0)?"#{m} #{ms} ":""
		day_string = (d != 0)?"#{d} #{ds}":""
		return "#{year_string}#{month_string}#{day_string} old"
	end
This topic is locked and can not be replied to.