tags:

views:

130

answers:

5

Greetings,

I am setting up this example Perl snippet to validate for months in a date:

Some scenarios I want to accept are:

MM M

#!/usr/bin/perl
use strict;
use warnings;

my $pattern;
my $month = "(0[1-9]|1[012])";
my $day = "(0[1-9]|[12]\d|3[01])";

system("cls");

do {

    print "Enter in a month: ";
    chomp($pattern = <STDIN>);

    # We only want to print if the pattern matches
    print "Pattern matches\n" if ($pattern =~ /$month/);


} while ($pattern ne "Q");

When I run this, it correctly filters from 01-12 but when I change the regex to:

$month = "(0?[1-9]|1[012])";

then the regex allows 13, 14, etc... what gives?

+13  A: 

You shouldn't use a regular expression to do numeric range validation. The regular expression you want is:

/^(\d+)$/

Then,

if ($1 >= 1 && $1 <= 12) {
    # valid month
}

This is much easier to read than any regular expression to validate a numeric range.

As an aside, Perl evaluates regular expressions by searching within the target for a matching expression. So:

/(0[1-9]|1[012])/

searches for a 0 followed by 1 to 9, or a 1 followed by 0, 1, or 2. This would match "202" for example, and many other numbers. On the other hand:

/(0?[1-9]|1[012])/

searches for an optional 0 1 to 9, or a 1 followed by 0, 1, or 2. So "13" matches here because it contains a 1, matching the first half of the regex. To make your regular expressions work as you expect,

/^(0?[1-9]|1[012])$/

The ^ and $ anchor the search to the start and end of the string, respectively.

Greg Hewgill
`/(0[1-9]|1[012])/` would not match "202", it would match the 02 part of "2**02**", but that is quite differant.
Paul Creasey
+1: Regular expressions for simple numerical validation is overkill.
dreamlax
@Paul: Depends on what you mean by "match". In this case, using the regular expression to *match* against `202` (i.e. `$pattern =~ m/$month/`, noting the meaning of the `m` prefix) would return a true value, and for the code given that is precisely what we don't want. However, it would only *capture* the `02` part with the parens. So if you use match to mean capture, then no, it would not.
Adam Bellaire
+7  A: 

If you really like to use regex, you need to put ^ and $, like

"^(0?[1-9]|1[012])$"

it will not match 13, 14 ....

S.Mark
pattern match or search problem.
Dyno Fu
+1  A: 

To give you hint - month number "120" also matches in your version :-)

Change:

my $month = "(0[1-9]|1[012])";

to

my $month = /^(0[1-9]|1[012])$/;

and then play more with it

MBO
A: 

here's one way

while(1){
    print "Enter in a month: ";
    $pattern = <STDIN>;
    chomp($pattern);
    if ($pattern =~ /^(Q|q)$/ ){last;}
    if ($pattern =~ /^[0-9]$/ || $pattern =~ /^[0-9][12]$/ ) {
        print "Pattern matches\n";
    }else{
        print "try again\n";
    }
}

output

$ perl perl.pl
Enter in a month: 01
Pattern matches
Enter in a month: 000
try again
Enter in a month: 12
Pattern matches
Enter in a month: 00
try again
Enter in a month: 02
Pattern matches
Enter in a month: 13
try again
Enter in a month:
ghostdog74
This doesn't work properly, 41, 42, 51, 52, etc all say "Pattern matches".
dreamlax
+2  A: 

Don't use regular expressions.

Perl has the ability to automatically evaluate as a number or a string based on context. 01-09 will evaluate to 1-9 in the numeric context. So, you can simply check for a value:

print "Enter in a month: ";
chomp($pattern = <STDIN>);
# We only want to print if the pattern matches
print "Pattern matches\n" if ($pattern < 13 && $pattern > 0);
HerbN
And, after you've checked the range, you can printf it with "%02d" if you need the leading zero.
brian d foy