Forum: Ruby Calculating the age given DOB

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.
Deniz D. (Guest)
on 2007-03-14 16:56
(Received via mailing list)
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 D.
John J. (Guest)
on 2007-03-14 17:20
(Received via mailing list)
Here is a free Ruby Favicon for all who wish to use it.
Just place it in the top directory of a site (or the appropriate
place for a web app) and do the appropriate html.
John J.
Brian A. (Guest)
on 2007-03-14 17:45
(Received via mailing list)
Deniz D. wrote:
> 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 D.


(DateTime.now - birthdate) / 365.25
Clifford H. (Guest)
on 2007-03-14 18:11
(Received via mailing list)
Brian A. wrote:
> (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.
Deniz D. (Guest)
on 2007-03-14 18:15
(Received via mailing list)
Brian A. wrote:
>> --Deniz D.
>
>
> (DateTime.now - birthdate) / 365.25

And this would work with Date objects?
Reid T. (Guest)
on 2007-03-14 18:34
(Received via mailing list)
On Wed, 2007-03-14 at 23:55 +0900, Deniz D. wrote:
> 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 D.
>
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
Brian A. (Guest)
on 2007-03-14 18:35
(Received via mailing list)
Deniz D. wrote:
>>>
>>> --Deniz D.
>>
>>
>> (DateTime.now - birthdate) / 365.25
>
> And this would work with Date objects?

Why don't you give it a try?
Brian A. (Guest)
on 2007-03-14 18:36
(Received via mailing list)
Clifford H. wrote:
> 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" ?
Brian A. (Guest)
on 2007-03-14 18:45
(Received via mailing list)
Reid T. wrote:
>>
>
Consider when today.month < bd.month - you'll have an off-by-one error.
Clifford H. (Guest)
on 2007-03-14 18:50
(Received via mailing list)
Brian A. wrote:
> Clifford H. wrote:
>> Brian A. wrote:
>>> (DateTime.now - birthdate) / 365.25
> 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.
Pit C. (Guest)
on 2007-03-14 19:15
(Received via mailing list)
Brian A. schrieb:
> 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
John J. (Guest)
on 2007-03-14 19:27
(Received via mailing list)
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.
Brian A. (Guest)
on 2007-03-14 19:40
(Received via mailing list)
Clifford H. wrote:
> 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. Senault (Guest)
on 2007-03-14 19:55
(Received via mailing list)
Le 14 mars à 15:47, Deniz D. 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 :

>> d1 = Date.today
=> #<Date: 4908347/2,0,2299161>
>> d2 = Date.new(1975,6,16)
=> #<Date: 4885159/2,0,2299161>
>> (d1.year - d2.year) - (d1.yday >= d2.yday ? 0 : 1)
=> 31
>> d1 = Date.new(2007, 06, 16)
=> #<Date: 4908535/2,0,2299161>
>> (d1.year - d2.year) - (d1.yday >= d2.yday ? 0 : 1)
=> 32

Fred
Sur M. (Guest)
on 2007-03-14 21:45
(Received via mailing list)
Thats gr8 !
thanks.
Pit C. (Guest)
on 2007-03-14 21:51
(Received via mailing list)
F. Senault schrieb:
>>> (d1.year - d2.year) - (d1.yday >= d2.yday ? 0 : 1)

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. Senault (Guest)
on 2007-03-14 22:56
(Received via mailing list)
Le 14 mars 2007 à 20:50, Pit C. a écrit :

> F. Senault schrieb:
>>>> (d1.year - d2.year) - (d1.yday >= d2.yday ? 0 : 1)
>
> 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...  :)

>> d1 = Date.new(2007, 3, 1); d2 = Date.new(2004, 3, 1)
=> #<Date: 4906131/2,0,2299161>
>> (d1.year - d2.year) - (d1.yday >= Date.new(d1.year, d2.month, 1).yday + d2.day - 1 ? 0 
: 1)
=> 3
>> d1 = d1 - 1
=> #<Date: 4908319/2,0,2299161>
>> (d1.year - d2.year) - (d1.yday >= Date.new(d1.year, d2.month, 1).yday + d2.day - 1 ? 0 
: 1)
=> 2

A pity Date.new doesn't handle overflows.  I'd like to be able to write
this :

>> Date.new(2007, 2, 29).to_s
=> '2007-03-01'
>> Date.new(2007, 3, 0).to_s
=> '2007-02-28'

Fred
Nando S. (Guest)
on 2007-03-15 08:07
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}"
Rick D. (Guest)
on 2007-03-15 21:29
(Received via mailing list)
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


