首页 web前端 js教程 日期正则表达式详解

日期正则表达式详解

Sep 21, 2017 am 11:25 AM
正则 表达式 详解

日期正则一般是对格式有要求,且数据不是直接由用户输入时使用。这篇文章主要介绍了日期正则表达式的思路详解,需要的朋友可以参考下

1        概述

首先需要说明的一点,无论是Winform,还是Webform,都有很成熟的日历控件,无论从易用性还是可扩展性上看,日期的选择和校验还是用日历控件来实现比较好。

前几天在CSDN多个版块看到需要日期正则的帖子,所以整理了这篇文章,和大家一起讨论交流,如有遗漏或错误的地方,还请大家指正。

日期正则一般是对格式有要求,且数据不是直接由用户输入时使用。因应用场景的不同,写出的正则也不同,复杂程度也自然不同。正则的书写需要根据具体情况具体分析,一个基本原则就是:只写合适的,不写复杂的。

对于日期提取,只要能与非日期区分开,写最简单的正则即可,如


\d{4}-\d{2}-\d{2}
登录后复制

如果可以在源字符串中唯一定位yyyy-MM-dd格式的日期,则可用做提取。

对于验证,如果仅仅是验证字符组成及格式是没有多大意义的,还要加入对规则的校验。由于闰年的存在,使得日期的校验正则变得比较复杂。

先来考察一下日期的有效范围以及什么是闰年。

2 日期的规则

2.1 日期的有效范围

对于日期的有效范围,不同的应用场景会有所不同。

MSDN中定义的DateTime对象的有效范围是:0001-01-01 00:00:00到9999-12-31 23:59:59。

UNIX时间戳的0按照ISO 8601规范为 :1970-01-01T00:00:00Z。

而实际应用中,日期的范围基本上不会超出DateTime所规定的范围,所以正则验证取其中常用的日期范围即可。

2.2 什么是闰年

(以下摘自百度百科)

闰年(leap year)是为了弥补因人为历法规定造成的年度天数与地球实际公转周期的时间差而设立的。补上时间差的年份为闰年。

地球绕日运行周期为365天5小时48分46秒(合365.24219天),即一回归年(tropical year)。公历的平年只有365日,比回归年短约0.2422 日,每四年累积约一天,把这一天加于2月末(即2月29日),使当年时间长度变为366日,这一年就为闰年。

需要注意的是,现在的公历是根据罗马人的“儒略历”改编而得。由于当时没有了解到每年要多算出0.0078天的问题,从公元前46年,到16世纪,一共累计多出了10天。为此,当时的教皇格雷果里十三世,将1582年10月5日人为规定为10月15日。并开始了新闰年规定。即规定公历年份是整百数的,必须是400的倍数才是闰年,不是400的倍数的就是平年。比如,1700年、1800年和1900年为平年,2000年为闰年。此后,平均每年长度为365.2425天,约4年出现1天的偏差。按照每四年一个闰年计算,平均每年就要多算出0.0078天,经过四百年就会多出大约3天来,因此,每四百年中要减少三个闰年。闰年的计算,归结起来就是通常说的:四年一闰;百年不闰,四百年再闰。

2.3 日期的格式

根据不同的语言文化,日期的连字符会有所不同,通常有以下几种格式:

yyyyMMdd

yyyy-MM-dd

yyyy/MM/dd

yyyy.MM.dd

3 日期正则表达式构建

3.1 规则分析

写复杂正则的一个常用方法,就是先把不相关的需求拆分开,分别写出对应的正则,然后组合,检查一下相互的关联关系以及影响,基本上就可以得出对应的正则。

按闰年的定义可知,日期可以有几种分类方法。

3.1.1 根据天数是否与年份有关划分为两类

与年份无关的一类中,根据每月天数的不同,又可细分为两类

  • 1、3、5、7、8、10、12月为1-31日

  • 4、6、9、11月为1-30日

与年份有关的一类中

  • 平年2月为1-28日

  • 闰年2月为1-29日

  • 所有年份的所有月份都包含1-28日

  • 所有年份除2月外都包含29和30日

  • 所有年份1、3、5、7、8、10、12月都包含31日

  • 闰年2月包含29日

3.1.2 根据包含日期不同可划分为四类

3.1.3 分类方法选择

