Time String to Seconds - php

How can i parse strings with regex to calculate the total seconds?
The strings will be in example:
40s
11m1s
1h47m3s
I started with the following regex
((\d+)h)((\d+)m)((\d+)s)
But this regex will only match the last example.
How can i make the parts optional?
Is there a better regex?

The format that you are using is very similar to the one that is used by java.time.Duration:
https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-
Maybe you can use it instead of writing something custom?
Duration uses a format like this:
P1H47M3S
Maybe you can add the leading "P", and parse it (not sure if you have to uppercase)?
The format is called "ISO-8601":
https://en.wikipedia.org/wiki/ISO_8601
For example,
$set = array(
'40s',
'11m1s',
'1h47m3s'
);
$date = new DateTime();
$date2 = new DateTime();
foreach ($set as $value) {
$date2->add(new DateInterval('PT'.strtoupper($value)));
}
echo $date2->getTimestamp() - $date->getTimestamp(); // 7124 = 1hour 58mins 44secs.

You could use optional non-capture groups, for each (\dh, \dm, \ds):
$strs = ['40s', '11m1s', '1h47m3s'];
foreach ($strs as $str) {
if (preg_match('~(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?~', $str, $matches)) {
print_r($matches);
}
}
Outputs:
Array
(
[0] => 40s
[1] => // h
[2] => // m
[3] => 40 // s
)
Array
(
[0] => 11m1s
[1] => // h
[2] => 11 // m
[3] => 1 // s
)
Array
(
[0] => 1h47m3s
[1] => 1 // h
[2] => 47 // m
[3] => 3 // s
)
Regex:
(?: # non-capture group 1
( # capture group 1
\d+ # 1 or more number
) # end capture group1
h # letter 'h'
) # end non-capture group 1
? # optional
(?: # non-capture group 2
( # capture group 2
\d+ # 1 or more number
) # end capture group1
m # letter 'm'
) # end non-capture group 2
? # optional
(?: # non-capture group 3
( # capture group 3
\d+ # 1 or more number
) # end capture group1
s # letter 's'
) # end non-capture group 3
? # optional

This expression:
/(\d*?)s|(\d*?)m(\d*?)s|(\d*?)h(\d*?)m(\d*?)s/gm
returns 3 matches, one for each line. Each match is separated into the salient groups of only numbers.
The gist is that this will match either any number of digits before an 's' or that plus any number of digits before an 'm' or that plus any number of digits before an 'h'.

Related

regexp monetary strings with decimals and thousands separator

https://www.tehplayground.com/KWmxySzbC9VoDvP9
Why is the first string matched?
$list = [
'3928.3939392', // Should not be matched
'4.239,99',
'39',
'3929',
'2993.39',
'393993.999'
];
foreach($list as $str){
preg_match('/^(?<![\d.,])-?\d{1,3}(?:[,. ]?\d{3})*(?:[^.,%]|[.,]\d{1,2})-?(?![\d.,%]|(?: %))$/', $str, $matches);
print_r($matches);
}
output
Array
(
[0] => 3928.3939392
)
Array
(
[0] => 4.239,99
)
Array
(
[0] => 39
)
Array
(
[0] => 3929
)
Array
(
[0] => 2993.39
)
Array
(
)
You seem to want to match the numbers as standalone strings, and thus, you do not need the lookarounds, you only need to use anchors.
You may use
^-?(?:\d{1,3}(?:[,. ]\d{3})*|\d*)(?:[.,]\d{1,2})?$
See the regex demo
Details
^ - start of string
-? - an optional -
(?: - start of a non-capturing alternation group:
\d{1,3}(?:[,. ]\d{3})* - 1 to 3 digits, followed with 0+ sequences of ,, . or space and then 3 digits
| - or
\d* - 0+ digits
) - end of the group
(?:[.,]\d{1,2})? - an optional sequence of . or , followed with 1 or 2 digits
$ - end of string.

preg_match_all split conditional expression