--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
Brian C. (Guest)
on 2007-03-16 08:15
(Received via mailing list)
On Fri, Mar 16, 2007 at 04:28:16AM +0900, Rick DeNatale wrote:
>    # 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.
Nando S. (Guest)
on 2007-03-16 09:17
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
Pit C. (Guest)
on 2007-03-16 10:20
(Received via mailing list)
Rick DeNatale schrieb:
> 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
Colin B. (Guest)
on 2007-03-16 12:50
(Received via mailing list)
Extending the original poster's question, as people here are aware
working in complete months in Gregorian calendars can be tricky. For
example, if you are trying to generate a series of dates at intervals
of an integer number of months (years are just 12 months) then - as
far as I'm aware - there's no "standard" way of doing it in Ruby, or
in many other computer languages for that matter. (I'd be delighted to
be contradicted: this also applies to anything else I've got wrong in
the following post.)

The Date method >> doesn't do it if the day of the month is greater than
28:
    require 'date'
    dt = Date.new( 2000, 1, 31 )  #  2000-01-31
    dt = dt >> 1                  #  2000-02-29; which is correct
    dt = dt >> 1                  #  2000-03-29; what is wanted is
2000-03-31
    dt = dt >> 1                  #  2000-04-29; what is wanted is
2000-04-30
(Of course, you can use dt >> 1; dt >> 2; dt >> 3; etc, but it's not
elegant.)

Also, date2 - date will give the number of days, but as far as I am
aware there isn't a "standard" Ruby way of calculating periods between
two dates in terms of, for example, complete months. (Or complete 12
months, the original poster's problem.)

If there isn't anything "standard" out there which does this sort of
thing, I'd be more than happy to collaborate on something if people
would find it useful. Or if there are one or more "projects" out there
which work, or are close to working, I'd be happy to collaborate on
something which could become "standard". Or indeed in porting
something from another language.

I have done a lot of this type of working with Gregorian dates in
various computer languages, but my Ruby experience is very limited,
and I'm not a programmer. (Even though I've been reading this list
since 2003.) Also, I sometimes feel that programming is too English
and Western oriented, so I'd be interested (as far as is reasonably
practical) in making things more general than just the Gregorian
calendar after the date(s) that the Julian calendar was corrected.

I wrote some code some time ago: the proper names can be decided on
later, but I have in mind adding methods to Date
  dt.to( dt2 ), dt2.from( dt )
both of which would return an instance of a Date_period class, which
would hold the number of days from dt to dt2, and also the number of
complete months and the number of days following the complete months.
(And the dates themselves - or rather their object references - for
various reasons, which might or might not be a good idea.) Date_period
would have methods to return days, complete years, years and days,
months and days, etc.

For a series of dates the idea is to sub-class or modify Date to have
an (optional?) instance variable @anniversary_day, which is needed to
be able to retain the "true" anniversary day as well as the day in the
month in the date instance. Or at least to add a Date method
step_with_anniversary, which would have an option for the anniversary
day to be 29, 30 or 31 even if the start date is 28 of February.

Depending on whether it's a good idea or not, + and - (and maybe <<
and >>) in Date (or the subclass) could be adapted to also work with
instances of Date_period, or new methods could be added to Date (or
the subclass).

There are some tricky problems to be solved, and this post is long
enough already, but I'd be more happy to correspond off list
(including sending copies of the code I have), and then report back.
F. Senault (Guest)
on 2007-03-16 17:30
(Received via mailing list)
Le 16 mars à 11:49, Colin B. a écrit :

> Extending the original poster's question, as people here are aware
> working in complete months in Gregorian calendars can be tricky. For
> example, if you are trying to generate a series of dates at intervals
> of an integer number of months (years are just 12 months) then - as
> far as I'm aware - there's no "standard" way of doing it in Ruby, or
> in many other computer languages for that matter. (I'd be delighted to
> be contradicted: this also applies to anything else I've got wrong in

Depending on what you mean with this, I believe Visual Basic (gasp !
shock ! horror !) has a DateSerial function that can overflow in any
direction.  If you want to know the last day of february this year, you
can write DateSerial(2007, 3, 0), for instance.

It's extremely useful.  (And you have the same with TimeSerial, even if
it's a bit less useful.)

Fred
Rick D. (Guest)
on 2007-03-16 23:16
(Received via mailing list)
On 3/16/07, Pit C. <removed_email_address@domain.invalid> wrote:
>    There are 3 years between 2001-03-02 and 2004-03-01
Thanks, yep my bad!

Here's a refined attempt.

I've fixed that problem and I've also added support to specify a day
to be used as the anniversary date for a leap-day.  I did extensive
research (on Wikipedia <G>) and it seems that:

  1) People born on February 29 are called leaplings.
  2) For legal purposes most jurisdictions consider 1 March to be the
