Regex Javascript Capture groups with quantifier Not Working

Viewed 199

I have this nice regex:

 *(?:(?:([0-9]+)(?:d| ?days?)(?:, ?| )?)|(?:([0-9]+)(?:h| ?hours?)(?:, ?| )?)|(?:([0-9]+)(?:m| ?minutes?)(?:, ?| )?)|(?:([0-9]+)(?:s| ?seconds?)(?:, ?| )?))+

that pretty much matches a human-readable time-delta. It works on php, python, and go, but for some reason the capture groups do not work on javascript. Here is a working php example on regex101 that shows the working capture groups. You will notice that upon changing it to javascript (ECMAscript) mode, the capture group will only capture the last value. Can somebody please help and clarify what I am doing wrong, and whu it doesn't work on js?

1 Answers

Here's a simpler example that demonstrates the issue:

console.log(
  '34'.match(/(?:(3)|(4))+/)
);

In PHP, whenever a capture group is matched, it will be put into the result. In contrast, in JavaScript, things are more complicated: when there are capturing groups on one side of an alternation |, whenever the whole alternation token is entered, there are 2 possibilities:

  • The alternation that is taken contains the capture group, and the result will have the capture group index set to the matched value
  • The alternation that is taken does not contain the capture group, in which case the result will have undefined assigned to that index - even if the capturing group was matched previously.

This is described in the specification:

Any capturing parentheses inside a portion of the pattern skipped by | produce undefined values instead of Strings.

and

Step 4 of the RepeatMatcher clears Atom's captures each time Atom is repeated.

because each iteration of the outermost * clears all captured Strings contained in the quantified Atom


In your case, the easiest tweak to fix it would be to remove the repeating outermost capturing group, so that only one subsequence is matched at a time, eg 1m, and then 1d, then iterate through the matches, instead of trying to match everything all in one go. To ensure that all the matches are next to each other (eg 1m1d, and not 1m 1d), check the index while iterating through the matches to see if it's next to a previous match or not.

Related