Matching hour-minute-second (hms) duration string

Viewed 47

I want to match the digits before h, m, and s into their respective capture groups. I want to match all 3 groups if possible. If one or two of the other groups are missing then match the last group(s).

As seen in the image below and in this regex101, I have currently a regex of
/(\d+)(?=h)|(\d+)(?=m)|(\d+)(?=s)/g, which matches, for my test string, 12 matches with 1 group in each match.

regex and string

But I want 7 matches. Where the first match contains 3 groups, the 2:nd to 4:th match contain 2 groups each and the last 3 matches contain 1 group each.

So I want:

  1. Match 1: 11h22m33s
    1. Group 1: 11
    2. Group 2: 22
    3. Group 3: 33
  2. Match 2: 11h22m
    1. Group 1: 11
    2. Group 2: 22
  3. Match 3: 11h33s
    1. Group 1: 11
    2. Group 2: 33
  4. Match 4: 22m33s
    1. Group 1: 22
    2. Group 2: 33
  5. Match 5: 11h
    1. Group 1: 11
  6. Match 6: 22m
    1. Group 1: 22
  7. Match 7: 33s
    1. Group 1: 33

Edit

The test strings can be contained within other strings! E.g. 08:00 + 11h. See https://regex101.com/r/RWA9Oy/1

1 Answers

If you need to make sure the whole string matches the pattern, you can use

^(?!$)(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$

See the regex demo.

If you need to extract these strings from the longer texts, you can use

\b(?=\w)(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?\b(?!\w)

See this regex demo.

Details:

  • ^ - start of string
  • (?!$) - the string can't end at the very start (=cannot be empty)
  • \b(?=\w) - a word boundary where the char immediately on the right is a word char (=is a letter, digit or _)
  • (?:(\d+)h)? - an optional non-capturing group matching one or more digits (capturing them into Group 1) and a h letter
  • (?:(\d+)m)? - an optional non-capturing group matching one or more digits (capturing them into Group 2) and a m letter
  • (?:(\d+)s)? - an optional non-capturing group matching one or more digits (capturing them into Group 3) and a s letter
  • $ - end of string
  • \b(?!\w) - a word boundary where the char immediately on the right is NOT a word char or at the end of string.

Just in case you want allow any amount of whitespace between the components, you may add \s*:

^(?!\s*$)\s*(?:(\d+)\s*h\s*)?(?:(\d+)\s*m\s*)?(?:(\d+)\s*s)?\s*$

Or, for partial matching

\b(?=\w)(?:(\d+)\s*h\s*)?(?:(\d+)\s*m\s*)?(?:(\d+)\s*s)?\b(?!\w)

See this regex demo and another regex demo.

Related