Minguo.tw
Home / Validating ROC date input

Validating ROC Date Input: Regex and Edge Cases

A working starting pattern: ^([1-9][0-9]{2})[/.\-](0[1-9]|1[0-2])[/.\-](0[1-9]|[12][0-9]|3[01])$. It checks shape and range, but regex alone cannot verify a real calendar date — you still need to construct it and see if it succeeds.

What the regex actually checks

This pattern matches a three-digit ROC year, a two-digit month from 01–12, and a two-digit day from 01–31, separated by /, . or -:

^([1-9][0-9]{2})[/.\-](0[1-9]|1[0-2])[/.\-](0[1-9]|[12][0-9]|3[01])$
Matches115/07/29 · 099-01-01 · 100.12.31
Rejects15/07/29 (two-digit year) · 115/13/01 (month 13) · 115/02/30 (regex can't catch this — see below)

Test any specific value against the ROC year converter once it passes validation, as a sanity check before it goes further into a pipeline.

Advertisement

Why 115/02/30 passes the regex and shouldn't

The pattern above accepts day 30 for any month, because encoding "30 is invalid in February, and 31 is invalid in April, June, September and November, and 29 is only valid in February during a leap year" directly into a regular expression produces an unreadable, hard-to-maintain pattern for very little benefit. The practical answer is to let the regex handle shape and obvious range violations, then hand the three numbers to your language's date constructor and catch the error it raises for a genuinely invalid combination:

// Python
from datetime import date

def is_valid_roc_date(roc_year: int, month: int, day: int) -> bool:
    if roc_year <= 0:
        return False
    try:
        date(roc_year + 1911, month, day)
        return True
    except ValueError:
        return False

is_valid_roc_date(115, 2, 30)  # False -- February has no 30th
is_valid_roc_date(112, 2, 29)  # False -- 2023 (ROC 112) is not a leap year
is_valid_roc_date(113, 2, 29)  # True  -- 2024 (ROC 113) is a leap year

Every mainstream language does this the same way: build the date, catch the exception (or check the error return, in Go). Regex narrows the input to plausible shapes; the date constructor is the actual calendar authority.

The checks regex genuinely handles well

Format and obvious range violations are exactly what regex is good at, and worth keeping even though the final validity check happens elsewhere:

ROC year 0 and before-ROC dates

Reject an ROC year of 0 or negative outright; it is not a valid pre-1912 date, it's almost always a parsing bug or a field that was never an ROC year in the first place. Where you genuinely need to represent a pre-Republic date, use a separate "before ROC N" field or a plain Gregorian year rather than trying to force it through the same ROC-year-plus-1911 formula, which was never designed to run backward past year 1.

Detecting a Y1C-truncated field

A two-digit year field that should hold a three-digit ROC year is the classic sign of the Y1C bug: ROC 100 (2011) stored as 00, ROC 101 as 01, and so on. Regex can flag the shape (two digits where three are expected), but it cannot tell you whether 00 means ROC 100 or a genuinely malformed record — that requires checking neighbouring rows for a plausible sequence, or falling back to a manual review of the source document.

Match your validator to your actual input, not every possible format

It's tempting to write one maximally permissive pattern that accepts every separator and every width you can imagine. Resist it. A validator that's too permissive will accept malformed strings that happen to look date-shaped — a product code, a phone extension, an unrelated ID — and misclassify them as dates. Write the pattern for the formats you've actually confirmed your data source produces, and reject everything else with a clear error rather than trying to guess.

Frequently asked questions

What regex validates a ROC date like 115/07/29?

^([1-9][0-9]{2})[/.\-](0[1-9]|1[0-2])[/.\-](0[1-9]|[12][0-9]|3[01])$ checks a three-digit ROC year, a month 01–12 and a day 01–31 separated by /, . or -. It does not check the day against the actual month length or leap years — that needs a real date library, not regex.

Why can't regex alone fully validate a date?

Because whether day 30 or 31 is valid depends on the month, and whether 29 February is valid depends on whether the Gregorian year (ROC year + 1911) is a leap year. A regex can check format and range, but the calendar rule that ties day to month and year has to be checked by actually constructing the date and seeing if it succeeds.

How do I check a ROC year isn't zero or negative?

Reject any parsed ROC year that is 0 or less before conversion. There is no ROC year 0 — the calendar runs from before-ROC years straight to ROC year 1 (1912) — so a value of 0 almost always means the input was malformed or mis-parsed, not a legitimate date.

Should validation accept both 115/07/29 and 115.7.29?

Only if your input source genuinely produces both. Accepting more formats than you actually receive increases the chance of misreading an unrelated string as a date. Match the validator to your actual input formats and reject everything else explicitly, rather than writing a maximally permissive pattern.

How do I detect a Y1C-truncated two-digit year during validation?

If a field that should hold a three-digit ROC year contains only two digits, or contains 00 where a sequential ROC year 100 record is expected, flag it rather than silently accepting the low value. A two-digit field cannot be safely reconstructed from pattern alone — it needs a lookup against the surrounding record or a manual review.

Advertisement

Related