I have data in this format:
Randomtext1(random2, random4) Randomtext2 (ran dom) Randomtext3 Randomtext4 (random5,random7,random8) Randomtext5 (Randomtext4 (random5,random7,random8), random10) Randomtext11()
with this:
preg_match_all("/\b\w+\b(?:\s*\(.*?\)|)/",$text,$matches);
I obtain:
0 => 'Randomtext1(random2, random4)',
1 => 'Randomtext2 (ran dom)',
2 => 'Randomtext3',
3 => 'Randomtext4 (random5,random7,random8)',
4 => 'Randomtext5 (Randomtext4 (random5,random7,random8)',
5 => 'random10',
6 => 'Randomtext11()',
but I want
0 => 'Randomtext1(random2, random4)',
1 => 'Randomtext2 (ran dom)',
2 => 'Randomtext3',
3 => 'Randomtext4 (random5,random7,random8)'
4 => 'Randomtext5 (Randomtext4 (random5,random7,random8), random10)'
5 => 'Randomtext11()'
Any ideas?
You need a recursive pattern to handle nested parenthesis:
if ( preg_match_all('~\w+(?:\s*(\([^()]*+(?:(?1)[^()]*)*+\)))?~', $text, $matches) )
print_r($matches[0]);
demo
details:
~ # delimiter
\w+
(?:
\s*
( # capture group 1
\(
[^()]*+ # all that isn't a round bracket
# (possessive quantifier *+ to prevent too many backtracking
# steps in case of badly formatted string)
(?:
(?1) # recursion in the capture group 1
[^()]*
)*+
\)
) # close the capture group 1
)? # to make the group optional (instead of "|)")
~
Note that you don't need to add word-boundaries around \w+

PHP Regex Matching Multiple Options

I am attempting to write some code that looks for the following:
Yesterday
Last 7 Days
Last 30 Days
This Year
Last Year
I have the following regex:
/yesterday|(\d+)(?=\s+(\w+))|(\w+)(?=\s+(year))/i
using:
preg_match("/yesterday|(\d+)(?=\s+(\w+))|(\w+)(?=\s+(year))/i", $input, $output)
I get the following results using phpliveregex.com with the preg_match:
array(5
0 => Last
1 =>
2 =>
3 => Last
4 => Year
)
array(5
0 => This
1 =>
2 =>
3 => This
4 => year
)
array(1
0 => yesterday
)
array(3
0 => 30
1 => 30
2 => days
)
array(3
0 => 7
1 => 7
2 => days
My issue is with the 'Year' options and the fact that they have empty keys because I want to refer to $output[1] and $output[2] to get the interval and 'span' (days). Only a single string will be passed at a time so it will be one of the options listed above and not multiple options to look for at once.
If anyone can help me find the best solution to return 'yesterday' or ('7' and 'days') or ('30' and 'days') or ('This' and 'Year') or ('Last' and 'Year') I would appreciate it very much!
EDIT
This is my desired output:
'Yesterday'
$output[0] => 'Yesterday'
'Last 7 Days'
$output[0] => '7'
$output[1] => 'Days'
'Last 30 Days'
$output[0] => '30'
$output[1] => 'Days'
'This Year'
$output[0] => 'This'
$output[1] => 'Year'
'Last Year'
$output[0] => 'Last'
$output[1] => 'Year'
I am trying to capture the 'groups' necessary to process the rest of my code.
You can use the branch reset feature to avoid empty groups:
$text = <<<'EOD'
Yesterday
Last 7 Days
Last 30 Days
This Year
Last Year
EOD;
$pattern = '~\b(?|yesterday\b|\d+(?= (days\b))|\w+(?= (year\b)))~i';
if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER))
print_r($matches);
// or preg_match without PREG_SET_ORDER if you test the strings one by one
pattern details:
\b
(?| # open the branch reset group
yesterday \b # when this branch succeeds the capture group is not defined
|
\d+ (?=[ ](days\b)) # in each branch the capture group
|
\w+ (?=[ ](year\b)) # has the same number
) # (so there is only one capture group)
result:
Array
(
[0] => Array
(
[0] => Yesterday
)
[1] => Array
(
[0] => 7
[1] => Days
)
[2] => Array
(
[0] => 30
[1] => Days
)
[3] => Array
(
[0] => This
[1] => Year
)
[4] => Array
(
[0] => Last
[1] => Year
)
)
Note that when you build the branch reset, you must begin with alternatives that has no groups, then alternatives with one groups, then two groups, etc. otherwise you may obtain useless empty groups in the result.
Note too that the group 0 isn't really a capture group but it is the whole match.
You can use:
/((?:Last|This)\s+(?:\d+\s+Days|Year)|Yesterday)/
Matches:
MATCH 1
1. [0-9] `Yesterday`
MATCH 2
1. [10-21] `Last 7 Days`
MATCH 3
1. [22-34] `Last 30 Days`
MATCH 4
1. [35-44] `This Year`
MATCH 5
1. [45-54] `Last Year`
Regex Demo:
https://regex101.com/r/mA8jZ5/1
Regex Explanation:
/((?:Last|This)\s+(?:\d+\s+Days|Year)|Yesterday)/
1st Capturing group ((?:Last|This)\s+(?:\d+\s+Days|Year)|Yesterday)
1st Alternative: (?:Last|This)\s+(?:\d+\s+Days|Year)
(?:Last|This) Non-capturing group
1st Alternative: Last
Last matches the characters Last literally (case sensitive)
2nd Alternative: This
This matches the characters This literally (case sensitive)
\s+ match any white space character [\r\n\t\f ]
Quantifier: + Between one and unlimited times, as many times as possible, giving back as needed [greedy]
(?:\d+\s+Days|Year) Non-capturing group
1st Alternative: \d+\s+Days
\d+ match a digit [0-9]
Quantifier: + Between one and unlimited times, as many times as possible, giving back as needed [greedy]
\s+ match any white space character [\r\n\t\f ]
Quantifier: + Between one and unlimited times, as many times as possible, giving back as needed [greedy]
Days matches the characters Days literally (case sensitive)
2nd Alternative: Year
Year matches the characters Year literally (case sensitive)
2nd Alternative: Yesterday
Yesterday matches the characters Yesterday literally (case sensitive)
What you just described can be Achieved with the following Regex:
(yesterday|\d+(?=\s+\w+)|\w+(?=\s+year))\s*(\w*)$
Tested on Regex101.com Demo Here :

