PHP 프로그래머로서 우리는 필연적으로 날짜 정규식을 접하게 됩니다. 그렇다면 프로그래머로서 날짜 정규식에 대한 솔루션이 몇 개나 있습니까? 이번 글에서는 날짜 정규 표현식에 대해 자세히 알아 보겠습니다.
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입니다.
ISO 8601 사양에 따르면 UNIX 타임스탬프의 0은 1970-01-01T00:00:00Z입니다.
실제 애플리케이션에서는 날짜 범위가 기본적으로 DateTime에서 지정한 범위를 초과하지 않으므로 일반 검증에서는 일반적으로 사용되는 날짜 범위만 사용할 수 있습니다.
2.2 윤년이란 무엇인가
(다음은 바이두 백과사전에서 발췌)
윤년(윤년)은 인위적인 달력 규정으로 인한 연간 일수와 윤년의 시차를 메우기 위해 제정되었습니다. 실제 지구의 혁명 기간. 시차가 메워지는 해는 윤년이다.
지구의 태양 공전 궤도는 365일 5시간 48분 46초(365.24219일)이며, 이는 열대년입니다. 년도). 그레고리력의 평년은 365일밖에 되지 않으며, 이는 열대년보다 약 0.2422 정도 짧습니다. 4년마다 하루 정도씩 쌓이게 되는데, 2월 말(즉, 2월 29일)에 이 날을 더해 1년이 366일이 된다.
현재의 그레고리력은 로마인의 "율리우스력"에서 채택되었다는 점에 유의해야 합니다. 기원전 46년부터 16세기까지 매년 0.0078일을 추가로 계산해야 한다는 사실을 당시에는 이해하지 못했기 때문에 총 10일이 추가되었습니다. 이 때문에 당시 교황 그레고리오 13세는 1582년 10월 5일을 10월 15일로 인위적으로 지정했다. 그리고 새로운 윤년 규정을 시작했습니다. 즉, 그레고리력의 연도를 100으로 규정하고, 400의 배수이어야 윤년이고, 400의 배수가 아니면 평년으로 규정한다. 예를 들어 1700년, 1800년, 1900년은 평년이고 2000년은 윤년입니다. 이후 평균 연간 길이는 365.2425일이고 약 4년 동안 1일의 편차가 발생했다. 4년마다 윤년을 계산하면 매년 평균 0.0078일이 추가되며, 400년 후에는 약 3일이 추가됩니다. 따라서 400년마다 3번의 윤년이 줄어듭니다. 윤년의 계산은 평소와 같이 요약할 수 있습니다. 4년마다 윤년이 있고, 100년마다 윤년이 없으며, 400년마다 또 다른 윤년이 있습니다.
2.3 날짜 형식
다른 언어와 문화에 따라 날짜의 하이픈은 일반적으로 다음과 같은 형식이 있습니다:
yyyyMMdd
yyyy-MM-dd
yyyy/MM/dd
yyyy.MM.dd
3
날짜 정규식 구성
3.1 규칙 분석
복잡한 정규식을 작성하는 일반적인 방법은 먼저 관련 없는 요구사항을 분리하고 해당 정규식을 별도로 작성한 후 결합하여 상호 관계와 영향을 확인하는 것입니다. 기본적으로 해당 정규 규칙을 얻습니다.
윤년의 정의에 따르면 날짜는 여러 가지 방법으로 분류될 수 있습니다.
3.1.1 일수가 연도와 관련이 있는지에 따라 두 가지 범주로 나뉘며, 연도와 관련이 없는 범주는 더 나아가 각 달의 일수에 따라 두 가지로 나눌 수 있습니다. 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 날짜에 따라 4가지 카테고리로 나눌 수 있어요
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('txtenterschooldate')"/>
以上内容就是关于日期正则表达式的思路详解,如果大家觉得有用那就赶紧收藏起来吧。
相关推荐:
위 내용은 날짜 정규식 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!