跳到主内容
d.devtul.fun
EN
正则 · 日期

日期正则

一份能直接抄走的日期校验正则,外加一个硬事实:正则判断不了一个日期是否真的存在。

正则模式

^\d{4}-\d{2}-\d{2}$

匹配 ISO 8601 日历日期的外形 YYYY-MM-DD。它只查数字布局,拒绝不了 2 月 30 日,也拒绝不了不存在的月份。

在线测试这个正则

在下面直接改文本 —— 匹配在本机完成,不会上传任何内容。

其它写法

美式 MM/DD/YYYY
^(0?[1-9]|1[0-2])/(0?[1-9]|[12]\d|3[01])/\d{4}$

匹配美式斜杠日期。依然拒绝不了 2 月 30 日,它只是把每个字段限制在一个合理区间。

欧式 DD-MM-YYYY
^(0?[1-9]|[12]\d|3[01])-(0?[1-9]|1[0-2])-\d{4}$

匹配日居前的欧洲日期。对大量取值与美式存在歧义,这本身就够成为避开斜杠的理由。

能直接抄走的日期正则

最稳妥的日期形态是 ISO 8601 日历日期,它表面形式的正则短得可怜:

^\d{4}-\d{2}-\d{2}$

它要求四位数字、一个连字符、两位数字、一个连字符、两位数字。这就是它全部的结构承诺。它只字不提 2 月 30 日是否存在,因为正则不做算术。

这条正则到底匹配了什么

  • \d{4} —— 年,四位数字,无符号,无上限。0000 和 9999 都能过。
  • - —— 字面连字符分隔符,ISO 的选择。(斜杠不是 ISO,而且引歧义。)
  • \d{2} —— 月,两位数字。00 和 13 都能过,因为模式只数数字。
  • \d{2} —— 日,两位数字,对现实同样 blindness。

这个模式是格式的门卫,不是真相的门卫。

美式与欧式,以及它们怎么撞车

在 ISO 之外你会遇到两种本地习惯,而它们在"哪位是月"上意见相左:

  • 美式 MM/DD/YYYY: ^(0?[1-9]|1[0-2])/(0?[1-9]|[12]\d|3[01])/\d{4}$
  • 欧式 DD-MM-YYYY: ^(0?[1-9]|[12]\d|3[01])-(0?[1-9]|1[0-2])-\d{4}$

撞车就是那桩著名的 03/04/2026 事故。在美国它是 3 月 4 日,在世界大部分地区它是 4 月 3 日。如果你的表单默默假定其一、而用户假定其二,记录会整月录错,且要很久以后才被发现。光这个歧义,就够成为处处默认 ISO 的理由。

为什么正则抓不出真实日期

这是本页核心论点,毫无商量余地。正则作用于字符,不作用于公历。想想:

  • 2026-02-30 —— 二月从没有 30 天,可模式照收。02 和 30 各自都是合法的数字对。
  • 2026-13-01 —— 第 13 月不存在,但 13 不过是两位数字。
  • 闰年:2024-02-29 是真的,2025-02-29 不是。判定的前提是"模 4、模 100、模 400"的算术,没有正则能优雅表达,除非写成恐怖的巨长分支。

你可以把月收紧成 0[1-9]|1[0-2]、把日收紧成 0[1-9]|[12]\d|3[01],但这仍会欣然收下 4 月 31 日和每一个非闰年里的 2 月 29 日。形状被框住了,真相没有。

正则能推到多远

如果你铁了心不靠解析器也要挡掉最离谱的错,可以把每月天数上限按月份写死,再给二月开特例:

^(?:\d{4}-(?:0[13-9]|1[0-2])-(?:0[1-9]|[12]\d|30)|...)$

老实说代价是:模式膨胀成几十条分支,还得再额外用更多分支把闰年例外焊上去,最后既难维护又容易写错。在它身上耗的每个小时,都是本可以拿来调一次 strptime 的时间。

常见错误写法与修正

错误症状修正
\d{2}-\d{2}-\d{4}分不清月日,美式欧式打架显式钉死顺序,优先用 ISO YYYY-MM-DD
^....-..-..$接受字母和符号数字段用 \d 而不是点号
日用 [1-31]字符类写错,实际只匹配 1、2、3用 (0?[1-9]|[12]\d|3[01])
没锚点能匹配藏在别的文本里的日期保留 ^ 和 $

该用什么替代

把日期交给真正的解析器,它懂日历。Python 里:

from datetime import datetime

def parse_date(s):
    d = datetime.strptime(s, "%Y-%m-%d")
    # 防 2026-02-30 被规范化成 3 月 2 日
    if d.strftime("%Y-%m-%d") != s:
        raise ValueError("日期不存在")
    return d

JavaScript 里要先解析,再回读比较,因为 new Date('2026-02-30') 会悄悄滚到三月:

function parseDate(s) {
  const d = new Date(s + "T00:00:00");
  if (d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2,"0") + "-" + String(d.getDate()).padStart(2,"0") !== s)
    throw new Error("日期不存在");
  return d;
}

解析器能抓住正则抓不住的东西:2 月 30 日是虚构的。

生成与解析是两回事

如果你是在产出日期、而不是校验用户输入,别用拼接字符串再配一条正则。用语言自带的日期格式化器来生成,分隔符和补零永远正确,然后以无歧义的格式(UTC、ISO)存储,只在显示层做本地化。正则唯一诚实的角色,仍然是解析器动手之前、一道廉价的前置过滤,先把明显畸形的输入挡掉。

真正决定"日期是否合法"的,从来不是正则里那几个 \d,而是背后那套公历规则。把这套规则交给标准库,你的代码会更短、更对,也更经得起闰年二月的考验。当你纠结要不要为正则再多加一条分支时,先问自己:这活儿,解析器一行不就干完了么?与其和 2 月 29 日、4 月 31 日反复搏斗,不如把这套规则一次性交给标准库,让它替你记着哪个月有几天。

时区与完整 ISO 8601

日期常常还会长出时间。完整 ISO 8601 形如 2026-02-28T14:30:00Z 或 2026-02-28T14:30:00+05:00。给它的正则更长、在闰秒和混合时差上更不可信。还是那句话,解析它:每种现代语言都有 ISO 日期时间解析器(datetime.fromisoformat、小心使用的 new Date,或 Luxon 这类库)。用解析器校验、用解析器格式化,把正则放回它唯一擅长的一件事——在麻烦解析器之前,先粗略查一下外形。

常见问题

最好的日期正则是什么?

只查外形就用 ^\d{4}-\d{2}-\d{2}$ 的 ISO 形态。要正确就别用正则,用日期解析器。

正则能拒绝 2 月 30 日吗?

不能。正则只框住数字,做不了日历算术,也处理不了闰年。必须靠解析器。

为什么 MM/DD/YYYY 危险?

它和 DD/MM/YYYY 撞车:03/04/2026 在美国是 3 月 4 日,世界大部分地区却是 4 月 3 日。优先用 ISO YYYY-MM-DD。

怎么在 JavaScript 里校验日期?

用 new Date 解析后把年月日读回来和输入字符串比较,否则 2 月 30 日会悄悄变成 3 月 2 日。

其它正则速查

打开完整的正则测试器