2

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

  • there will only be numbers separated by , or -
  • there may be ranges of numbers (separated by -)
  • the numbers should go from 1 to 31
  • the value of the second part is greater than the first one in a range (this is more complex, but if it is very difficult I will discard it)

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?

5
  • 3
    Suggestion: Don't use regex to do that Commented Feb 28, 2019 at 12:18
  • @Jerry Because it is very complicated to control or is it impossible? Commented Feb 28, 2019 at 12:20
  • 2
    1. You are having trouble making it work, while you could have already gone and done that without using regex but using split/comparison/loops, 2. maintenance, readability suffers a lot especially when the string that needs to be parsed is being given meaning/value that need to abide to certain restrictions. The current answer for instance accepts 39 as well, and does not fulfil the condition for ranges. Commented Feb 28, 2019 at 12:24
  • 1
    @Joacer It is not impossible, as you can always write out every combination. But that is indeed complicated to control. Commented Feb 28, 2019 at 12:26
  • Thanks for your help, I will leave the complications and I will focus on doing it as @Jerry says without regular expressions Commented Feb 28, 2019 at 12:28

2 Answers 2

1

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)
Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.