日期驗證正則表達式

日期正則表達式

網(wǎng)摘 2009年07月20日 07:26:00  @過客的博客

概述

首先需要說明的一點,無論是Winform低斋,還是Webform修噪,都有很成熟的日歷控件躲雅,無論從易用性還是可擴展性上看喊巍,日期的選擇和校驗還是用日歷控件來實現(xiàn)比較好屠缭。

前幾天在CSDN多個版塊看到需要日期正則的帖子,所以整理了這篇文章崭参,和大家一起討論交流呵曹,如有遺漏或錯誤的地方,還請大家指正何暮。
日期正則一般是對格式有要求奄喂,且數(shù)據(jù)不是直接由用戶輸入時使用。因應(yīng)用場景的不同海洼,寫出的正則也不同跨新,復(fù)雜程度也自然不同。正則的書寫需要根據(jù)具體情況具體分析坏逢,一個基本原則就是:只寫合適的玻蝌,不寫復(fù)雜的。

對于日期提取词疼,只要能與非日期區(qū)分開俯树,寫最簡單的正則即可,如\d{4}-\d{2}-\d{2}
如果可以在源字符串中唯一定位yyyy-MM-dd格式的日期贰盗,則可用做提取许饿。

對于驗證,如果僅僅是驗證字符組成及格式是沒有多大意義的舵盈,還要加入對規(guī)則的校驗陋率。由于閏年的存在,使得日期的校驗正則變得比較復(fù)雜秽晚。

先來考察一下日期的有效范圍以及什么是閏年瓦糟。

日期的規(guī)則

  1. 日期的有效范圍
    對于日期的有效范圍,不同的應(yīng)用場景會有所不同赴蝇。
    MSDN 中定義的 DateTime 對象的有效范圍是:0001-01-01 00:00:00到9999-12-31 23:59:59菩浙。
    UNIX 時間戳的 0 按照 ISO 8601 規(guī)范為:1970-01-01T00:00:00Z。

    而實際應(yīng)用中句伶,日期的范圍基本上不會超出 DateTime 所規(guī)定的范圍劲蜻,所以正則驗證取其中常用的日期范圍即可。

  2. 什么是閏年(以下摘自百度百科)

    閏年(leap year)是為了彌補因人為歷法規(guī)定造成的年度天數(shù)與地球?qū)嶋H公轉(zhuǎn)周期的時間差而設(shè)立的考余。補上時間差的年份為閏年先嬉。

    地球繞日運行周期為 365 天 5 小時 48 分 46 秒(合 365.24219 天),即一回歸年(tropical year)楚堤。公歷的平年只有 365 日疫蔓,比回歸年短約 0.2422 日含懊,每四年累積約一天,把這一天加于 2 月末(即 2 月 29 日)衅胀,使當年時間長度變?yōu)?366 日岔乔,這一年就為閏年。

    需要注意的是拗小,現(xiàn)在的公歷是根據(jù)羅馬人的“儒略歷”改編而得重罪。由于當時沒有了解到每年要多算出 0.0078 天的問題,從公元前 46 年哀九,到 16 世紀剿配,一共累計多出了 10 天。為此阅束,當時的教皇格雷果里十三世呼胚,將 1582 年 10 月 5 日人為規(guī)定為 10 月 15 日。并開始了新閏年規(guī)定息裸。即規(guī)定公歷年份是整百數(shù)的蝇更,必須是 400 的倍數(shù)才是閏年,不是 400 的倍數(shù)的就是平年呼盆。比如年扩,1700 年、1800 年和 1900 年為平年访圃,2000 年為閏年厨幻。此后,平均每年長度為 365.2425 天腿时,約 4 年出現(xiàn) 1 天的偏差况脆。按照每四年一個閏年計算,平均每年就要多算出 0.0078 天批糟,經(jīng)過四百年就會多出大約 3 天來格了,因此,每四百年中要減少三個閏年徽鼎。閏年的計算盛末,歸結(jié)起來就是通常說的:四年一閏;百年不閏纬傲,四百年再閏满败。

  3. 日期的格式
    根據(jù)不同的語言文化,日期的連字符會有所不同叹括,通常有以下幾種格式:

     yyyyMMdd
     yyyy-MM-dd
     yyyy/MM/dd
     yyyy.MM.dd
    

日期正則表達式構(gòu)建

規(guī)則分析

