Reputation: 191
In JavaScript, I'm looking for a regex to catch multiple optional groups in a string. But at least one of the groups should exist.
String: foo bar 12 seconds 3minutes 4h
Regex so far: /(?:(\d+)\s?s(?:econds?)?)?(?:(\d+)\s?m(?:inutes?)?)?(?:(\d+)\s?h(?:ours?)?)?/gi
I need to capture 12 seconds
3minutes
and 4h
, returning only the number values in their respective groups.
These time units can either exist or be swapped around. My final result would need to look like this:
12s 3m //['12', '3', undefined]
10 seconds //['10', undefined, undefined]
4hours //[undefined, undefined, '4']
3 minutes //[undefined, '3', undefined]
1hour 54seconds 7minutes //['54', '7', '1']
undefined
null
or even an empty string. As long as they are in their respective index.
Any easy way to handle this with a single exec
or match
without using loops?
Upvotes: 0
Views: 619
Reputation: 34385
As Wiktor correctly states, there is no way to do this with a single regex. Here is a simple function that implements a 3-regex solution:
function get_time_parts(text) {
var s, m, h;
// Seconds part: Either "s", "sec", "secs" "second" or "seconds".
s = text.match(/\b(\d+)\s*s(?:ec(?:ond)?s?)?\b/i);
s = s ? s[1] : undefined;
// Minutes part: Either "m", "min", "mins" "minute" or "minutes".
m = text.match(/\b(\d+)\s*m(?:in(?:ute)?s?)?\b/i);
m = m ? m[1] : undefined;
// Hours part: Either "h", "hr", "hrs" "hour" or "hours".
h = text.match(/\b(\d+)\s*h(?:rs?|ours?)?\b/i);
h = h ? h[1] : undefined;
return (s || m || h) ? [s, m, h] : null;
}
As stated in the comments, this function allows the following time part variations:
Seconds part: Either "s", "sec", "secs" "second" or "seconds".
Minutes part: Either "m", "min", "mins" "minute" or "minutes".
Hours part: Either "h", "hr", "hrs" "hour" or "hours".
The regexes are case insensitive so will allow variations, e.g. HR, Sec, mIN, etc. If none of the parts are present, the function returns null.
Upvotes: 1
Reputation: 1363
There is no simple solution to do this with plain regex. The simplest solution is to use the exec
method and to set the values to hash (object). Furthermore you can simplify your regex - All inute, econd, our are completely useless in your regex. If you want only s or second you should use (?:s|second)
, because in your example 5 samples would match too.
The easiest solution for your problem (without handling the order of the units):
var str = "foo bar 12 seconds 5m 4hours";
var re = /(\d+)\s*([smh])/gi
var hash = {};
var m;
while ((m = re.exec(str)) !== null) {
// get values
var value = m[1];
var unit = m[2].toLowerCase();
// set value
hash[unit] = value;
}
console.log(hash);
This solution will always use the last occur and will not depend on the order of the units.
Upvotes: 1
Reputation: 1794
Not sure this matches to you various types of input strings, but here is something that I came up with for the input string you put up there. I assumed the seconds come first, minutes next and then hours as you have it in your question input string. Is this order correct all the time?
let str = "foo bar 12 seconds 3minutes 4h";
let result = str.match(/(\d+) ?(?:sec|seconds) ?(\d+) ?(?:min|minutes) ?(\d+) ?(?:h|hours?)/);
console.log(`${result[3]}hour ${result[1]}second ${result[2]}minutes`);
Upvotes: -1