因为日期分类之后的实现,是要通过(exp1|exp2|exp3)这种分支结构来实现的,而分支结构是从左侧分支依次向右开始尝试匹配,当有一个分支匹配成功时,就不再向右尝试,否则尝试所有分支后并报告失败。

分支的多少,每个分支的复杂程度都会影响匹配效率,考虑到被验证日期概率分布,绝大多数都是落到1-28日内,所以采用第二种分类方法,会有效提高匹配效率。

3.2 正则实现

采用3.1.2节的分类方法,就可以针对每一个规则写出对应的正则,以下暂按MM-dd格式进行实现。

先考虑与年份无关的前三条规则,年份可统一写作


(?!0000)[0-9]{4}
登录后复制

下面仅考虑月和日的正则

包括平年在内的所有年份的月份都包含1-28日


(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])
登录后复制

包括平年在内的所有年份除2月外都包含29和30日


(0[13-9]|1[0-2])-(29|30)
登录后复制

包括平年在内的所有年份1、3、5、7、8、10、12月都包含31日


(0[13578]|1[02])-31)
登录后复制

合起来就是除闰年的2月29日外的其它所有日期


(?!0000)[0-9]{4}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)
登录后复制

接下来考虑闰年的实现

闰年2月包含29日

这里的月和日是固定的,就是02-29,只有年是变化的。

可通过以下代码输出所有的闰年年份,考察规则


for (int i = 1; i < 10000; i++){
  if ((i % 4 == 0 && i % 100 != 0) || i % 400 == 0){
   richTextBox2.Text += string.Format("{0:0000}", i) + "\n";
  }
}
登录后复制

根据闰年的规则,很容易整理出规则,四年一闰;


([0-9]{2}(0[48]|[2468][048]|[13579][26])
登录后复制

百年不闰,四百年再闰。


(0[48]|[2468][048]|[13579][26])00
登录后复制

合起来就是所有闰年的2月29日


([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)
登录后复制

四条规则都已实现,且互相间没有影响,合起来就是所有符合DateTime范围的日期的正则


^((?!0000)[0-9]{4}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)|([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)$
登录后复制

考虑到这个正则表达式仅仅是用作验证,所以捕获组没有意义,只会占用资源,影响匹配效率,所以可以使用非捕获组来进行优化。


^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$
登录后复制

以上正则年份0001-9999,格式yyyy-MM-dd。可以通过以下代码验证正则的有效性和性能


DateTime dt = new DateTime(1, 1, 1);
DateTime endDay = new DateTime(9999, 12, 31);
Stopwatch sw = new Stopwatch();
sw.Start();
 
Regex dateRegex = new Regex(@"^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$");
 
//Regex dateRegex = new Regex(@"^((?!0000)[0-9]{4}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)|([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)$");
Console.WriteLine("开始日期: " + dt.ToString("yyyy-MM-dd"));
while (dt < endDay){
  if (!dateRegex.IsMatch(dt.ToString("yyyy-MM-dd"))){
   Console.WriteLine(dt.ToString("yyyy-MM-dd") + " false");
  }
  dt = dt.AddDays(1);
}
if (!dateRegex.IsMatch(dt.ToString("yyyy-MM-dd"))){
  Console.WriteLine(dt.ToString("yyyy-MM-dd") + " false");
}
Console.WriteLine("结束日期: " + dt.ToString("yyyy-MM-dd"));
sw.Stop();
Console.WriteLine("测试用时: " + sw.ElapsedMilliseconds + "ms");
Console.WriteLine("测试完成!");
Console.ReadLine();
登录后复制

4 日期正则表达式扩展

4.1 “年月日”形式扩展

  以上实现的是yyyy-MM-dd格式的日期验证,考虑到连字符的不同,以及月和日可能为M和d,即yyyy-M-d的格式,可以对以上正则进行扩展


^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])([-/.]?)(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])([-/.]?)(?:29|30)|(?:0?[13578]|1[02])([-/.]?)31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2([-/.]?)29)$
登录后复制

  使用反向引用进行简化,年份0001-9999,格式yyyy-MM-dd或yyyy-M-d,连字符可以没有或是“-”、“/”、“.”之一。


^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))$
登录后复制

  这就是“年月日”这种形式最全的一个正则了,不同含义部分以不同颜色标识,可以根据自己的需要进行栽剪。

