For dates (i.e. times with h,m,s=0), it's better to use timegm+gmtime over timelocal+localtime. I think your program will return an incorrect answer for some time periods due to DST.
Update:
Solution which still uses core module Time::Local:
use strict;
use warnings;
use Time::Local qw( timegm_nocheck );
# birth_date_age(2006, 4, 28) for people born on April 28th, 2006.
sub birth_date_age {
my ($bday_y, $bday_m, $bday_d) = @_;
# ----------------------------------------
# -- Get current date.
my ($now_y, $now_m, $now_d) = (localtime)[5,4,3];
$now_y += 1900;
$now_m++;
my $date_now = timegm_nocheck(0,0,0, $now_d, $now_m-1, $now_y);
# ----------------------------------------
# -- Get next birthday.
my $date_next = timegm_nocheck(0,0,0, $bday_d, $bday_m-1, $now_y);
$date_next = timegm_nocheck(0,0,0, $bday_d, $bday_m-1, $now_y+1)
if $date_next <= $date_now;
my ($next_y) = (gmtime($date_next))[5];
$next_y += 1900;
# ----------------------------------------
# -- Get prev birthday.
my $prev_y = $next_y - 1;
my $date_prev = timegm_nocheck(0,0,0, $bday_d, $bday_m-1, $prev_y);
# ----------------------------------------
# -- Calculate age and others
my $age = $next_y - $bday_y - 1;
my $days_to = ($date_next - $date_now) / (24*60*60);
my $days_from = ($date_now - $date_prev) / (24*60*60);
return ($age, $days_from, $days_to);
}
Notes:
-
On years without a Feb 29th, March 1st will be considered the birthday anniversary of people born on Feb 29th.
-
I leave the date parsing to the caller. It makes for a more flexible solution.
-
For arguments, days, months and years are one-based.
-
Internally, days, months and years are one-based.