Calculating the age given DOB

D

Deniz Dogan

Hello, fellow rubyists.

I have a problem. I'm trying to write a simple program which calculates
the age of a person given the person's date of birth in an instance of a
Date class. I have no idea what is the best way to do this. I don't feel
like using any ugly "hacks" to do it, and I wonder if there is any good
(perhaps mathematical) algorithm out there to solve the problem.

--Deniz Dogan
 
B

Brian Adkins

Deniz said:
Hello, fellow rubyists.

I have a problem. I'm trying to write a simple program which calculates
the age of a person given the person's date of birth in an instance of a
Date class. I have no idea what is the best way to do this. I don't feel
like using any ugly "hacks" to do it, and I wonder if there is any good
(perhaps mathematical) algorithm out there to solve the problem.

--Deniz Dogan


(DateTime.now - birthdate) / 365.25
 
C

Clifford Heath

Brian said:
(DateTime.now - birthdate) / 365.25

That's not very reliable. There are algorithms that calculate
the number of days since some epoch from a date, and the
reverse, based on Fortran code that was posted in the CACM
sometime around 1970. I have a copy somewhere, but there
must be a modern version already in Ruby. If you can't
find a way, I'll dig out mine.

Just convert both dates to days-since-epoch and subtract.

Clifford.
 
B

Brian Adkins

Clifford said:
That's not very reliable. There are algorithms that calculate
the number of days since some epoch from a date, and the
reverse, based on Fortran code that was posted in the CACM
sometime around 1970. I have a copy somewhere, but there
must be a modern version already in Ruby. If you can't
find a way, I'll dig out mine.

Just convert both dates to days-since-epoch and subtract.

Clifford.

Do you mind if I ask why you make the statement, "That's not very
reliable." ? For what values of birthdate (an instance of Date) would
the above expression not be "reliable" ?
 
R

Reid Thompson

Hello, fellow rubyists.

I have a problem. I'm trying to write a simple program which calculates
the age of a person given the person's date of birth in an instance of a
Date class. I have no idea what is the best way to do this. I don't feel
like using any ugly "hacks" to do it, and I wonder if there is any good
(perhaps mathematical) algorithm out there to solve the problem.

--Deniz Dogan
rthompso@jhereg:~$ irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> bd = Date.new(1966, 1, 21)
=> #<Date: 4878293/2,0,2299161>
irb(main):003:0> today = Date.new(2007,3,14)
=> #<Date: 4908347/2,0,2299161>
irb(main):004:0> age = today.year - bd.year
=> 41
 
B

Brian Adkins

Reid said:
rthompso@jhereg:~$ irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> bd = Date.new(1966, 1, 21)
=> #<Date: 4878293/2,0,2299161>
irb(main):003:0> today = Date.new(2007,3,14)
=> #<Date: 4908347/2,0,2299161>
irb(main):004:0> age = today.year - bd.year
=> 41

Consider when today.month < bd.month - you'll have an off-by-one error.
 
C

Clifford Heath

Brian said:
Do you mind if I ask why you make the statement, "That's not very
reliable." ?

I have written genealogy software, and sometimes I want to know
how old someone was on a given date, like when they married.
1900 was not a leap year, as I'm sure you know. Neither was
1800 or 1700, but 1600 was. Does that make more sense now?

If I write a "library function" like this, I like to be a stickler,
because it's usually myself that gets tripped up.
 
P

Pit Capitain

Brian said:
Consider when today.month < bd.month - you'll have an off-by-one error.

age -= 1 if today.month < bd.month || today.month == bd.month &&
today.day < bd.day

I don't know how people born at February 29th celebrate their birthday
in non-leap years. The code above makes them one year older on March 1st.

Regards,
Pit
 
J

John Joyce

I think we can honestly assume that if you are born on leap day, you
become highly tolerant of systems that don't even accept leap day
birthdays and probably write Feb 28 or Mar 1 consistently by habit.
 
B

Brian Adkins

Clifford said:
I have written genealogy software, and sometimes I want to know
how old someone was on a given date, like when they married.
1900 was not a leap year, as I'm sure you know. Neither was
1800 or 1700, but 1600 was. Does that make more sense now?

If I write a "library function" like this, I like to be a stickler,
because it's usually myself that gets tripped up.

Gotcha. I saw "simple program" and was thinking more in terms of
approximate real values vs. discrete values (e.g. gaining a year of age
in an instant) which is why I didn't throw in a round. My guess is that
the OP probably wants the discrete value though, so the other part of
the thread should do the trick.
 
F

F. Senault

Le 14 mars à 15:47, Deniz Dogan a écrit :
Hello, fellow rubyists.

I have a problem. I'm trying to write a simple program which calculates
the age of a person given the person's date of birth in an instance of a
Date class. I have no idea what is the best way to do this. I don't feel
like using any ugly "hacks" to do it, and I wonder if there is any good
(perhaps mathematical) algorithm out there to solve the problem.

Depends on what you want exactly. If you want to compute the "usual"
age (i.e. I'll say I'm 31 years old, not 31.7426420260096), you might
try this :
=> 32