birthdate of a leapling in non-leap years for the purposes of
determining legal age.
  3) There are some jurisdictions, e.g. Taiwan which use 28 February
instead.

Although some leaplings try to pass themselves off as approximately
1/4 their legal age, I haven't made allowances for that in the
following code:

rick@frodo:/public/rubyscripts$ cat datemath.rb
require 'date'
class Date

  def leap_year?
    year % 400 == 0 || year % 100 != 0 && year % 4 == 0
  end

  def leap_day?
    month = 2 && day == 29
  end

  # The lyday is an altered yday. It is computed as if every year
  # was a leap year.  It's purpose is to determine whether a date has
  # been 'virtually' crossed
  def lyday
    yday + ((leap_year? || yday < 60) ? 0 : 1)
  end

  # return the 'legal' anniversary day cooresponding to first in the
year of last
  # This is the first date unless that date is leap day (29 February
xxxx) and the
  # second date is within a leap year.
  #
  # The leapling date is 1 March, xxxx by default.  The year of the
leapling date is
  # ignored.
  def self.ly_adjust(first, second, leapling_date)
    if first.leap_day? && !second.leap_year?
      leapling_date ||= Date.new(2004,3,1)
      Date.new(first.year, leapling_date.month, leapling_date.day)
    else
      first
    end
  end

  # return the number of years since the given date.
  # leapling date is a date, (in any year) which is
  # considered the anniversary of February 29 in non leap years.
  # In most jurisdictions the legal birthday in non-leap years for
determining
  # legal ages is March 1, which is the default, however some
jurisdictions
  # legislate another date, most commonly February 28.
  def years_since(date,leapling_date = nil)
    first, last = *(self >= date ? [date, self] : [self, date])
    first = Date.ly_adjust(first,last, leapling_date)
    (self <=> date) * ((last.year - first.year) - (first.lyday >
last.lyday ? 1 : 0))
  end
end

# The following methods demonstrate and test the above code.
# These really should be Test::Unit test cases, but I think that this
form works
# better for showing what the code does as well as verification.
#
# tryit displays the number of years between an end date and a start
date
# for leapling_dates of nil, 1 March, and 28 February,
# it prints each result, and returns an array of the three results.
def tryit(start_date,end_date)
  leapling_dates = [nil]
  result = []
  for ld in [nil, Date.new(2000,3,1), Date.new(2000,2,28)]
    puts "With leapling_date of #{ld}" if ld
    diff = end_date.years_since(start_date,ld)
    result << diff
    puts "There are #{diff} years between #{start_date} and #{end_date}"
  end
  puts
  result
end

# tryit2 takes two dates, and the array expected from tryit
# It calls tryit, checks the result and prints an error message if
# the results aren't as expected.
# It then reverses the arguments which should result in negated
# values of the expected results.
def tryit2(start_date,end_date,expected)
  puts "***** Error *****" unless expected == tryit(start_date,
end_date)
  expected = expected.map {|e| -e}
  puts "***** Error *****" unless expected == tryit(end_date,
start_date)
end

tryit2(Date.new(2000,3,14), Date.new(2007,3,15), [7, 7, 7])
tryit2(Date.new(2000,3,15), Date.new(2007,3,15), [7, 7, 7])
tryit2(Date.new(2000,3,16), Date.new(2007,3,15), [6, 6, 6])
tryit2(Date.new(2000,2,29), Date.new(2007,2,27), [6, 6, 6])
tryit2(Date.new(2000,2,29), Date.new(2007,2,28), [6, 6, 7])
tryit2(Date.new(2000,2,29), Date.new(2007,3,1), [7, 7, 7])
tryit2(Date.new(2000,2,29), Date.new(2007,3,2), [7, 7, 7])
tryit2(Date.new(2004,3,1), Date.new(2007,3,1), [3, 3, 3])

rick@frodo:/public/rubyscripts$ ruby datemath.rb
There are 7 years between 2000-03-14 and 2007-03-15
With leapling_date of 2000-03-01
There are 7 years between 2000-03-14 and 2007-03-15
With leapling_date of 2000-02-28
There are 7 years between 2000-03-14 and 2007-03-15

There are -7 years between 2007-03-15 and 2000-03-14
With leapling_date of 2000-03-01
There are -7 years between 2007-03-15 and 2000-03-14
With leapling_date of 2000-02-28
There are -7 years between 2007-03-15 and 2000-03-14

There are 7 years between 2000-03-15 and 2007-03-15
With leapling_date of 2000-03-01
There are 7 years between 2000-03-15 and 2007-03-15
With leapling_date of 2000-02-28
There are 7 years between 2000-03-15 and 2007-03-15

