Home >Web Front-end >JS Tutorial >Detailed explanation of date regular expressions

Detailed explanation of date regular expressions

巴扎黑
巴扎黑Original
2017-09-21 11:25:392005browse

Date regularization is generally used when there are format requirements and the data is not directly input by the user. This article mainly introduces the detailed explanation of date regular expressions. Friends who need it can refer to it

1 Overview

First of all It should be noted that both Winform and Webform have very mature calendar controls. From the perspective of ease of use and scalability, it is better to use calendar controls to implement date selection and verification.

A few days ago, I saw posts in multiple sections of CSDN that required date regularization, so I compiled this article to discuss and communicate with you. If there are any omissions or errors, please correct me.

Date regularization is generally used when there are format requirements and the data is not directly input by the user. Due to different application scenarios, the regular rules written are also different, and the complexity is naturally different. Regular writing requires detailed analysis based on specific situations. A basic principle is: only write appropriate ones, not complex ones.

For date extraction, as long as it can be distinguished from non-dates, just write the simplest regular expression, such as


\d{4}-\d{2}-\d{2}

If it can be in the source string The date that uniquely locates the yyyy-MM-dd format can be used for extraction.

For verification, it doesn’t make much sense if you just verify the character composition and format. You must also add verification of the rules. Due to the existence of leap years, date verification becomes more complicated.

Let’s first examine the valid range of dates and what leap years are.

2 Date rules

2.1 Date valid range

The valid range of dates will vary in different application scenarios.

The valid range of the DateTime object defined in MSDN is: 0001-01-01 00:00:00 to 9999-12-31 23:59:59.

The 0 of the UNIX timestamp is: 1970-01-01T00:00:00Z according to the ISO 8601 specification.

In actual applications, the date range will basically not exceed the range specified by DateTime, so regular verification can only use the commonly used date range.

2.2 What is a leap year

(The following is taken from Baidu Encyclopedia)

Leap year is to make up for the problems caused by artificial calendar regulations It is established based on the time difference between the annual number of days and the actual revolution period of the earth. The year in which the time difference is made up is a leap year.

The Earth’s orbit around the sun is 365 days, 5 hours, 48 ​​minutes and 46 seconds (365.24219 days), which is a tropical year. The ordinary year of the Gregorian calendar only has 365 days, which is about 0.2422 days shorter than the tropical year. It accumulates about one day every four years. This day is added to the end of February (i.e. February 29), so that the length of the year becomes 366 days. This year is It's a leap year.

It should be noted that the current Gregorian calendar is adapted from the "Julian Calendar" of the Romans. Because we did not understand at the time that an extra 0.0078 days had to be calculated every year, from 46 BC to the 16th century, there were a total of 10 extra days. For this reason, Pope Gregory XIII at the time artificially designated October 5, 1582 as October 15. And started the new leap year regulations. That is to say, it is stipulated that the Gregorian calendar year is a hundred, and it must be a multiple of 400 to be a leap year, and if it is not a multiple of 400, it is an ordinary year. For example, 1700, 1800 and 1900 are ordinary years, and 2000 is a leap year. Since then, the average annual length has been 365.2425 days, with a deviation of 1 day in about 4 years. According to the calculation of a leap year every four years, an average of 0.0078 days will be added every year. After four hundred years, there will be about three extra days. Therefore, three leap years will be reduced every four hundred years. The calculation of leap years can be summed up as usual: there is a leap every four years; there will be no leaps every hundred years, and there will be leaps every four hundred years.

2.3 Date format

According to different languages ​​and cultures, the hyphens of dates will be different. There are usually the following formats:

yyyyMMdd

yyyy-MM-dd

yyyy/MM/dd

yyyy.MM.dd

3 Date regular expression construction

##3.1 Rule analysis

A common way to write complex regular expressions is to first Split the relevant requirements, write the corresponding rules respectively, and then combine them to check the mutual correlation and influence. Basically, you can get the corresponding rules.

According to the definition of leap year, dates can be classified in several ways.

3.1.1 Divide into two categories according to whether the number of days is related to the year

In one category that is not related to the year, according to the number of days in each month Different, it can be divided into two categories

  • 1, 3, 5, 7, 8, 10 and December are 1-31 days

  • 4, 6, 9, and November are from 1 to 30.

In the category related to the year,

  • in ordinary years, February is Days 1-28

  • February in leap years is day 1-29

  • All months in all years include days 1-28

  • All years except February include the 29th and 30th

  • All years include 1, 3, 5, 7, 8, 10, and December Including the 31st

  • February in leap years includes the 29th

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

The above is the detailed content of Detailed explanation of date regular expressions. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn