Gossamer Forum
Home : Products : DBMan : Installation :

Substituting current age for birthdate within text field

Quote Reply
Substituting current age for birthdate within text field
Hi Carol, et al,

I'm back to work on my membership database of personal profiles.

It's for a parents' support group with one record/profile for each family. I want to add a text field named 'Children' that will function in the following way:

1. When a member creates their record, they are instructed to fill out this field with their children's names and birthdates, for example:

"Tommy (09-Feb-1993), Jenny (30-Apr-1998), expecting baby (01-Jan-2000)"

2. The exact formatting is not important except for the dates, i.e., it doesn't matter if they put the date in parentheses, or commas between names. How to format the date as dd-mmm-yyyy will be clearly specified in the instructions.

3. The field data will be stored in the database file as is, but the dates will be converted to and replaced with current ages each time the record is viewed. So, based on today's date of 27-Aug-1999, the following would be displayed:

"Children: Tommy (6.5 yrs), Jenny (16 mos), expecting baby (01-Jan-2000)"

4.a. From the above example, you can see that any date in the future is left unchanged.
b. Birthdates formatted incorrectly are also be left as is; I don't want a lot of fancy error checking, and they can always modify their record if they screw up the date format.
c. If the age is 2 yrs or higher, it is shown in years (nearest tenth of a year).
d. If the age is between 8 weeks and 2 yrs, it is shown in months (nearest whole month).
e. If the age is between 2 and 8 weeks, it is shown in weeks (nearest whole week).
f. If the age is less than 2 weeks, it is shown in days.

