Joacer
Joacer

Reputation: 568

Problem with ranges in regular expression

I'm trying to make a regular expression that validates the following format:

Example that I would consider valid:

4,31,2-22,8
29,1-10,2-12,9

Example that I would consider invalid:

4,31,2-22,8,
29,1-10,-2-12-,9
29,1-50,12-2,32

The regular expression that I have so far is the following:

(((1[0-9]|2[0-9]|3[0-1]|[1-9])(\-(1[0-9]|2[0-9]|3[0-1]|[1-9]))?)(\,((1[0-9]|2[0-9]|3[0-1]|[1-9])(\-(1[0-9]|2[0-9]|3[0-1]|[1-9]))?))*)

At the moment this expression takes me well the "-" and ",", and that the numbers go from 1 to 31. The problem of the rank that the second value is greater than the first I have no idea of how to solve it. Any suggestions?

Upvotes: 2

Views: 170

Answers (2)

Mihai Matei
Mihai Matei

Reputation: 24276

As Jeff already suggested I wouldn't use regex because it would be very hard to understand.

The solution is much simpler than it seems:

function isValid($string)
{
    $numbers = explode(',', $string);

    // Covers the case when you have an empty value at the beginning/end of string.
    if (count($numbers) !== count(array_filter($numbers))) {
        return false; 
    }

    foreach ($numbers as $number) {
        if (is_numeric($number) && $number >= 1 && $number <= 31) {
            continue;
        }

        if (!preg_match('/^(\d+)-(\d+)$/', $number, $matches)) {
            return false;
        }

        if ($matches[1] >= 1 && $matches[1] <= 31 && 
            $matches[2] >= 1 && $matches[2] <= 31 && 
            $matches[2] > $matches[1]
        ) {
            continue;
        }

        return false;
    }

    return true;
}

$strings = [
    '4,31,2-22,8',
    '29,1-10,2-12,9',
    '4,31,2-22,8,',
    '29,1-10,-2-12-,9',
    '29,1-50,12-2,32',
];


foreach ($strings as $string) {
    var_dump(isValid($string));
}

The results would be:

bool(true)
bool(true)
bool(false)
bool(false)
bool(false)

Upvotes: 1

Nick
Nick

Reputation: 147146

I think it's best to do this with a combination of regex and regular code. This function checks to see if the entire string matches a pattern of numbers or ranges separated by commas, then extracts the individual numbers or ranges and applies the other error checking (between 1 and 31, end >= start) to them:

function validate_range($range) {
    if (!preg_match('/^((\d+(?:-\d+)?)(?:,(?!$)|$))+$/', $range)) return false;
    preg_match_all('/(\d+(?:-\d+)?)/', $range, $matches);
    foreach ($matches[1] as $match) {
        if (strpos($match, '-') !== false) {
            list($start, $end) = explode('-', $match);
            if ($end < $start) return false;
            if ($start < 1 || $start > 31 || $end < 1 || $end > 31) return false;
        }
        if ($match < 1 || $match > 31) return false;
    }
    return true;
}

You can test it like this:

$ranges = array(
    '4,31,2-22,8',
'29,1-10,2-12,9',
'4,31,2-22,8,',
'29,1-10,-2-12-,9',
'29,1-50,12-2,32');
foreach ($ranges as $range) {
    if (validate_range($range)) 
        echo "$range is OK!\n";
    else
        echo "$range is no good\n";
}

Output:

4,31,2-22,8 is OK!
29,1-10,2-12,9 is OK! 
4,31,2-22,8, is no good 
29,1-10,-2-12-,9 is no good 
29,1-50,12-2,32 is no good

Demo on 3v4l.org

Upvotes: 1

Related Questions