4.2 其它形式扩展

  了解了以上正则各部分代表的含义,互相间的关系后,就很容易扩展成其它格式的日期正则,如dd/MM/yyyy这种“日月年”格式的日期。


^(?:(?:(?:0?[1-9]|1[0-9]|2[0-8])([-/.]?)(?:0?[1-9]|1[0-2])|(?:29|30)([-/.]?)(?:0?[13-9]|1[0-2])|31([-/.]?)(?:0?[13578]|1[02]))([-/.]?)(?!0000)[0-9]{4}|29([-/.]?)0?2([-/.]?)(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00))$
登录后复制

  这种格式需要注意的就是不能用反向引用来进行优了。连字符等可根据自己的需求栽剪。

4.3 添加时间的扩展

  时间的规格很明确,也很简单,基本上就HH:mm:ss和H:m:s两种形式。


([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]
登录后复制

  合入到日期的正则中,yyyy-MM-dd HH:mm:ss


^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)\s+([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$
登录后复制

4.4 年份定制

  以上所有涉及到平年的年份里,使用的是0001-9999。当然,年份也可以根据闰年规则定制。

  如年份1600-9999,格式yyyy-MM-dd或yyyy-M-d,连字符可以没有或是“-”、“/”、“.”之一。


^(?:(?:1[6-9]|[2-9][0-9])[0-9]{2}([-/.]?)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:(?:1[6-9]|[2-9][0-9])(?:0[48]|[2468][048]|[13579][26])|(?:16|[2468][048]|[3579][26])00)([-/.]?)0?2\2(?:29))$
登录后复制

5 特别说明

  以上正则采用的是最基本的正则语法规则,绝大多数采用传统NFA引擎的语言都可以支持,包括JavaScript、Java、.NET等。

  另外需求说明的是,虽然日期的规则相对明确,可以采用这种方式裁剪来得到符合要求的日期正则,但是并不推荐这样使用正则,正则的强大在于它的灵活性,可以根据需求,量身打造最合适的正则,如果只是用来套用模板,那正则也就不称其为正则了。

  正则的语法规则并不多,而且很容易入门,掌握语法规则,量体裁衣,才是正则之“道”。

6 应用

一、首先看需求

  日期的输入:

  手动输入,可输入两种格式yyyymmdd或yyyy-mm-dd

二、解决思路

用户手动输入日期,需要验证输入的日期格式

用户可能的输入情况可以分为以下几种:

(1).输入为空或者为空格

(2).输入非日期格式

  根据保存到数据库中的日期格式,保存的格式为yyyy-mm-dd,所以用户在输入yyyymmdd后需要进行转换,转换成yyyy-mm-dd。

  思路:

  验证日期格式,首现想到的是VS的验证控件,但是因为需要验证的控件有几十个,使用验证控件就需要一个个的拉控件,如果后期需要修改也很麻烦,而通过JS实现控制,再通过正则表达式对日期进行验证。

三、JS实现


//验证日期
function date(id) {
  var idvalue = document.getElementById(id).value;  //通过查找元素
  var tmpStr = "";
  var strReturn = "";
  //调用trim()去掉空格,因为js不支持trim()
  var iIdNo = trim(idvalue);
  //正则表达式,判断日期格式,包括日期的界限,日期的格式,平年和闰年
  var v = idvalue.match(/^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$/);
  //输入为空时跳过检测
  if (iIdNo.length == 0) {
    return false;
  }
  //自动更改日期格式为yyyy-mm-dd
  if (iIdNo.length == 8) {
    tmpStr = iIdNo.substring(0, 8);
    tmpStr = tmpStr.substring(0, 4) + "-" + tmpStr.substring(4, 6) + "-" + tmpStr.substring(6, 8)
    document.getElementById(id).value = tmpStr;
    document.getElementById(id).focus();
  }
  //验证,判断日期格式
  if ((iIdNo.length != 8) && !v) {
    strReturn = "日期格式错误,提示:19990101或1999-01-01";
    alert(strReturn);
    document.getElementById(id).select();
    return false;
  }
}
//运用正则表达式去除字符串两端空格(因为js不支持trim()) 
function trim(str) {
  return str.replace(/(^\s*)|(\s*$)/g, "");
}
//前台调用(获得焦点触发)
<input class="txtenterschooldate" size="14" type="text" id="txtenterschooldate" name="txtenterschooldate" onblur="date(&#39;txtenterschooldate&#39;)"/>
登录后复制

以上是日期正则表达式详解的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

C++中的众数函数详解 C++中的众数函数详解 Nov 18, 2023 pm 03:08 PM

C++中的众数函数详解在统计学中,众数指的是一组数据中出现次数最多的数值。在C++语言中,我们可以通过编写一个众数函数来找到任意一组数据中的众数。众数函数的实现可以采用多种不同的方法,下面将详细介绍其中两种常用的方法。第一种方法是使用哈希表来统计每个数字出现的次数。首先,我们需要定义一个哈希表,将每个数字作为键,出现次数作为值。然后,对于给定的数据集,我们遍

Win11管理员权限获取详解 Win11管理员权限获取详解 Mar 08, 2024 pm 03:06 PM

Windows操作系统是全球最流行的操作系统之一,其新版本Win11备受瞩目。在Win11系统中,管理员权限的获取是一个重要的操作,管理员权限可以让用户对系统进行更多的操作和设置。本文将详细介绍在Win11系统中如何获取管理员权限,以及如何有效地管理权限。在Win11系统中,管理员权限分为本地管理员和域管理员两种。本地管理员是指具有对本地计算机的完全管理权限

Oracle SQL中的除法运算详解 Oracle SQL中的除法运算详解 Mar 10, 2024 am 09:51 AM

OracleSQL中的除法运算详解在OracleSQL中,除法运算是一种常见且重要的数学运算操作,用于计算两个数相除的结果。除法在数据库查询中经常用到,因此了解OracleSQL中的除法运算及其用法是数据库开发人员必备的技能之一。本文将详细讨论OracleSQL中除法运算的相关知识,并提供具体的代码示例供读者参考。一、OracleSQL中的除法运算

C++中的取余函数详解 C++中的取余函数详解 Nov 18, 2023 pm 02:41 PM

C++中的取余函数详解在C++中,取余运算符(%)用于计算两个数相除的余数。它是一种二元运算符,其操作数可以是任何整数类型(包括char、short、int、long等),也可以是浮点数类型(如float、double)。取余运算符返回的结果与被除数的符号相同。例如,对于整数的取余运算,我们可以使用以下代码来实现:inta=10;intb=3;

PHP模运算符的作用及用法详解 PHP模运算符的作用及用法详解 Mar 19, 2024 pm 04:33 PM

PHP中的模运算符(%)是用来获取两个数值相除的余数的。在本文中,我们将详细讨论模运算符的作用及用法,并提供具体的代码示例来帮助读者更好地理解。1.模运算符的作用在数学中,当我们将一个整数除以另一个整数时,会得到一个商和一个余数。例如,当我们将10除以3时,商为3,余数为1。模运算符就是用来获取这个余数的。2.模运算符的用法在PHP中,使用%符号来表示模

linux系统调用system()函数详解 linux系统调用system()函数详解 Feb 22, 2024 pm 08:21 PM

Linux系统调用system()函数详解系统调用是Linux操作系统中非常重要的一部分,它提供了一种与系统内核进行交互的方式。其中,system()函数是一个常用的系统调用函数之一。本文将详细介绍system()函数的使用方法,并提供相应的代码示例。系统调用的基本概念系统调用是用户程序与操作系统内核交互的一种方式。用户程序通过调用系统调用函数来请求操作系统

如何用 Golang 正则匹配多个单词或字符串? 如何用 Golang 正则匹配多个单词或字符串? May 31, 2024 am 10:32 AM

Golang正则表达式使用管道符|来匹配多个单词或字符串,将各个选项作为逻辑OR表达式分隔开来。例如:匹配"fox"或"dog":fox|dog匹配"quick"、"brown"或"lazy":(quick|brown|lazy)匹配"Go"、"Python"或"Java":Go|Python|Java匹配单词或4位邮政编码:([a-zA

Linux的curl命令详解 Linux的curl命令详解 Feb 21, 2024 pm 10:33 PM

Linux的curl命令详解摘要:curl是一种强大的命令行工具,用于与服务器进行数据通信。本文将介绍curl命令的基本用法,并提供实际的代码示例,帮助读者更好地理解和应用该命令。一、curl是什么?curl是一个命令行工具,用于发送和接收各种网络请求。它支持多种协议,如HTTP、FTP、TELNET等,并提供了丰富的功能,如文件上传、文件下载、数据传输、代

See all articles