Reputation: 12005
The best way to explain my problem is to just show you.
Input String:
/04-11-2010/12:45/
Regular Expression to get date and time parts:
preg_match('@/(\d\d)-(\d\d)-(\d\d\d\d)/(\d\d):(\d\d)/@', $input, $matches);
PHP Matches Array:
Array
(
[0] => /01-11-2010/12:45/
[1] => 01
[2] => 11
[3] => 2010
[4] => 12
[5] => 45
)
Now the above regex works perfectly at getting the individual component parts that represent the date and time in the input string.
The problem is that the time part needs to be optional without bringing down the entire regular expression.
Problem Input String:
/04-11-2010//
PHP Matches Array
Array
(
)
Basically what I need to be returned by the matches array is:
Array
(
[0] => /01-11-2010/12:45/
[1] => 01
[2] => 11
[3] => 2010
[4] =>
[5] =>
)
Note array elements 4 and 5 still need to exist but return empty.
Upvotes: 0
Views: 129
Reputation: 47894
I'll demonstrate a few working techniques to parse or extract data from your time-optional DateTime expressions. See this demo for proof of their output.
The first consideration for parsing date/datetime expressions should be a legitimate datetime parser. Only deviate from this choice if you have a compelling reason.
In this case, parse the dynamic expression after determining if it has a time component. Either way, add a pipe to the end of the format parameter to zero-out any missing datetime values. This creates an object instead of an array, but depending on your next step, this may be an ideal source to extract values from
var_export(
DateTime::createFromFormat(
str_contains($test, ':')
? '/d-m-Y/H:i/|'
: '/d-m-Y//|',
$test
)
);
For the most direct approach to populate the desired array, sscanf()
is viable because the optional components are at the end of the string. The trailing time values will be returned as null
elements if they are not encountered.
var_export(
sscanf(
$test,
'/%02s-%02s-%04s/%02[^/:]:%02[^/:]'
)
);
A regular expression of one or more non-digital characters fed to preg_split()
is very easy on human eyes and returns a flat array, but will need to be padded with null
time elements when they are not supplied in the input string.
var_export(
array_pad(
preg_split(
'/\D+/',
$test,
0,
PREG_SPLIT_NO_EMPTY
)
5,
null
),
);
preg_match()
may be a comfortable call for developers with basic regex skills and it has the advantage over preg_split()
if validation is required, but its output is a reference variable which includes the full string match which must be shifted off. Make the time expression optional and set the PREG_UNMATCHED_AS_NULL
flag to gain null
elements when missing time values.
var_export(
preg_match(
'#/(\d{2})-(\d{2})-(\d{4})/(?:(\d{2}):(\d{2})/)?#',
$test,
$matches,
PREG_UNMATCHED_AS_NULL
)
? array_slice($matches, 1)
: []
);
And finally, the least attractive option is to tokenize the string. Not only is a loop used inside of this IIFE to make iterated strtok()
calls, the returned array still needs to append null
time elements. It's just plain fugly.
var_export(
(function($tokens) {
while ($t = strtok('/:-')) {
$tokens[] = $t;
}
return $tokens + [3 => null, 4 => null];
})([strtok($test, '/:-')])
);
Upvotes: 0
Reputation: 1762
Use native PHP functions for this task, using regular expressions is a bit of an overkill.
PHP 5 has the date_parse function:
$string = '/04-11-2010/12:45/';
$dateArray = date_parse(str_replace('/', ' ', $string));
print_r($dateArray);
$string = '/04-11-2010//';
$dateArray = date_parse(str_replace('/', ' ', $string));
print_r($dateArray);
Output:
Array
(
[year] => 2010
[month] => 11
[day] => 4
[hour] => 12
[minute] => 45
[second] => 0
[fraction] => 0
[warning_count] => 0
[warnings] => Array
(
)
[error_count] => 0
[errors] => Array
(
)
[is_localtime] =>
)
Array
(
[year] => 2010
[month] => 11
[day] => 4
[hour] =>
[minute] =>
[second] =>
[fraction] =>
[warning_count] => 0
[warnings] => Array
(
)
[error_count] => 0
[errors] => Array
(
)
[is_localtime] =>
)
PHP 5.3 has a more flexible date_parse_from_format function that you could also use.
Upvotes: 0
Reputation: 342373
@OP, don't need messy regex.
$str="/04-11-2010/12:45/";
$s = array_filter(explode('/',$str));
$date=$s[1];
$time=$s[2];
$date_parts=explode("-",$date);
$time_parts=explode(":",$time);
if ( checkdate($date_parts[1],$date_parts[0],$date_parts[2]) ){
print "date ok\n";
}
Upvotes: 0
Reputation: 59983
Use the question mark operator and a non-capturing group to make stuff optional.
@/(\d\d)-(\d\d)-(\d\d\d\d)/(?:(\d\d):(\d\d))?/@
I'm not sure how this interacts with the match array - if having the empty array elements is absolutely critical, you might need to instead go for
@/(\d\d)-(\d\d)-(\d\d\d\d)/((?:\d\d)?):?((?:\d\d)?)/@
Which has its own false-positives (the colon in the time is now optional).
Upvotes: 2
Reputation: 53940
@/(\d\d)-(\d\d)-(\d\d\d\d)/((?:\d\d)?):?((?:\d\d)?)/@
does what you want (i.e. populates groups 4 and 5), but also accepts incomplete times like in
/04-11-2010/12:/
don't know if this is fine with you
Upvotes: 1
Reputation: 11831
I'm not a php-head, but how about:
preg_match('@/(\d\d)-(\d\d)-(\d\d\d\d)/(\d\d)?:?(\d\d)?/@', $input, $matches);
As far as regexps go, that should match a string that has no time field.
Upvotes: 0
Reputation: 655239
Make the second part optional:
'@/(\d\d)-(\d\d)-(\d\d\d\d)/(?:(\d\d):(\d\d))?/@'
Here a non-capturing group (?:…)
is used that cannot be referenced and thus doesn’t change the matching groups.
Upvotes: 1