There are -7 years between 2007-03-15 and 2000-03-15
With leapling_date of 2000-03-01
There are -7 years between 2007-03-15 and 2000-03-15
With leapling_date of 2000-02-28
There are -7 years between 2007-03-15 and 2000-03-15

There are 6 years between 2000-03-16 and 2007-03-15
With leapling_date of 2000-03-01
There are 6 years between 2000-03-16 and 2007-03-15
With leapling_date of 2000-02-28
There are 6 years between 2000-03-16 and 2007-03-15

There are -6 years between 2007-03-15 and 2000-03-16
With leapling_date of 2000-03-01
There are -6 years between 2007-03-15 and 2000-03-16
With leapling_date of 2000-02-28
There are -6 years between 2007-03-15 and 2000-03-16

There are 6 years between 2000-02-29 and 2007-02-27
With leapling_date of 2000-03-01
There are 6 years between 2000-02-29 and 2007-02-27
With leapling_date of 2000-02-28
There are 6 years between 2000-02-29 and 2007-02-27

There are -6 years between 2007-02-27 and 2000-02-29
With leapling_date of 2000-03-01
There are -6 years between 2007-02-27 and 2000-02-29
With leapling_date of 2000-02-28
There are -6 years between 2007-02-27 and 2000-02-29

There are 6 years between 2000-02-29 and 2007-02-28
With leapling_date of 2000-03-01
There are 6 years between 2000-02-29 and 2007-02-28
With leapling_date of 2000-02-28
There are 7 years between 2000-02-29 and 2007-02-28

There are -6 years between 2007-02-28 and 2000-02-29
With leapling_date of 2000-03-01
There are -6 years between 2007-02-28 and 2000-02-29
With leapling_date of 2000-02-28
There are -7 years between 2007-02-28 and 2000-02-29

There are 7 years between 2000-02-29 and 2007-03-01
With leapling_date of 2000-03-01
There are 7 years between 2000-02-29 and 2007-03-01
With leapling_date of 2000-02-28
There are 7 years between 2000-02-29 and 2007-03-01

There are -7 years between 2007-03-01 and 2000-02-29
With leapling_date of 2000-03-01
There are -7 years between 2007-03-01 and 2000-02-29
With leapling_date of 2000-02-28
There are -7 years between 2007-03-01 and 2000-02-29

There are 7 years between 2000-02-29 and 2007-03-02
With leapling_date of 2000-03-01
There are 7 years between 2000-02-29 and 2007-03-02
With leapling_date of 2000-02-28
There are 7 years between 2000-02-29 and 2007-03-02

There are -7 years between 2007-03-02 and 2000-02-29
With leapling_date of 2000-03-01
There are -7 years between 2007-03-02 and 2000-02-29
With leapling_date of 2000-02-28
There are -7 years between 2007-03-02 and 2000-02-29

There are 3 years between 2004-03-01 and 2007-03-01
With leapling_date of 2000-03-01
There are 3 years between 2004-03-01 and 2007-03-01
With leapling_date of 2000-02-28
There are 3 years between 2004-03-01 and 2007-03-01

There are -3 years between 2007-03-01 and 2004-03-01
With leapling_date of 2000-03-01
There are -3 years between 2007-03-01 and 2004-03-01
With leapling_date of 2000-02-28
There are -3 years between 2007-03-01 and 2004-03-01

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/
Brian C. (Guest)
on 2007-03-16 23:31
(Received via mailing list)
On Fri, Mar 16, 2007 at 07:49:16PM +0900, Colin B. wrote:
> Extending the original poster's question, as people here are aware
> working in complete months in Gregorian calendars can be tricky.

I remember reading an article about why it can be important though.

For example, in a program which manages a doctor's surgery, it has to be
able to calculate vaccination schedules. These are often specified in
months, e.g. "second vaccination must be 4 to 6 months after the first
vaccination". Unfortunately this means that the minimum and maximum
number
of days between vaccinations varies depending on exactly when in the
year
you had the first one.

Now of course this is completely ludicrous - the body clock doesn't run
in
months, and the medical people should have specified the intervals in
days
or weeks. But database programmers are not qualified to make such
medical
judgements. And therefore, they have to implement the rules as laid down
by
the medical authority.

Regards,

Brian.
Cesar Rabak (Guest)
on 2007-09-26 01:10
(Received via mailing list)
John J. escreveu:
> 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.
In fact a lot of jurisdictions around the world would not _allow_ the
Registrar have a person born in a February 29th...
This topic is locked and can not be replied to.