1        概述

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

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

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

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

1

\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格式进行实现。

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

1

(?!0000)[0-9]{4}

下面仅考虑月和日的正则

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

1

(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])

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

1

(0[13-9]|1[0-2])-(29|30)

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

1

(0[13578]|1[02])-31)

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

1

(?!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,只有年是变化的。

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

1

2

3

4

5

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";

  }

}

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

1

([0-9]{2}(0[48]|[2468][048]|[13579][26])

百年不闰,四百年再闰。

1

(0[48]|[2468][048]|[13579][26])00

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

1

([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29)

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

1

^((?!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)$

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

1

^(?:(?!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。可以通过以下代码验证正则的有效性和性能

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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的格式,可以对以上正则进行扩展

1

^(?:(?!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,连字符可以没有或是“-”、“/”、“.”之一。

1

^(?:(?!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这种“日月年”格式的日期。

1

^(?:(?:(?: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两种形式。

1

([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]

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

1

^(?:(?!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

^(?:(?: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实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

//验证日期

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('txtenterschooldate')"/>

总结

以上所述是小编给大家介绍的关于日期正则表达式的思路详解,希望对大家有所帮助

关于日期正则表达式的思路详解相关推荐

  1. 验证手机号码格式的正则表达式编写思路详解

    Cooking Regex微信公众号 正则表达式学习 一.获取目前中国三大运营商的手机号码段 手机号码段 如上图我们可以根据不同运营商的号码段来编写正则,也可以进行统一校验的编写,我这边主要是编写统一 ...

  2. python按照号段生成手机号接收验证码,验证手机号码格式的正则表达式编写思路详解...

    一.获取目前中国三大运营商的手机号码段 如上图我们可以根据不同运营商的号码段来编写正则,也可以进行统一校验的编写,我这边主要是编写统一校验的. 二.思路 1.为了得到号码段的规律,我们可以把它们先进行 ...

  3. python正则表达式提取数字比较好_python正则表达式从字符串中提取数字的思路详解...

    python从字符串中提取数字 使用正则表达式,用法如下: ## 总结 ## ^ 匹配字符串的开始. ## $ 匹配字符串的结尾. ## \b 匹配一个单词的边界. ## \d 匹配任意数字. ## ...

  4. python处理excel大数据-Python实现大数据收集至excel的思路详解

    一.在工程目录中新建一个excel文件 二.使用python脚本程序将目标excel文件中的列头写入,本文省略该部分的code展示,可自行网上查询 三.以下code内容为:实现从接口获取到的数据值写入 ...

  5. 2022年亚太数学建模竞赛-问题C:全球变暖与否?-思路详解

    一.题目分析 数据分析典中典,数据量适中,主要考察预测模型,另外还需要收集额外的数据辅助建模和判断. 要求 1.你同意全球气温的说法吗?使用2022_APMMC_C_Data.csv以及您的团队收集的 ...

  6. 【蓝桥杯Python组】2022年第十三届蓝桥杯省赛B组Python解题思路详解

    第十三届蓝桥杯省赛B组Python解题思路详解 因为今年采用线上的举办方式进行比赛,所以组委会对题目做了一定的调整,将原来的5道填空+5道编程题变成了2道填空+8道编程题,据说是为了防止抄袭.其实题目 ...

  7. python实现括号匹配代码_python实现括号匹配的思路详解

    1.用一个栈[python中可以用List]就可以解决,时间和空间复杂度都是O(n) # -*- coding: utf8 -*- # 符号表 SYMBOLS = {'}': '{', ']': '[ ...

  8. 【直播】陈安东,但扬:CNN模型搭建、训练以及LSTM模型思路详解

    CNN模型搭建.训练以及LSTM模型思路详解 目前 Datawhale第24期组队学习 正在如火如荼的进行中.为了大家更好的学习"零基础入门语音识别(食物声音识别)"的课程设计者 ...

  9. vuepdf转换html,Vue网页html转换PDF(最低兼容ie10)的思路详解

    Vue网页html转换PDF(最低兼容ie10)的思路详解 发布时间:2020-10-16 13:05:09 来源:脚本之家 阅读:95 作者:冷藏封 HTML转PDF: 1.页面底层实现--Vue: ...

最新文章

  1. 细鹏系列裸金属服务器多核算力,鲲鹏凌云,开启多元计算新架构_外发版(40页)-原创力文档...
  2. linux发现很多pif和exe文件,u盘里 木马xftiaj.pif 是什么文件,肿么删除?
  3. 将构件发布到私有的nexus maven 仓库
  4. linux下top命令参数解释
  5. Vue 性能优化--打包优化
  6. 良好的JavaScript编码风格(语法规则)
  7. c语言 常量字符串数组,C语言常量以及字符串数组
  8. python双等号怎么输入_python中的星号‘*’和双星号‘**’的解说
  9. 戴尔游匣7559经常(大概半小时)断电,关机的解决办法
  10. 【Flink】Flink时间是如何设计的
  11. 企业架构 | TOGAF架构能力框架
  12. e531网卡驱动linux,联想e531网卡驱动下载-联想e531笔记本无线网卡驱动v6.30.223.201 官方版 - 极光下载站...
  13. dtft变换的性质_dtft(dtft和dft的关系区别)
  14. 系统架构设计-企业信息化战略与实施 知识点
  15. 10分钟学会数据地图制作,让你的可视化再高一级!
  16. 两高一部电子数据取证规则_“鼓浪听涛,论道取证”2019中国电子数据取证峰会在厦门召开,行业专家齐聚...
  17. Vue开发需要的网站
  18. 条形码转化成二维码_在线条形码生成器
  19. 难道我的博客也成了名人博客?
  20. GoLang之init函数

热门文章

  1. 利用map和reduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456:
  2. Office 2007 SP1 V12.0.6207.1000 简/繁/英文版 迅雷高速下载
  3. android 根目录 文件 打包,Android image文件的打包和解包
  4. 微波遥感2:微波与物质的相互作用
  5. AcrelEMS-IDC数据中心综合能效管理系统解决方案-Susie 周
  6. 联想win8改win7
  7. 痞子衡嵌入式:实测i.MXRT1010上的普通GPIO与高速GPIO极限翻转频率
  8. java抢购小米手机_小米手机怎样抢购
  9. JavaScript程序设计-简介
  10. SQL语句大全—DBA数据(六)