neubert
neubert

Reputation: 16792

matching a part of a string from the beginning

I have a date (eg. yyyy-mm-dd hh:mm:ss) and I want to be able to return true if we have a string matching that pattern, completely, or that partially matches that pattern, from beginning to end. eg. 44 would match the pattern because 44 would match the yy but -12 would not match because - isn't a valid "y" character.

A few inelegant solutions occur to me. I could do preg_match on something like this:

#^\d(\d(\d(\d(-)?)?)?)?$#

That just performs a partial match based on the year. It'd need to be expanded out to account for the month, day, hour, minute, etc, but that should show idea # 1.

I could also do something like...

$patterns = [
    '',
    '\d',
    '\d\d',
    '\d\d\d',
    '\d\d\d\d',
    '\d\d\d\d-',
    ...
];
isset($patterns[strlen($str)] && preg_match('#^' . $patterns[strlen($str)] . '$#', $str)

But that kinda seems convoluted as well.

I could also do this:

switch (strlen($str)) {
    case 1: return preg_match('#^\d$#', $str);
    case 2: return preg_match('#^\d\d$#', $str);
    case 3: return preg_match('#^\d\d\d$#', $str);
    case 4: return preg_match('#^\d\d\d\d$#', $str);
    case 5: return preg_match('#^\d\d\d\d-$#', $str);
    ...
}

But that seems bloated as well.

In other words, I'm looking for a function for which 2005- will return true, as will 2 and 2005-1. But -2005 will return false, as will 205- or neubert.

What I want to be able to do is to pass valid values into an SQL query. eg. WHERE date_column LIKE '$str%'. If date_column is a DATETIME then searching for -12- is a waste of time because it's not possible for date_column to have that as a value.

Any ideas?

Upvotes: 4

Views: 81

Answers (4)

mister martin
mister martin

Reputation: 6252

I know this question has already been answered and closed, but I thought it was an interesting challenge and I was determined to come up with a non-regex solution. I also wanted it to be more reusable, rather than hard-coded for one specific case.

Here is what I ended up with; I'm sure it could be further improved! :)

// case-insensitive string format comparison
// if $strict is true, the string lengths must also match
// if $strict is false, the strings are compared left to right
function strfcasecmp($str, $format, $strict = false) {
    $len1 = strlen($str);
    $len2 = strlen($format);

    // make sure we have a valid length
    if ($len1 < 1 || $len1 > $len2) {
        return false;
    }

    // if strict, make sure length matches as well
    if ($strict) {
        if ($len1 !== $len2) {
            return false;
        }
    }

    // compare alpha, numeric, space & printable characters
    for ($i = 0; $i < $len1; $i++) {
        switch (true) {
            case ctype_alpha($format[$i]):
                if (!ctype_alpha($str[$i])) {
                    return false;
                }
                break;

            case ctype_digit($format[$i]):
                if (!ctype_digit($str[$i])) {
                    return false;
                }
                break;

            case ctype_space($format[$i]):
                if (!ctype_space($str[$i])) {
                    return false;
                }
                break;

            case ctype_punct($format[$i]):
                if ($str[$i] !== $format[$i]) {
                    return false;
                }
                break;

            default:
                // character must match at least one type specified above
                return false;
        }
    }

    return true;
}

Example usage based on your original question:

$format = '0000-00-00 00:00:00';
var_dump(strfcasecmp('44', $format)); // true
var_dump(strfcasecmp('-12', $format)); // false
var_dump(strfcasecmp('2005-', $format)); // true
var_dump(strfcasecmp('2', $format)); // true
var_dump(strfcasecmp('2005-1', $format)); // true
var_dump(strfcasecmp('-2005', $format)); // false
var_dump(strfcasecmp('205-', $format)); // false
var_dump(strfcasecmp('neubert', $format)); // false

Upvotes: 1

Mr. Llama
Mr. Llama

Reputation: 20899

Something like this should work:

function doesMatch($str) {
    // The date pattern split so that each array entry matches exactly one character
    $pattern_chunks = array(
        '\d', '\d', '\d', '\d', '-',
        '\d', '\d', '-',
        '\d', '\d',
        '\s',
        '\d', '\d', ':',
        '\d', '\d', ':',
        '\d', '\d'
    );

    $chunk_count = count($pattern_chunks);
    $str_len = strlen($str);

    // If the string is empty, it's clearly not a date
    if ( $str_len < 1 ) { return false; }

    // If the string is longer than our pattern chunks, there's no way it matches
    if ( $str_len > $chunk_count ) { return false; }

    // Make a pattern using the first N chunks of our pattern parts
    $pattern = '^' . implode('', array_slice($pattern_chunks, 0, $str_len)) . '$';

    // Return if the string matches
    return (preg_match($pattern, $str) > 0);
}

For example your input string is 6 characters long, it only uses the first 6 chunks of the date pattern (^\d\d\d\d-\d$).

Upvotes: 2

anubhava
anubhava

Reputation: 785246

Based on comments below your question and if I understood the question right, you can use this single regex to match your inputs:

^(?:\d{1,3}|\d{4}(?:-(?:\d{1,2}(?:-\d{0,2})?)?)?)$

RegEx Demo

Upvotes: 2

Peter
Peter

Reputation: 9123

You could also match using strpos():

$a = "2016-05-17 was a good day.";
if (strpos(substr($a, 0, 10), '2016-05-17') !== false) {
    echo "Indeed it was";
}

Upvotes: 0

Related Questions