寫復(fù)雜正則的一個常用方法,就是先把不相關(guān)的需求拆分開宵荒,分別寫出對應(yīng)的正則汁雷,然后組合净嘀,檢查一下相互的關(guān)聯(lián)關(guān)系以及影響,基本上就可以得出對應(yīng)的正則侠讯。

按閏年的定義可知挖藏,日期可以有幾種分類方法。

1. 根據(jù)天數(shù)是否與年份有關(guān)劃分為兩類:

    1. 與年份無關(guān)的一類中厢漩,根據(jù)每月天數(shù)的不同,又可細分為兩類

        - 1溜嗜、3宵膨、5、7炸宵、8辟躏、10、12 月為 1-31 日
        - 4土全、6捎琐、9、11 月為 1-30 日

    2. 與年份有關(guān)的一類中

        -  平年2月為1-28日
        -  閏年2月為1-29日

2. 根據(jù)包含日期不同可劃分為四類

    - 所有年份的所有月份都包含1-28日
    - 所有年份除2月外都包含29和30日
    - 所有年份1裹匙、3瑞凑、5、7概页、8籽御、10、12月都包含31日
    - 閏年2月包含29日

3. 分類方法選擇

    因為日期分類之后的實現(xiàn)绰沥,是要通過 `(exp1|exp2|exp3)` 這種分支結(jié)構(gòu)來實現(xiàn)的篱蝇,而分支結(jié)構(gòu)是從左側(cè)分支依次向右開始嘗試匹配,當有一個分支匹配成功時徽曲,就不再向右嘗試零截,否則嘗試所有分支后并報告失敗。

    分支的多少秃臣,每個分支的復(fù)雜程度都會影響匹配效率涧衙,考慮到被驗證日期概率分布,絕大多數(shù)都是落到1-28日內(nèi)奥此,所以采用第二種分類方法弧哎,會有效提高匹配效率。

正則實現(xiàn)

采用上述第 2 種分類方法稚虎,就可以針對每一個規(guī)則寫出對應(yīng)的正則撤嫩,以下暫按 `MM-dd` 格式進行實現(xiàn)。

先考慮與年份無關(guān)的前三條規(guī)則蠢终,年份可統(tǒng)一寫作

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

下面僅考慮月和日的正則

1. 包括平年在內(nèi)的所有年份的月份都包含1-28日
    
        (0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])

2. 包括平年在內(nèi)的所有年份除2月外都包含29和30日

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

3. 包括平年在內(nèi)的所有年份 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)

接下來考慮閏年的實現(xiàn)

1. 閏年2月包含29日

    這里的月和日是固定的瞄沙,就是02-29己沛,只有年是變化的【嗑常可通過以下代碼輸出所有的閏年年份申尼,考察規(guī)則

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

    根據(jù)閏年的規(guī)則,很容易整理出規(guī)則肮疗,四年一閏晶姊;

        ([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)

    四條規(guī)則都已實現(xiàn)们衙,且互相間沒有影響,合起來就是所有符合 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)$

    考慮到這個正則表達式僅僅是用作驗證碱呼,所以捕獲組沒有意義蒙挑,只會占用資源,影響匹配效率愚臀,所以可以使用非捕獲組來進行優(yōu)化忆蚀。

        ^(?:(?!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,格式y(tǒng)yyy-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("結(jié)束日期:   " + dt.ToString("yyyy-MM-dd"));
        sw.Stop();
        Console.WriteLine("測試用時:   " + sw.ElapsedMilliseconds + "ms");
        Console.WriteLine("測試完成!");
        Console.ReadLine();

日期正則表達式擴展

“年月日”形式擴展

以上實現(xiàn)的是 yyyy-MM-dd 格式的日期驗證舶斧,考慮到連字符的不同欣鳖,以及月和日可能為 Md,即 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-ddyyyy-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))$

這就是“年月日”這種形式最全的一個正則了蜕依,不同含義部分以不同顏色標識,可以根據(jù)自己的需要進行栽剪。

其它形式擴展

了解了以上正則各部分代表的含義笔横,互相間的關(guān)系后竞滓,就很容易擴展成其它格式的日期正則咐吼,如 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))$

這種格式需要注意的就是不能用反向引用來進行優(yōu)了。連字符等可根據(jù)自己的需求栽剪锯茄。

添加時間的擴展

時間的規(guī)格很明確厢塘,也很簡單,基本上就 HH:mm:ssH: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]$

