Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Broken Date Handling

by davorg (Chancellor)
on Oct 10, 2002 at 10:18 UTC ( [id://204128]=perlquestion: print w/replies, xml ) Need Help??

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

I just got burnt by some dodgy date handling in one of my scripts. I worked out wht the problem is, but I wonder if anyone else would be a stupid as I was.

Basically, I'm getting dates from a web form. The dates are read from three drop-down menus - one each for day, month and year.. The values for day and month are all two digit numbers (with leading zeroes for numbers under ten) and the years are all four digits.

Now this gives people the opportunity to input invalid dates (like 2002-02-30 or 2002-09-31) so I need to validate them. I thought I'd be clever and use timelocal to do this. Here's what I did.

# $d, $m and $y have come from the web form. They will # be (for example) $d = 09, $m = 10, $y = 2002. # First we need to convert them to "timelocal-friendly" # values. $m--; $y -= 1900; # Then use eval to check date is valid eval "timelocal 0, 0, 0, $d, $m, $y"; # Warn on errors die "Invalid date\n" if $@;

All very clever, I thought. But it didn't work. Or rather, it worked in most cases, but there were a small number of valid dates that were marked as invalid. The sample values for $d, $m and $y above give one such example.

Without cheating and looking at the value of $@, can you work out what the problem is?

--
<http://www.dave.org.uk>

"The first rule of Perl club is you do not talk about Perl club."
-- Chip Salzenberg

Replies are listed 'Best First'.
Re: Broken Date Handling
by jj808 (Hermit) on Oct 10, 2002 at 10:38 UTC
    Because '09' is treated as an octal number (starts with '0'), but the highest digit in base 8 is 7.

    JJ

    Update:
    So the script will fail on the 9th of every month, and on every day in September - a total of 41 days. Or it will work 89% of the time - isn't that good enough :-)

    Update 2:
    Aha, I didn't notice the $m--

    I must admit that on first glance I wouldn't have seen the problem unless it was pointed out that the code was failing. It's the sort of bug that causes lots of swearing at the computer, then a very loud 'Doh!' when you fix it...

      Yeah. So I fixed it by adding

      $d += 0;

      to the code.

      The script doesn't fail in September. But it fails on both the 8th and the 9th of every month.

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg

        Why doesn't it fail in September then? Or August, for that matter?

        Of course, you've already done a calculation on that value. Doh.

(tye)Re: Broken Date Handling
by tye (Sage) on Oct 10, 2002 at 15:31 UTC
    Without cheating and looking at the value of $@, can you work out what the problem is?

    Yes! The problem is that you used eval"" instead of eval{}. Where is this web form of yours? I'd like to send it a "year" of "2002; system('rm -rf /')".

            - tye (we should rename "string" eval, probably via y/a/i/)
      I'd like to send it a "year" of "2002; system('rm -rf /')".

      Wouldn't get you very far as I extract the first set of digits I find in the "year" input and use those as the year.

      But, you're right. I'll change to block eval.

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg

        tye++ beat me to the punch.

        You know, the eval STRING was the first thing that jumped at me when I looked at the snippet. Without any clue as to what the problem really was, I would first have suggested that the code would be much better off with a switch to eval BLOCK. Even simple economics would suggest so - the latter form does not have to hook back into the compiler at runtime.

        For the record, assuming you really needed to stick with that form for whatever reason, then forcing numification of the day is just a kludge that only treats the symptom of your problem: you are treating user input data as code. This is the same class of problem that plagues all the simple, strictly stringifying scripting languages like Tcl. I was reminded of it immediately because I remember having to use a Tcl/Tk app at a job where entering 025 rather 25 in a text input box produced 21 (== 025 octal records) records - and there was no way at all to fix this, because of the way Tcl works. Fortunately, Perl is not Tcl. The real fix here is, or I should say would be, to eschew interpolation so that no user data makes it int your eval'd code:

        eval "timelocal 0, 0, 0, \$d, \$m, \$y"; or better eval 'timelocal 0, 0, 0, $d, $m, $y';

        Now the eval'd timelocal code will read the variables themselves, rather than be passed their stringified content.

        That said, I consider eval STRING a red flag - a large flashing one actually. Whenever I see one or feel tempted to resort to it, I ask myself whether there really is no other way to accomplish that. Sometimes I'd go so far as to assert that if there isn't, maybe it shouldn't be done at all. There are very, very, very few cases where eval STRING ever is the proper approach, and esentially all of them have to do with doing some form of deep magic. It never is the right one if you're trying to solve a relatively ordinary problem - where "verifying dates for correctness" classifies as a terribly boring one.

        eval STRING is the power to do anything. Be very, very afraid of it. Do not use without the proper amount of respect and awe.

        Makeshifts last the longest.

Re: Broken Date Handling
by davis (Vicar) on Oct 10, 2002 at 10:26 UTC
    It looks like you don't pad the value of $m after decrementing it - and 10-1 == 9. (not "09").
    Cheers.
    Update: After trying that out, I'm wrong. Guess that's rashness for you.
    davis
    Is this going out live?
    No, Homer, very few cartoons are broadcast live - it's a terrible strain on the animator's wrist

      You're in the right sort of area, but not padding $m is a good thing. Today's date works fine.

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg

Re: Broken Date Handling
by bigj (Monk) on Oct 10, 2002 at 22:13 UTC
    Perhaps it's an idea to avoid such an errorprone code and to use instead an existing module.
    use Date::Calc qw/check_date/; check_date($year,$month,$day) or die "Invalid date";
    Note, that also the year-1900 conversion isn't necessary any longer.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (2)
As of 2024-04-25 12:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found