Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

getting answer 70 years off

by derekstucki (Sexton)
on Jan 17, 2014 at 21:35 UTC ( [id://1071022]=perlquestion: print w/replies, xml ) Need Help??

derekstucki has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to turn a date of birth into an age using timelocal and localtime as follows:

if( $entry =~ /^ *(\d{1,2})[ -\/\\]+(\d{1,2})[ -\/\\]+(\d{4}) *$/ ) { # $mon should be 0 indexed, so subtract 1 my $mon = $1 - 1; my $day = $2; my $year = $3; # localtime returns years since 1900 my $currentyear = (localtime( time() ))[5] + 1900; if( $mon < 0 or $mon > 11 or $day < 1 or $day > 31 or $year > $currentyear # the 130 is setting a max acceptable age or $year < $currentyear - 130 ) { return undef; } my $time = timelocal( 0, 0, 0, $day, $mon, $year ); my $age = (localtime ( time - $time ))[5]; # do some stuff with it }

When I test this, it always comes up with an age 70 years too old. Is there a discrepancy between the epoch used by localtime and timelocal? And if so, does that difference vary from system to system, ie linux to mac to windows?

Replies are listed 'Best First'.
Re: getting answer 70 years off
by no_slogan (Deacon) on Jan 17, 2014 at 22:12 UTC
    my $age = (localtime ( time - $time ))[5]

    Don't do this. A difference of two times is not something you should pass to localtime. If you only need an approximate age, you can use something like

    $age = (time() - $time) / (60*60*24*365.25);

    If you want an accurate answer that takes leap years and stuff into account, use a package like Date::Manip or DateTime or whatever.

Re: getting answer 70 years off
by kcott (Archbishop) on Jan 17, 2014 at 23:44 UTC

    G'day derekstucki,

    Your main problem is that "my $age = (localtime ( time - $time ))[5];" is not doing what you presumable imagine it does. Consider the following which assumes an age of 25:

    $ perl -Mstrict -Mwarnings -E ' my $x = time; say $x; my $y = int($x - (25*365.24225*24*60*60)); say $y; my $z = (localtime($x-$y))[5]; say $z ' 1389999563 601076303 95

    That's out by 70. Using ages of 5 and 45 gives 75 and 115 respectively: i.e. out by 70 also. You added the comment "# localtime returns years since 1900": you took this into consideration with your first localtime() (i.e. ... + 1900) but ignored it with the second. The epoch started in 1970: that's 70 years since 1900. Had you added 1900 for $age, then substracted 1970, you would have got the right answer: obviously that's ridiculously complicated. Here's a much easier way to do it:

    #!/usr/bin/env perl -l use strict; use warnings; use Time::Piece; use Time::Seconds; my @YMDs = ([2004, 1, 17], [2004, 1, 18], [2004, 1, 19]); my $now = localtime; print 'Now: ', $now->strftime('%Y-%m-%d'); for (@YMDs) { my $dob = Time::Piece->strptime(join('-' => @$_), '%Y-%m-%d'); print 'DOB: ', $dob->strftime('%Y-%m-%d'); print 'Age: ', int(($now->epoch - $dob->epoch) / ONE_YEAR); }

    Output:

    Now: 2014-01-18 DOB: 2004-01-17 Age: 10 DOB: 2004-01-18 Age: 10 DOB: 2004-01-19 Age: 9

    You also have problems in your regex:

    You also have problems in your validation (if( $mon < 0 or ...); for instance, 31st February would be considered valid!

    -- Ken

      Oh, I can't believe I forgot to put the '-' at the end of the group, thanks for that. As for not needing to escape the '/', I'm afraid you're mistaken, at least for perl 5.14.2. I just tested it, and it doesn't compile: "Unmatched [ in regex;", and vim's highlighting ends the regex there, which in my opinion is reason enough to include it, even if it is unnecessary and benign. I did intend for that group to match one or more times, as some people may put spaces around their '\/-' ( sounds stupid, but there's a lot of stupid out there, and I try to account for all of it that I can ), but thanks for pointing it out.
        "As for not needing to escape the '/', I'm afraid you're mistaken, at least for perl 5.14.2. I just tested it, and it doesn't compile: "Unmatched [ in regex;", ..."

        I'm currently using 5.18.1 but also have 5.14.2 available: I can't reproduce the error you report:

        $ perlbrew switch perl-5.14.2 $ perl -v This is perl 5, version 14, subversion 2 (v5.14.2) ... $ perl -Mstrict -Mwarnings -e 'my $re = qr{[ /\\-]}' $ perlbrew switch perl-5.18.1t $ perl -v This is perl 5, version 18, subversion 1 (v5.18.1) ... $ perl -Mstrict -Mwarnings -e 'my $re = qr{[ /\\-]}'

        The perlrecharclass link I provided was for 5.18.0. The perlrecharclass documentation for 5.14.2 has the same information.

        "... and vim's highlighting ends the regex there, which in my opinion is reason enough to include it, even if it is unnecessary and benign."

        I also use vim with syntax highlighting. I find it has many problems, particularly with regular expressions. I don't change my code to fit in with vim's problems.

        -- Ken

Re: getting answer 70 years off
by PerlSufi (Friar) on Jan 17, 2014 at 21:45 UTC
    Hello. I'm curious if there is any reason you aren't using something like the DateTime module for this from CPAN? You could start with:
    use strict; use warnings; use DateTime; my $dt = DateTime->today();
    and do all the other stuff you need after that.. also, is $entry an argument from the command line or line from a file? Also, I think I needed to use Date::Format for localtime formatting stuff
Re: getting answer 70 years off
by Laurent_R (Canon) on Jan 17, 2014 at 22:12 UTC
    When you do this subtraction time - $time, you get the number of seconds elapsed between the date you passed to the program and now. Then, when you apply localtime to that value, you are calculating the date that occurred that number of seconds after the epoch, i.e. Jan 1, 1970. So, assume you passed 01-17-2007, just 7 years ago, local time will return you the date just seven years after Jan 1, 1970, i.e. Jan 1, 1977. And  (localtime ( time - $time ))[5] will give you 77. If you subtract 70, you should get the right age, but there are date and time calculating modules (DateTime, for example) to do this calculation better for you.
Re: getting answer 70 years off
by dwm042 (Priest) on Jan 17, 2014 at 22:48 UTC

    From the documentation for Time::Local in CPAN:

    This module provides functions that are the inverse of built-in perl functions localtime() and gmtime(). They accept a date as a six-element array, and return the corresponding time(2) value in seconds since the system epoch (Midnight, January 1, 1970 GMT on Unix, for example).

    The difference between system epoch start (1970) and the localtime conversion point (1900) appears to account for your missing 70 years.

    dwm042

      IIRC, Mac OS set the epoch at Jan 1, 1900 for some reason.

      It helps to remember that the primary goal is to drain the swamp even when you are hip-deep in alligators.
Re: getting answer 70 years off
by derekstucki (Sexton) on Jan 21, 2014 at 20:58 UTC
    Thanks for all the replies. For the record, I see the consensus as being that due to the difference in what localtime and timelocal assume, using them together in this manner is a bad idea, and the best solution is the use DateTime or similar. I assume that DateTime will work the same on all systems, OSs. If that is incorrect, somebody please say so.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (4)
As of 2024-03-29 12:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found