Calculating the age given DOB

C

Colin Bartlett

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

F. Senault

Le 16 mars à 11:49, Colin Bartlett 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
 
R

Rick DeNatale

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

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
 
B

Brian Candler

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.
 
C

Cesar Rabak

John Joyce 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...
 

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