Regexp tip request

I have a string like
"first,second[,b],third[a,b[1,2,3]],fourth[a[1,2]],sixth"
I want to explode it to array
Array (
0 => "first",
1 => "second[,b]",
2 => "third[a,b[1,2,3]]",
3 => "fourth[a[1,2]]",
4 => "sixth"
}
I tried to remove brackets:
preg_replace("/[ ( (?>[^[]]+) | (?R) )* ]/xis",
"",
"first,second[,b],third[a,b[1,2,3]],fourth[a[1,2]],sixth"
);
But got stuck one the next step
PHP's regex flavor supports recursive patterns, so something like this would work:
$text = "first,second[,b],third[a,b[1,2,3]],fourth[a[1,2]],sixth";
preg_match_all('/[^,\[\]]+(\[([^\[\]]|(?1))*])?/', $text, $matches);
print_r($matches[0]);
which will print:
Array
(
[0] => first
[1] => second[,b]
[2] => third[a,b[1,2,3]]
[3] => fourth[a[1,2]]
[4] => sixth
)
The key here is not to split, but match.
Whether you want to add such a cryptic regex to your code base, is up to you :)
EDIT
I just realized that my suggestion above will not match entries starting with [. To do that, do it like this:
$text = "first,second[,b],third[a,b[1,2,3]],fourth[a[1,2]],sixth,[s,[,e,[,v,],e,],n]";
preg_match_all("/
( # start match group 1
[^,\[\]] # any char other than a comma or square bracket
| # OR
\[ # an opening square bracket
( # start match group 2
[^\[\]] # any char other than a square bracket
| # OR
(?R) # recursively match the entire pattern
)* # end match group 2, and repeat it zero or more times
] # an closing square bracket
)+ # end match group 1, and repeat it once or more times
/x",
$text,
$matches
);
print_r($matches[0]);
which prints:
Array
(
[0] => first
[1] => second[,b]
[2] => third[a,b[1,2,3]]
[3] => fourth[a[1,2]]
[4] => sixth
[5] => [s,[,e,[,v,],e,],n]
)

regex to match 3 parts from a given string

Example input:
hjkhwe5boijdfg
I need to split this into 3 variables as below:
hjkhwe5 (any length, always ends in some number (can be any number))
b (always a single letter, can be any letter)
oijdfg (everything remaining at the
end, numbers or letters in any combination)
I've got the PHP preg_match all setup but have no idea how to do this complex regex. Could someone give me a hand?
Have a try with:
$str = 'hjkhwe5boijdfg';
preg_match("/^([a-z]+\d+)([a-z])(.*)$/", $str, $m);
print_r($m);
output:
Array
(
[0] => hjkhwe5boijdfg
[1] => hjkhwe5
[2] => b
[3] => oijdfg
)
Explanation:
^ : begining of line
( : 1rst group
[a-z]+ : 1 or more letters
\d+ : followed by 1 or more digit
) : end of group 1
( : 2nd group
[a-z] : 1 letter
) : end group 2
( : 3rd group
.* : any number of any char
) : end group 3
$
You can use preg_match as:
$str = 'hjkhwe5boijdfg';
if(preg_match('/^(\D*\d+)(\w)(.*)$/',$str,$m)) {
// $m[1] has part 1, $m[2] has part 2 and $m[3] has part 3.
}
See it

Categories