Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Calculate Age, days to next and days from last for a given birthday

by ruzam (Curate)
on Apr 28, 2006 at 20:03 UTC ( [id://546372]=sourcecode: print w/replies, xml ) Need Help??
Category: fun stuff
Author/Contact Info
Description: This is a little function to calculate a person's age, the number of days to their next birthday and the number of days from their last birthday.

If the birthdate is today, then the number of days to and from the last birthday = 0.

I can't say for certain that it's accurate for all situations (leap years and what not), but it's quite simple and doesn't rely on any additonal modules other than Time::Local which should be standard.

Updates:
- changed the name to 'birth_date_age_today' so there's no confusion as to it's purpose.
- Removed current day and month as these are not used.
- Used timelocal_nocheck to account for birthdates on Feb 29 - Added condition to leap year check so script continues to operate in the year 2100
- Added more comments (so people don't get the wrong idea about leap years). - fixed missing brackets on leap year test - added test code to check results
#!/usr/bin/perl

use strict;
use warnings;

use Time::Local qw(timelocal_nocheck);

# calculate Age, days from last birthday and days to next birthday for
# a given birth date.
# birthdate is a string of the format 'YYYY-MM-DD'
#  - good for accepting dates from databases
# If the birthdate is today, then days from = days to = 0
# returns age, days from, days to

# NOTE: this script does no date validation
# NOTE: this script uses the current year to calculate age.
# NOTE: if you modify this script to use anything other than current y
+ear,
#       there may be leap year implications

sub birth_date_age_today {
    my $birthdate = shift || return 0;
    $birthdate =~ m/^(\d\d\d\d)-(\d\d)-(\d\d)$/ or return 0;

    my ($byyyy,$bmm,$bdd) = ($1,$2,$3);
        --$bmm;
    my ($yyyy) = (localtime)[5];
        $yyyy += 1900;

    my $cur_day_of_year = (localtime)[7];
        # nocheck or feb 29 birthdays will have problems
    my $birth_day_of_year = (localtime timelocal_nocheck(0, 0, 0, $bdd
+, $bmm, $yyyy - 1900))[7];

    # calculate age
    my $age = $yyyy - $byyyy;
    my $daysto;
    my $daysfrom;
    if ($cur_day_of_year < $birth_day_of_year) {
        # haven't hit birthday yet this year
        $age--;
        $daysto = $birth_day_of_year - $cur_day_of_year;
        # last year's birthday
        $yyyy--;
        $birth_day_of_year = (localtime timelocal_nocheck(0, 0, 0, $bd
+d, $bmm, $yyyy - 1900))[7];
        # correct for days (2100 isn't a leap year)
        $daysfrom = (($yyyy % 4 or $yyyy == 2100) ? 365 : 366) - $birt
+h_day_of_year + $cur_day_of_year;
    }
    elsif ($cur_day_of_year > $birth_day_of_year) {
        # passed birthday this year
        $daysfrom = $cur_day_of_year - $birth_day_of_year;
        # next year's birthday
        # Note: we get the number of days to the birthday in the next 
+year ($yyyy - 1899)
        # but we use the current year for checking leap year because w
+e need to know how
        # many days are left in this year
        $birth_day_of_year = (localtime timelocal_nocheck(0, 0, 0, $bd
+d, $bmm, $yyyy - 1899))[7];
                # correct for leap days (2100 isn't a leap year)
        $daysto = (($yyyy % 4 or $yyyy == 2100) ? 365 : 366) - $cur_da
+y_of_year + $birth_day_of_year;
    }
    else { $daysfrom = $daysto = 0 }

    return $age, $daysfrom, $daysto;
}

# a simple test script
# run through every date (birthdate) of a given year
# print the results (relative to current date)
my %days = (
    1 => 31,
    2 => 28,
    3 => 31,
    4 => 30,
    5 => 31,
    6 => 30,
    7 => 31,
    8 => 31,
    9 => 30,
    10 => 31,
    11 => 30,
    12 => 31,
    );

my $yyyy = 1965;
# This is not totally correct!  Just simplified for this test
$days{2} = ($yyyy % 4 or $yyyy == 1900 or $yyyy == 2100) ? 28 : 29;

for (my $mm = 1; $mm <= 12; ++$mm) {
    for (my $dd = 1; $dd <= $days{$mm}; ++$dd) {
        my $testdate = sprintf("$yyyy-%02d-%02d", $mm, $dd);
        my ($age, $daysfrom, $daysto) = birth_date_age_today($testdate
+);
        print "birthdate $testdate  age $age  from last $daysfrom  day
+s to $daysto\n";
    }
}
  • Comment on Calculate Age, days to next and days from last for a given birthday
  • Download Code
Replies are listed 'Best First'.
Re: Calculate Age, days to next and days from last for a given birthday
by ikegami (Patriarch) on Apr 28, 2006 at 20:23 UTC
    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.

      The time period in this case would I guess be midnight as that's when the date would switch over. Wouldn't it be more appropriate to have that happen on local time? Otherwise your birthdate wouldn't flip until gmtime midnight, which wouldn't be correct (unless you happen to be living on the gm time line).
        As you can see in my (newly added) code, I work with *local* dates using timegm+gmtime. The number of days bewteen two dates is the same no matter which time zone you are in.
Re: Calculate Age, days to next and days from last for a given birthday
by eric256 (Parson) on Apr 28, 2006 at 20:31 UTC

    Where doesn't $session come from? This code wont run as is, and i'm not bright enough to fix it ;)


    ___________
    Eric Hodges
      My bad. I ripped the function from my code, which actually did more stuff.

      I've replaced the error with a regular current date.
Re: Calculate Age, days to next and days from last for a given birthday
by davidrw (Prior) on Apr 28, 2006 at 20:36 UTC
    $daysfrom = ($yyyy % 4 ? 365 : 366) isn't sufficient for leap years .. there's another part of the rule -- e.g. Feb 1900 only had 28 days but Feb 2000 had 29. google results

    I see you were trying to avoid other modules, but just for an example (and cause it's a module i use alot), here's a partial Date::Calc solution:
    my $birthday = '2000-01-02'; # yyyy-mm-dd use Date::Calc qw/Delta_Days Today Add_Delta_YMD/; my @date = split /-/, $birthday; $date[0] = (Today())[0]; # set the year to current year print Delta_Days( Add_Delta_YMD(@date,-1,0,0), @date ); # days since +last one print Delta_Days( @date, Add_Delta_YMD(@date,1,0,0) ); # days until +next one
    Update: added in the Add_Delta_YMD to account for birthdays on 2/29
      But when calculating days to/from birthdate, we don't deal with 1900. We only ever work with the current year (plus or minus one). I don't recall what the rules are regarding the 'leap year skip', but I'm pretty sure it won't affect this code for a very long time.
        We only ever work with the current year (plus or minus one).
        Which by definition includes the Feb/Mar boundry, so leap year can affect it by a day...
        I don't recall what the rules are regarding the 'leap year skip',
        See the google link above (searched for 'leap year calculation'); Also another reason to use Date::Calc, Date::Time, etc modules.
        but I'm pretty sure it won't affect this code for a very long time.
        Yeah.. i'm pretty sure that's what people said about the year 2000, which came and went, as will 2038 .. as will 2100 (which has only 28 days in Feb) .. Sure, that's a little way off, but on prinicpal you should care about the accuracy of your code, also, what about this realistic (at least plausible) scenario: Someone sees your snippet here and adapts it to their code, which isn't limited to the current year (e.g. days to/from a certain future date) and 2100 might be a valid input ..

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://546372]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (4)
As of 2024-04-26 05:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found