Every language
11 langages, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
const re = /(?<year>\d{4})-(?<month>\d{2})/u; // the (?< spelling is ES2018
const m = '2026-08-26'.match(re);
m.groups.year; // '2026'
m.groups.month; // '08'
// a group that skipped the match is undefined — NOT '':
const tz = /(?<tz>Z|[+-]\d{2}:\d{2})?/u.exec('2026-08-26');
const zone = tz?.groups?.tz ?? 'UTC'; // 'UTC' — never test with === ''The named syntax is ES2018: engines before it throw SyntaxError at parse time, never at match time. The u flag rides along for the same modern baseline and keeps \p{...} classes available. A group that did not participate reads as undefined — default it with ?? , never === ''.
TSTypeScript
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
function parseYear(s: string): number | null {
const m = dateRe.exec(s);
const year = m?.groups?.year; // string | undefined
return year !== undefined ? Number(year) : null;
}exec returns RegExpExecArray | null — the ?. chain absorbs the null match and the absent groups bag in one hop. The lib types the bag's values as string, which is optimistic: a group that skipped is undefined at runtime, so treat every named group as string | undefined.
GoGo
import "regexp"
// Go kept the P: (?P<Name>...) — the .NET spelling fails MustCompile.
var dateRe = regexp.MustCompile(`(?P<Year>\d{4})-(?P<Month>\d{2})`)
func dateParts(s string) (year, month string, ok bool) {
m := dateRe.FindStringSubmatch(s)
if m == nil {
return "", "", false
}
for i, name := range dateRe.SubexpNames() { // [0] is the whole pattern
switch name {
case "Year":
year = m[i]
case "Month":
month = m[i]
}
}
return year, month, true
}No GroupByName in the stdlib: FindStringSubmatch returns a flat slice and SubexpNames() is the only name-to-index map — its [0] entry is the whole pattern and unnamed groups read as "", which is why the switch falls through for them.
RsRust
use regex::Regex;
fn main() {
let date = Regex::new(r"(?<year>\d{4})-(?<month>\d{2})").unwrap();
// the (?P<year>...) spelling compiles too — the crate accepts both
if let Some(caps) = date.captures("2026-08-26") {
let year = caps.name("year").map(|m| m.as_str()); // Option<&str>
let month = caps.name("month").map(|m| m.as_str()); // Some("08")
println!("{year:?}-{month:?}"); // Some("2026")-Some("08")
}
}The regex crate documents both spellings — (?P<name>...) and (?<name>...). name() returns Option<Match>, so a group that did not participate is None with no empty-string ambiguity; unwrap() on Regex::new is idiomatic for a static pattern (a bad pattern should die in a test, not at match time).
PHPPHP
$date = '2026-08-26';
if (preg_match('/(?<year>\d{4})-(?<month>\d{2})/', $date, $m) === 1) {
$year = $m['year']; // and $m[1] — the same capture under both keys
$month = $m['month'];
}
// trap: a group that did not participate is OMITTED from $matches:
preg_match('/(?<tz>Z|[+-]\d{2}:\d{2})?/', $date, $t);
$tz = $t['tz'] ?? null; // null — the key is absent, not ''PCRE spells names (?<name>...), (?P<name>...) or (?'name'...) — all three compile. A participating group appears TWICE in $matches ($m[1] and $m['year'] alias one slot, so count($m) double-counts), while a group that did not participate is omitted entirely on current PHP (verified on 8.5: reading it warns 'Undefined array key') — pull it with ?? or array_key_exists.
PyPython
import re
date_re = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})')
m = date_re.search('shipped on 2026-08-26')
if m:
year = m.group('year') # '2026'
full = m.groupdict() # {'year': '2026', 'month': '08'}
# a group that skipped the match is None, not '':
t = re.search(r'(?P<tz>Z|[+-]\d{2}:\d{2})?', '2026-08-26')
t.group('tz') # None
t.groupdict() # {'tz': None}The P is NOT optional: re rejects the .NET-style (?<name>...) with re.PatternError even on current CPython (verified on 3.14), and (?'name'...) too. A group that did not participate is None in both group('name') and groupdict() — never ''.
C#C#
using System.Text.RegularExpressions;
var m = Regex.Match("2026-08-26", @"(?<year>\d{4})-(?<month>\d{2})");
if (m.Success)
{
string year = m.Groups["year"].Value; // Groups[1] is the same slot
var tz = Regex.Match("2026-08-26", @"(?<tz>Z|[+-]\d{2}:\d{2})?");
if (!tz.Groups["tz"].Success) { /* the group skipped */ }
}Groups["name"] is never null — Group.Success is the only way to tell a skipped group from one that matched the empty string, because .Value reads "" in both cases. .NET is also the engine most relaxed about duplicate names in alternations; the branch-reset (?|...) construct is PCRE/Perl.
JvJava
import java.util.regex.*;
Pattern date = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})");
Matcher m = date.matcher("2026-08-26");
if (m.find()) {
String year = m.group("year"); // Java 7+
String month = m.group("month"); // m.group(1) is the same slot
}Named groups are Java 7 — older targets reject the pattern at compile. The name must start with a Latin letter: Pattern.compile("(?<2026>a)") dies with 'capturing group name does not start with a Latin letter' (verified), so numbered names need a prefix. A group that did not participate returns null from group("name").
SwSwift
import Foundation
let s = "2026-08-26"
let re = try NSRegularExpression(pattern: #"(?<year>\d{4})-(?<month>\d{2})"#)
if let m = re.firstMatch(in: s, range: NSRange(s.startIndex..., in: s)) {
let year = Range(m.range(withName: "year"), in: s).map { String(s[$0]) }
// Optional("2026") — nil when the name missed or the range is NSNotFound
}The Foundation API predates names being first-class: range(withName:) is a post-match NSRange lookup, not a typed capture, and a skipped group surfaces as a nil Range rather than a value. The modern road is the Regex/RegexBuilder API (Swift 5.7+), which types captures into the match's output tuple.
KtKotlin
val date = Regex("""(?<year>\d{4})-(?<month>\d{2})""") // raw string: no \\d
fun main() {
val m = date.find("2026-08-26")
val year = m?.groups?.get("year")?.value // by name — null when it skipped
val (y, mo) = m!!.destructured // positional: component1(), component2()
println("$year $y-$mo") // 2026 2026-08
}Triple-quoted strings kill the double-backslash noise of the Java spelling. Destructuring is POSITIONAL (component1()..componentN()) — it ignores names entirely; the by-name read is groups.get("year"), which returns null when the group did not participate.
RbRuby
date = /(?<year>\d{4})-(?<month>\d{2})/
if (m = date.match('2026-08-26'))
m[:year] # '2026' — Symbol, String, or index: same slot
m['month'] # '08'
m.named_captures # {'year' => '2026', 'month' => '08'}
end
tz = /(?<tz>Z|[+-]\d{2}:\d{2})?/.match('2026-08-26')
tz[:tz] # nil — non-participation, not ''MatchData#[] takes a Symbol, a String, or an index — m[:year], m['year'], and m[1] all alias one capture. named_captures (Ruby 2.4+) hands back the whole contract as a hash, with nil for groups that did not participate.
Keep going
Try the interactive regex-tester tool →Read the regex-tokens cheatsheet →