Критерії:
Кожен рік, що ділиться на 4, є високосним, за винятком випадків, коли він ділиться на 100, якщо не ділиться на 400. Отже:
2004 - leap year - divisible by 4
1900 - not a leap year - divisible by 4, but also divisible by 100
2000 - leap year - divisible by 4, also divisible by 100, but divisible by 400
Лютий має 29 днів у високосному році та 28, коли це не високосний рік
30 днів у квітні, червні, вересні та листопаді
31 день січня, березня, травня, липня, серпня, жовтня та грудня
Тест:
Наступні дати повинні пройти перевірку:
1976-02-29
2000-02-29
2004-02-29
1999-01-31
Наступні дати не повинні пройти перевірку:
2015-02-29
2015-04-31
1900-02-29
1999-01-32
2015-02-00
Діапазон:
Ми будемо тестувати дати з 1 січня 1000 р. По 31 грудня 2999 р. Технічно використовуваний зараз григоріанський календар увійшов у користування лише в 1753 р. Для Британської імперії та в різні роки в 1600-х рр. Для країн Європи, але я не збираюся турбуйся про це.
Regex для тестування на високосний рік:
Роки, що діляться на 400:
1200|1600|2000|2400|2800
can be shortened to:
(1[26]|2[048])00
if you wanted all years from 1AD to 9999 then this would do it:
(0[48]|[13579][26]|[2468][048])00
if you're happy with accepting 0000 as a valid year then it can be shortened:
([13579][26]|[02468][048])00
Роки, що діляться на 4:
[12]\d([02468][048]|[13579][26])
Роки, що діляться на 100:
[12]\d00
Не ділиться на 100:
[12]\d([1-9]\d|\d[1-9])
Роки, що діляться на 100, але не на 400:
((1[1345789])|(2[1235679]))00
Ділиться на 4, але не на 100:
[12]\d([2468][048]|[13579][26]|0[48])
Високосний рік:
divisible by 400 or (divisible by 4 and not divisible by 100)
((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48])
Не ділиться на 4:
[12]\d([02468][1235679]|[13579][01345789])
Не високосний рік:
Not divisible by 4 OR is divisible by 100 but not by 400
([12]\d([02468][1235679]|[13579][01345789]))|(((1[1345789])|(2[1235679]))00)
Дійсні місяць і день, крім лютого (MM-DD):
((01|03|05|07|08|10|12)-(0[1-9]|[12]\d|3[01]))|((04|06|09|11)-(0[1-9]|[12]\d|30))
shortened to:
((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30))
Лютий з 28 днями:
02-(0[1-9]|1\d|2[0-8])
Лютий з 29 днями:
02-(0[1-9]|[12]\d)
Дійсна дата:
(leap year followed by (valid month-day-excluding-february OR 29-day-february))
OR
(non leap year followed by (valid month-day-excluding-february OR 28-day-february))
((((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48]))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|[12]\d))))|((([12]\d([02468][1235679]|[13579][01345789]))|((1[1345789]|2[1235679])00))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|1\d|2[0-8]))))
Отже, у вас є регулярний вираз для дат між 1 січня 1000 та 31 грудня 2999 у форматі РРРР-ММ-ДД.
Я підозрюю, що це можна трохи скоротити, але я залишу це за кимось іншим.
Це буде відповідати всім дійсним датам. Якщо ви хочете, щоб він був дійсним лише тоді, коли він містить лише одну дату і нічого іншого, тоді оберніть його приблизно ^( )$
так:
^(((((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48]))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|[12]\d))))|((([12]\d([02468][1235679]|[13579][01345789]))|((1[1345789]|2[1235679])00))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|1\d|2[0-8])))))$
Якщо ви хочете вказати його як необов’язковий запис дати (тобто він може бути порожнім або дійсною датою), додайте ^$|
на початку, наприклад:
^$|^(((((1[26]|2[048])00)|[12]\d([2468][048]|[13579][26]|0[48]))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|[12]\d))))|((([12]\d([02468][1235679]|[13579][01345789]))|((1[1345789]|2[1235679])00))-((((0[13578]|1[02])-(0[1-9]|[12]\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\d|30)))|(02-(0[1-9]|1\d|2[0-8])))))$
date("Y-m-d", strtotime("2012-09-12"))=="2012-09-12";
або PHPcheckdate ( int $month , int $day , int $year )
.