DateTime 實值型別表示值範圍在公元(基督紀元)0001 年 1 月 1 日午夜 12:00:00 到公元 (C.E.) 9999 年 12 月 31 日晚上 11:59:59 之間的日期和時間。
我們進入正題。
首先需要驗證年份,顯然,年份範圍為 0001 - 9999,匹配YYYY的Regex為:
[0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3}
其中 [0-9] 也可以表示為 /d,但 /d 不如 [0-9] 直觀,因此下面我將一直採用 [0-9]
用Regex驗證日期的痛點有二:一是大小月份的天數不同,二是閏年的考慮。
對於第一個痛點,我們首先不考慮閏年,假設2月份都是28天,這樣,月份和日期可以分成三種情況:
下面僅考慮月和日的正則
1. 包括平年在內的所有年份的月份都包含1-28日
(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])
2. 包括平年在內的所有年份除2月外都包含29和30日
(0[13-9]|1[0-2])-(29|30)
3. 包括平年在內的所有年份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)
接著我們來解決第二個痛點:閏年的考慮。根據閏年的定義,我們可以將閏年分為兩類:
1、能被4整除但不能被100整除的年份。尋找後兩位的變化規律,可以很快得到下面的正則匹配:
([0-9]{2})(0[48]|[2468][048]|[13579][26])
2、能被400整除的年份。能被400整除的數肯定能被100整除,因此後兩位肯定是00,我們只要保證前兩位能被4整除即可,相應的Regex為:
(0[48]|[2468][048]|[3579][26])00
2.最強驗證日期的Regex,添加了閏年的驗證
這個日期Regex支援
YYYY-MM-DD
YYYY/MM/DD
YYYY_MM_DD
YYYY.MM.DD的形式
match : 2008-2-29 2008/02/29
not match : 2008-2-30 2007-2-29
1 : 四年一閏
([0-9]{2}(0[48]|[2468][048]|[13579][26])
2 : 百年不閏,四百年再閏
(0[48]|[2468][048]|[13579][26])00
3 : 合起來就是所有閏年的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)$
考慮到這個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)$
總合上面我們寫個完美的日期驗證函式
代碼如下 |
複製代碼 |
function CheckDate(strDate){ //var strDate = document.getElementById("date_hour").value; var reg=/^(/d{4})(/d{2})(/d{2})$/; if(!reg.test(strDate)){ alert("日期格式不正確!/n正確格式為:20040101"); return false; } //var ss=strDate.split("/"); //var year=ss[0]; //var month=ss[1]; //var date=ss[2]; var year=strDate.substring(0,4); var month=strDate.substring(4,6); var date=strDate.substring(6,8); //alert(year+month+date); if(!checkYear(year)){return false;} if(!checkMonth(month)){return false;} if(!checkDate(year,month,date)){return false;} return true; } function checkYear(year){ if(isNaN(parseInt(year))){ alert("年份輸入有誤,請重新輸入!"); return false; } else if(parseInt(year)<1950 || parseInt(year) >2050) { alert("年份應該在1950-2050之間!"); return false; } else return true; } function checkMonth(month){ if(isNaN(parseInt(month,10))){alert("月份輸入有誤,請重新輸入!"); return false;} else if(parseInt(month,10)<1 || parseInt(month,10) >12) { alert("月份應該在1-12之間!"); return false;} else return true; } function checkDate(year,month,date){ var daysOfMonth=CalDays(parseInt(year),parseInt(month)); if(isNaN(parseInt(date))){alert("日期輸入有誤,請重新輸入!"); return false;} else if(parseInt(date)<1||parseInt(date)>daysOfMonth){ alert("日期應該在1-"+daysOfMonth+"之間!"); return false;} else return true; } function CalDays(year,month){ var date= new Date(year,month,0); return date.getDate(); } function isLeapYear(year){ if((year %4==0 && year %100!=0) || (year %400==0)) return true; else return false; } |
總結
能被400整除的年份。能被400整除的數肯定能被100整除,因此後兩位肯定是00,我們只要保證前兩位能被4整除即可,相應的Regex為,
關於西曆閏年是這樣規定的:地球繞太陽公轉一周叫做一迴歸年,一迴歸年長365日5時48分 46秒。因此,西曆規定有平年和閏年,平年一年有365日,比迴歸年短0.2422日,四年共短0.9688日,故每四年增加一日,這一年有366日,就 是閏年。但四年增加一日比四個迴歸年又多0.0312日,400年後將多3.12日,故在400年中少設3個閏年,也就是在400年中只設97個閏年,這 樣西曆年的平均長度與迴歸年就相近似了。由此規定:年份是整百數的必須是400的倍數才是閏年,例如1900年、2100年就不是閏年。