年份定制

以上所有涉及到平年的年份里,使用的是0001-9999喂急。當然格嘁,年份也可以根據(jù)閏年規(guī)則定制。

如年份1600-9999廊移,格式y(tǒng)yyy-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))$

特別說明

以上正則采用的是最基本的正則語法規(guī)則,絕大多數(shù)采用傳統(tǒng)NFA引擎的語言都可以支持苗膝,包括JavaScript殃恒、Java、.NET等辱揭。

另外需求說明的是离唐,雖然日期的規(guī)則相對明確,可以采用這種方式裁剪來得到符合要求的日期正則问窃,但是并不推薦這樣使用正則亥鬓,正則的強大在于它的靈活性,可以根據(jù)需求泡躯,量身打造最合適的正則贮竟,如果只是用來套用模板,那正則也就不稱其為正則了较剃。

正則的語法規(guī)則并不多咕别,而且很容易入門,掌握語法規(guī)則写穴,量體裁衣惰拱,才是正則之“道”。

Python 正則表達式驗證傳統(tǒng)日期

網(wǎng)摘 2014年07月16日 13:52:47  @guaguastd的博客

正則表達式:

(?x)
(?:
    (?#dd/mm)(3[0-1]|[12][0-9]|0?[0-9])/(1[0-2]|0?[1-9])|
    (?#mm/dd)(1[0-2]|0?[1-9])/(3[0-1]|[12][0-9]|0?[0-9]))/
(?#yy or yyyy)(?:[0-9]{2})?[0-9]{2}

Python code:

def dateCheck(sDate):  
  
    import re  
  
    daysinmonth = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)  
    validdate = 0
    patt = "^(?P<month>[0-3]?[0-9])/(?P<day>[0-3]?[0-9])/(?P<year>[0-9]{4})$"
    match = re.search(patt, sDate)  
    if match:  
        month = int(match.group("month"))  
        day = int(match.group("day"))  
        year = int(match.group("year"))  
  
        if year < 50:  
            year += 2000  
        if year < 100:  
            year += 1900  
  
        if month == 2 and year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):  
            if day >= 1 and day <= 29:  
                validdate = 1  
        elif month >=1 and month <= 12:  
            if day >=1 and day <= daysinmonth[month-1]:  
                validdate = 1  
  
    if validdate == 0:  
        print 'date <%s> is invalid!' % sDate  
    else:  
        print 'date <%s> is valid!' % sDate  
  
def main():  
    while 1:  
        sDate = raw_input("Please input date (format is mm/dd/yyyy, exit to quit): ")  
        if sDate == 'exit':  
            break  
        else:  
            dateCheck(sDate)  
  
main()  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啊送,一起剝皮案震驚了整個濱河市偿短,隨后出現(xiàn)的幾起案子欣孤,更是在濱河造成了極大的恐慌,老刑警劉巖昔逗,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件降传,死亡現(xiàn)場離奇詭異,居然都是意外死亡勾怒,警方通過查閱死者的電腦和手機婆排,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笔链,“玉大人段只,你說我怎么就攤上這事〖ǎ” “怎么了赞枕?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坪创。 經(jīng)常有香客問我炕婶,道長,這世上最難降的妖魔是什么误堡? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任古话,我火速辦了婚禮,結(jié)果婚禮上锁施,老公的妹妹穿的比我還像新娘陪踩。我一直安慰自己,他們只是感情好悉抵,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布肩狂。 她就那樣靜靜地躺著,像睡著了一般姥饰。 火紅的嫁衣襯著肌膚如雪傻谁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天列粪,我揣著相機與錄音审磁,去河邊找鬼。 笑死岂座,一個胖子當著我的面吹牛态蒂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播费什,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼钾恢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瘩蚪,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤泉懦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后疹瘦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崩哩,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年拱礁,在試婚紗的時候發(fā)現(xiàn)自己被綠了琢锋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡呢灶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钉嘹,到底是詐尸還是另有隱情鸯乃,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布跋涣,位于F島的核電站缨睡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏陈辱。R本人自食惡果不足惜奖年,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沛贪。 院中可真熱鬧陋守,春花似錦、人聲如沸利赋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媚送。三九已至中燥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間塘偎,已是汗流浹背疗涉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吟秩,地道東北人咱扣。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像峰尝,于是被迫代替她去往敵國和親偏窝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容