Fred
 
P

Pit Capitain

Pardon Fred, using #yday would be short and nice, but this doesn't work
reliably if one year is a leap year and the other is not. Try it with

d1 = Date.new(2007, 3, 1); d2 = Date.new(2004, 3, 1)
d1 = Date.new(2004, 3, 1); d2 = Date.new(2001, 3, 2)

Regards,
Pit
 
F

F. Senault

Le 14 mars 2007 à 20:50, Pit Capitain a écrit :
Pardon Fred, using #yday would be short and nice, but this doesn't work
reliably if one year is a leap year and the other is not.

Yup, you're perfectly right. The following version seems to work, but
we're steadily losing elegance... :)
=> 2

A pity Date.new doesn't handle overflows. I'd like to be able to write
this :
=> '2007-02-28'

Fred
 
N

Nando Sanchez

Hi! I had this idea, hope it helps:

class Date
def elapsedYearsAndDays(rangeDate)
if self > rangeDate
startDate = rangeDate.clone #clone the values so they are not
changed unintentionally
endDate = self.clone
else
startDate = self.clone
endDate = rangeDate.clone
end
magicDay = (startDate.month == 2) && (startDate.day == 29) #This
is the real problem: Febraury 29th!
startDate += 1 if !endDate.leap? && magicDay
elapsedYears = endDate.year - startDate.year
previousStartDate = Date.new(endDate.year, startDate.month,
startDate.day)
if endDate < previousStartDate
elapsedYears -= 1
previousStartDate = Date.new(endDate.year - 1, startDate.month,
startDate.day)
previousStartDate -= 1 if previousStartDate.leap? && magicDay
end
elapsedDays = endDate - previousStartDate
return elapsedYears, elapsedDays
end
end

elapsedYears, elapsedDays =
Date.today.elapsedYearsAndDays(Date.new(1963, 11, 22))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"

elapsedYears, elapsedDays = Date.new(1963, 11,
22).elapsedYearsAndDays(Date.new(1963, 11, 22))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"

elapsedYears, elapsedDays = Date.new(2007, 3,
1).elapsedYearsAndDays(Date.new(2004, 3, 1))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"

elapsedYears, elapsedDays = Date.new(2004, 3,
1).elapsedYearsAndDays(Date.new(2001, 3, 2))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"

elapsedYears, elapsedDays = Date.new(2004, 2,
29).elapsedYearsAndDays(Date.new(2001, 3, 1))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"

elapsedYears, elapsedDays = Date.new(2004, 3,
1).elapsedYearsAndDays(Date.new(2001, 3, 1))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"

elapsedYears, elapsedDays = Date.new(2004, 2,
29).elapsedYearsAndDays(Date.new(2009, 3, 1))
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ""
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}"
 
R

Rick DeNatale

I think that this does the right thing:

rick@frodo:/public/rubyscripts$ cat datemath.rb
#!/usr/math/bin/ruby


require 'date'
class Date

# return the number of days since the beginning of the year
def years_since(date)
# The parens in the expression below aren't strictly necessary, but
# I think it makes what's going on a little bit clearer.
first, last = *(self >= date ? [date, self] : [self, date])
(self <=> date) * ((last.year - first.year) - (first.yday >
last.yday ? 1 : 0))
end
end

def tryit(d1,d2)
puts "There are #{d1.years_since(d2)} years between #{d2} and #{d1}"
end

tryit(Date.new(2007,3,15), Date.new(2000,3,14))
tryit(Date.new(2000,3,14), Date.new(2007,3,15))
tryit(Date.new(2007,3,15), Date.new(2000,3,15))
tryit(Date.new(2007,3,15), Date.new(2000,3,16))
tryit(Date.new(2000,3,16), Date.new(2007,3,15))

tryit(Date.new(2007,2,27), Date.new(2000,2,29))
tryit(Date.new(2000,2,29), Date.new(2000,2,27))
tryit(Date.new(2007,2,28), Date.new(2000,2,29))
tryit(Date.new(2000,2,29), Date.new(2007,2,28))
tryit(Date.new(2000,2,29), Date.new(2007,3,1))
tryit(Date.new(2007,3,1), Date.new(2000,2,29))
tryit(Date.new(2007,3,2), Date.new(2000,2,29))
tryit(Date.new(2000,2,29), Date.new(2007,3,2))

rick@frodo:/public/rubyscripts$ ruby datemath.rb
There are 7 years between 2000-03-14 and 2007-03-15
There are -7 years between 2007-03-15 and 2000-03-14
There are 6 years between 2000-03-15 and 2007-03-15
There are 6 years between 2000-03-16 and 2007-03-15
There are -6 years between 2007-03-15 and 2000-03-16
There are 6 years between 2000-02-29 and 2007-02-27
There are 0 years between 2000-02-27 and 2000-02-29
There are 6 years between 2000-02-29 and 2007-02-28
There are -6 years between 2007-02-28 and 2000-02-29
There are -7 years between 2007-03-01 and 2000-02-29
There are 7 years between 2000-02-29 and 2007-03-01
There are 7 years between 2000-02-29 and 2007-03-02
There are -7 years between 2007-03-02 and 2000-02-29
 
