PatternRegex

Match a time in 24-hour format

Match HH:MM on the 24-hour clock with real bounds: hours stop at 23 and minutes at 59, so 24:00 and 18:75 fail like they should.

Last updated

Patterng
(?:[01]\d|2[0-3]):[0-5]\d
Open in Regex Tester & Builder

Matches

  • 09:30
  • 23:59
  • 00:00

Doesn’t match

  • 24:00
  • 9:30
  • 18:75

Why it’s written this way

The hour is two alternatives, because its two digits aren't independent: 0x and 1x allow any second digit, but 2x only allows 0–3. Writing [0-2]\d instead is the classic bug — it happily accepts 27:00.

Minutes are simpler — first digit 0–5, second anything — so one class each does it.

Edge cases to know

  • →Single-digit hours without a leading zero (9:30) don't match; allow them with (?:[01]?\d|2[0-3]).
  • →Inside longer digit runs it can find a valid-looking substring (123:45 contains 23:45) — anchor with ^…$ when validating a whole field.
  • →Seconds (HH:MM:SS) need an explicit (?::[0-5]\d)? added; this pattern stops at minutes.

Part by part

(?:[01]\d|2[0-3])
The hour, expressed as two alternatives so that only 00 through 23 can match. A non-capturing group keeps it out of your results.
[01]\d
Covers 00 to 19: a leading 0 or 1 followed by any digit.
2[0-3]
Covers 20 to 23 — the reason 24:00 and 29:00 are rejected rather than quietly accepted.
:
A literal colon. It needs no escaping because a colon has no special meaning in regex.
[0-5]\d
The minutes, 00 to 59. The first digit is capped at 5, which is what stops 09:75 matching.

Common mistakes

  • \d{2}:\d{2}

    The obvious shortcut accepts 99:99. It is fine when you already trust the input, but it is not a validator — it only checks the shape, not the value.

    (?:[01]\d|2[0-3]):[0-5]\d
  • (?:[0-2]\d):[0-5]\d

    Capping the first digit at 2 looks right but permits 24 through 29. The alternation exists precisely because the valid range of the second digit depends on the first.

  • ([01]\d|2[0-3]):[0-5]\d

    A capturing group here costs you nothing at match time but fills every result with an extra element you did not ask for, which is noise when you are mapping over matches.

Questions

Does this match times with seconds?

No. It stops after the minutes, so in "14:30:59" it matches "14:30" and leaves the rest. Append (?::[0-5]\d)? if you want the seconds to be matched when present and skipped when they are not.

Why is 24:00 rejected when some systems use it?

ISO 8601 does permit 24:00 as an end-of-day marker, but almost nothing else does, and accepting it means downstream code has to handle an hour that no clock displays. If you need it, add |24:00 as an alternative.

Will it find a time inside a longer number?

Yes, because it is deliberately unanchored so it can pull timestamps out of log lines. In "1114:305" it will match "14:30". Wrap it in ^...$ when you are validating a field rather than scanning text.

How do I match a 12-hour time with am or pm instead?

That is a different shape, not a variation on this one: the hour range becomes 1 to 12 with no leading zero required, and the suffix is mandatory. Use (?:1[0-2]|0?[1-9]):[0-5]\d\s?(?:[AaPp][Mm]) and keep the two patterns separate rather than trying to cover both at once.

Related in Patterns