The age calculations need not be precise. I don't know Perl syntax very well, but here's a starting point:
Code:
variables: birthdate, age, units
for each occurence of a text string that matches the pattern dd-mmm-yyyy {
assume it's a birthdate
calculate the age IN DAYS and round to the nearest integer
if age >= 0 {
if age >= 730 {
divide age by 365.25 and round it to the nearest tenth
change units to "yrs" }
else if age > 56 {
divide age by 30.44 and round it to the nearest integer
change units to "mos" }
else if age >= 14 {
divide age by 7 and round it to the nearest integer
change units to "wks" }
else { if age = 1, change units to "day", else change units to "days" }
concatenate age and units, and replace the matched pattern with the result
}
}
Can someone help me flesh out the Perl code for this? I suppose it would be part of sub html_record in html.pl (I'm using the user-friendly version with my own html formatting, i.e., no auto-generated html).

Thanks in advance! Smile

Scott Noelle,
noelles@teleport.com
Quote Reply
Re: Substituting current age for birthdate within text field In reply to
Woah! This is extremely complex. I don't know if I could do that at all. Just figuring out how to tell the script what text to look for makes my mind boggle.

Before we go any further with this, though, can you be absolutely certain that none of the children would have been born before 1-Jan-1970?


------------------
JPD





Quote Reply
Re: Substituting current age for birthdate within text field In reply to
JPDeni wrote:
Code:
### To be added to sub html_record, I'm not sure at what point... ###

my ($tmp);
$tmp = $Children; # temporary variable for the text of field Children

### Convert and substitute all patterns matching dd-mmm-yyyy
### using the results of a conversion subroutine, &birthdate_to_age
### Question: Do the hyphen characters in the pattern need to be escaped?

$tmp =~ s/(\d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(?:19|20)\d{2})/&birthdate_to_age($1)/og;

. . .

###### Some of the following code is borrowed from the date routines in db.cgi ######

sub birthdate_to_age {

my ($bd_day, $bd_month, $bd_year) = split(/-/, $_[0]);

### At this point, if $bd_day is not between 1 and 31 then we'll
### return the original value $_[0] unconverted as it is not a valid date.
### I need help coding this; remember, $bd_day has a leading 0 if it's < 10.

my ($sec, $min, $hour, $day, $month, $year, $dweek, $dyear, $daylight) = localtime(time());
my ($age);
my ($units) = " days"

my (%months) = ("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
"Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11,"Dec" => 12);

$bd_month = $months{$bd_month}; # convert $bd_month to a number between 1 and 12
$bd_year = $bd_year - 1900;

### Now we can determine the age in days. Note the constants used are the
### average num of days in a year or month - close enough for this routine.
$age = (365.25 * ($year - $bd_year ))
+ ( 30.44 * ($month - $bd_month))
+ ($day - $bd_day );

### Now switching to my funky pseudocode from before, slightly more Perl-esque ###

if ($age < 0) {
return the original birthdate $_[0] unconverted }

if ($age >= 716) {
divide $age by 365.25 and round it to the nearest tenth;
change $units to " yrs"; }
elsif ($age > 56) {
divide $age by 30.44 and round it to the nearest integer;
change $units to " mos"; }
elsif ($age >= 14) {
divide $age by 7 and round it to the nearest integer;
change $units to " wks"; }
elsif ($age = 1) {
change $units to " day"; } # $units will remain " days" if $age is 0 or 2-13 days

return ($age.$units);
}

Well... How'd I do? Can you provide the finishing touches, or did I overlook something major?

Scott Noelle
noelles@teleport.com
Quote Reply
Re: Substituting current age for birthdate within text field In reply to
No, the hyphens don't need to be escaped.

Let's see if I can expand your psuedo-code.

Code:
if ($age < 0) {
return $_[0];
}
if ($age >= 716) {
# divide $age by 365.25 and round it to the nearest tenth;
$age = sprintf("%.1f",$age/365.25);
# change $units to " yrs";
$units = " yrs";
}
elsif ($age > 56) {
#divide $age by 30.44 and round it to the nearest integer;
$age = int(($age/30.44)+.5);
# change $units to " mos";
$units = " mos";
}
elsif ($age >= 14) {
# divide $age by 7 and round it to the nearest integer;
$age = int(($age/7)+.5);
# change $units to " wks";
$units = " wks";
}
elsif ($age = 1) {
# change $units to " day";
$units = " day";
}
return ($age.$units);

I'm quite impressed by your reg. expr. to ferret out the days. I haven't delved that far into them as yet and they always look impressive. Smile

Give 'er a try and see what happens. I'll be interested in the result.


------------------
JPD





Quote Reply
Re: Substituting current age for birthdate within text field In reply to
It'll be a while before I upload everything to test it, but you'll be the first to know!

Did you see my comment above?...
Code:
### At this point, if $bd_day is not between 1 and 31 then we'll
### return the original value $_[0] unconverted as it is not a valid date.
### I need help coding this; remember, $bd_day has a leading 0 if it's < 10.
My concern was that a leading 0 might mess up the comparison and, later, the age calculation. If not, it would simply be...
Code:
if (($bd_day > 31) &#0124; &#0124; ($bd_day < 1)) {return $_[0];}
Correct-a-mundo? (I don't care if someone enters "30-Feb-1999".) Is there a more elegant method in Perl?

Thanks again! --Scott
Quote Reply
Re: Substituting current age for birthdate within text field In reply to
I had missed that note.

After

Code:
my ($bd_day, $bd_month, $bd_year) = split(/-/, $_[0]);

add

Code:
$bd_day = int($bd_day);
$bd_year = int($bd_year);
unless ($bd_day && $bd_year && ($bd_day < 32)) {
return $_[0];
}

I also would make sure that they entered a correct month.

After

Code:
my (%months) = ("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
"Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11,"Dec" => 12);

add

Code:
unless (defined($months{$bd_mon})) {
return $_[0];
}


One other thing you might check for is to make sure they entered a 4-digit year. Otherwise you're going to have negative ages.

Instead of

Code:
$bd_year = $bd_year - 1900;

use

Code:
if ($bd_year > 1899) {
$bd_year = $bd_year - 1900;
}



------------------
JPD





Quote Reply
Re: Substituting current age for birthdate within text field In reply to
You're right. Some of the error-checking is handled by the reg. expr.

Code:
$bd_day = int($bd_day);
<HR></BLOCKQUOTE>

That is so you can use

Code:
unless ($bd_day && ($bd_day <= 31)) {return $_[0];}

If someone entered 00-Jan-1900, it would still recognize that there was something in the $bd_day variable -- the string 00. By "int"-ing the variable, it turns it to a 0.

(Boy, it's really hard to explain the reasons for things!! Smile )

Quote:
3. $bd_year cannot possibly be zero or empty. Is there any other reason to include it here?

No. There isn't. I had forgotten about your cool reg. expr. that requires the year to start with 19 or 20.

Looks good. Glad you did it instead of me!! Smile


------------------
JPD





Quote Reply
Re: Substituting current age for birthdate within text field In reply to
OK... I have put this all together below as two subroutines with minor improvements, and I changed the name of sub birthdate_to_age to html_date_to_age, as it will be in html.pl.

Some questions:

1. Some of your error checking suggestions are already handled by the reg. expression in...
Code:
$tmp =~ s/(\d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(?:19|20)\d{2})/&html_date_to_age($1)/og;
If I'm not mistaken, the pattern will *only* match a substring comprised of a 2-digit number, a hyphen, a valid month abbreviation, another hyphen, and a 4-digit number between 1900 and 2099. (I know, it's not Y2.1K compliant! Smile) Sub html_date_to_age is only called when the above matches, so is it necessary to have another layer of error checking in sub html_date_to_age ?

2. What is the purpose of this?...
Code:
$bd_day = int($bd_day);
I take it the int function will remove any leading zeroes. Is this necessary in order range-check $bd_day and/or use it in the $age calculation? There's no possibility of $bd_day containing a decimal point, is there?

3. $bd_year cannot possibly be zero or empty. Is there any other reason to include it here?
Code:
unless ($bd_day && $bd_year && ($bd_day < 32)) {
return $_[0];
}
Here's the whole thing. I'll add in the extra error checking if you still think it's needed, O Great One Wink ...
Code:
### In sub html_record, replace "$Children" with "&html_dates_to_ages($Children)"

sub html_dates_to_ages {

### Expected parameter is a text string containing birthdates.
### (Will also work for anniversaries, etc.) All patterns matching
### the date format dd-mmm-yyyy will be converted to a current age
### using the results of sub html_date_to_age, which will also (sort of)
### validate the day of month. Year must be between 1900 and 2099.

my ($tmp) = $_[0];
$tmp =~ s/(\d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(?:19|20)\d{2})/&html_date_to_age($1)/og;
return $tmp;
}

######################

sub html_date_to_age {

### Expected parameter is a date in the form dd-mmm-yyyy.
### The "dd" has not been validated, other than being digits.

my ($bd_day, $bd_month, $bd_year) = split(/-/, $_[0]);

### At this point, if $bd_day is not between 1 and 31 then we'll
### return the original value $_[0] unconverted as it is not a valid date.
### NOTE: This will not catch impossible dates like 31-Feb-1990.

unless ($bd_day && ($bd_day <= 31)) {return $_[0];}

### Continuing...

my ($sec, $min, $hour, $day, $month, $year, $dweek, $dyear, $daylight) = localtime(time());
my ($age);
my ($units) = " days"; ### This will be changed if the child's age > 2 weeks or = 1 day.

my (%months) = ("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
"Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11,"Dec" => 12);

$bd_month = $months{$bd_month}; # Convert $bd_month to a number between 1 and 12.
$bd_year = $bd_year - 1900; # Convert $bd_year to the same format as $year.

### Now we can determine the age in days. The constants used are the
### average num of days in a year or month - close enough for this routine.

$age = int( (365.25 * ($year - $bd_year ))
+ ( 30.44 * ($month - $bd_month))
+ ($day - $bd_day) );

if ($age < 0) { return $_[0]; } # No birthday party until you're born!
if ($age >= 716) { # Older than 23.5 months?
$age = sprintf("%.1f",$age/365.25); # convert $age to years, round to nearest tenth.
if ($age >= 10) {$age = int($age)} # 10 or older? No need for tenths.
else { $age =~ s/\.0// } # No need for age to end with ".0"
$units = " yrs";
}
elsif ($age > 56) { # Between 8 weeks and 23.5 months?
$age = int(($age/30.44)+.5); # convert $age to nearest whole month
$units = " mos";
}
elsif ($age >= 14) { # Between 2 weeks and 8 weeks?
$age = int(($age/7)+.5); # convert $age to nearest whole week
$units = " wks";
}
elsif ($age = 1) { $units = " day"; } # Use correct grammar
return ($age.$units);
Scottnoelles@teleport.com[\email]