B

Brian Candler

rick@frodo:/public/rubyscripts$ cat datemath.rb
#!/usr/math/bin/ruby


require 'date'
class Date

# return the number of days since the beginning of the year
def years_since(date)
# The parens in the expression below aren't strictly necessary, but
# I think it makes what's going on a little bit clearer.
first, last = *(self >= date ? [date, self] : [self, date])
(self <=> date) * ((last.year - first.year) - (first.yday >
last.yday ? 1 : 0))
end
end

I guess the most generic solution would return the number of [years, months,
days] between two dates - so you can say "You are 31 years, 3 months and 4
days old"

Anyone can work out their own age in this format, so the algorithm should be
clear, albeit probably messy to implement.
 
N

Nando Sanchez

Very nice piece of code Rick! I'm a Ruby newbee and I find it facinating
when I see code that applies better the "Ruby Way". Thanks for sharing.

I borrow parts of your code and rewrote the method I sent:

class Date
def elapsedYearsAndDays(rangeDate)
startDate, endDate = *(self >= rangeDate ? [rangeDate, self] :
[self, rangeDate])
#This is the real problem: Febraury 29th!
isMagicDate = (startDate.month == 2) && (startDate.day == 29)
startDate += 1 if !endDate.leap? && isMagicDate
elapsedYears = endDate.year - startDate.year
previousStartDate = Date.new(endDate.year, startDate.month,
startDate.day)
if endDate < previousStartDate
elapsedYears -= 1
previousStartDate = Date.new(endDate.year - 1, startDate.month,
startDate.day)
previousStartDate -= 1 if previousStartDate.leap? && isMagicDate
end
return elapsedYears, endDate - previousStartDate
end
end

startDates = [Date.today,
Date.new(1963, 11, 22),
Date.new(2007, 3, 1),
Date.new(2004, 3, 1),
Date.new(2004, 2, 29),
Date.new(2004, 3, 1),
Date.new(2004, 2, 29),
Date.new(2007,3,15),
Date.new(2000,3,14)]

endDates = [Date.new(1963, 11, 22),
Date.new(1963, 11, 22),
Date.new(2004, 3, 1),
Date.new(2001, 3, 2),
Date.new(2001, 3, 1),
Date.new(2001, 3, 1),
Date.new(2009, 3, 1),
Date.new(2000,3,14),
Date.new(2007,3,15)]

startDates.each_with_index do |startDate, index|
elapsedYears, elapsedDays =
startDate.elapsedYearsAndDays(endDates[index])
daysMessage = (elapsedDays > 0) ? (" and #{elapsedDays} day(s)") : ''
puts "Start date: #{startDate} End date: #{endDates[index]}"
puts "Elapsed time: #{elapsedYears} year(s)#{daysMessage}\n\n"
end

=begin
---------------------------------------------------------------------------
At the same time I took your code and added the same test dates I used:
=end

require 'date'
class Date

# return the number of days since the beginning of the year
def years_since(date)
# The parens in the expression below aren't strictly necessary, but
# I think it makes what's going on a little bit clearer.
first, last = *(self >= date ? [date, self] : [self, date])
(self <=> date) * ((last.year - first.year) - (first.yday >
last.yday ? 1 : 0))
end
end

startDates = [Date.today,
Date.new(1963, 11, 22),
Date.new(2007, 3, 1),
Date.new(2004, 3, 1),
Date.new(2004, 2, 29),
Date.new(2004, 3, 1),
Date.new(2004, 2, 29),
Date.new(2007,3,15),
Date.new(2000,3,14)]

endDates = [Date.new(1963, 11, 22),
Date.new(1963, 11, 22),
Date.new(2004, 3, 1),
Date.new(2001, 3, 2),
Date.new(2001, 3, 1),
Date.new(2001, 3, 1),
Date.new(2009, 3, 1),
Date.new(2000,3,14),
Date.new(2007,3,15)]

startDates.each_with_index do |startDate, index|
elapsedYears = startDate.years_since(endDates[index])
puts "Start date: #{startDate} End date: #{endDates[index]}"
puts "Elapsed time: #{elapsedYears} year(s)\n\n"
end

=begin
As you can see from the results, both pieces of code give the same
results
except when a leap year is involved. I think my version is correct in
those cases :) because we don't want to say that a complete year has
passed until that is totally true.
Regards,

Nando
=end
 
P

Pit Capitain

Rick said:
I think that this does the right thing:
(...)

tryit(Date.new(2007,3,1), Date.new(2004,3,1))
tryit(Date.new(2004,3,1), Date.new(2001,3,2))

# =>

There are 2 years between 2004-03-01 and 2007-03-01
There are 3 years between 2001-03-02 and 2004-03-01

See [ruby-talk:243700].

Regards,
Pit
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,237
Messages
2,571,190
Members
47,827
Latest member
wyton

Latest Threads

Top