Регулярное выражение / високосные годы и многое другое
недавно я искал регулярное выражение для проверки даты на стороне клиента, и я не смог найти то, которое может удовлетворить следующим критериям:
- имеет диапазон от 1800 - теперь
- выполняет правильную проверку даты с високосными годами
- ММ/ДД / гггг форма
- Неверная Проверка Даты
(эти ограничения были вне мой объем и требование согласно клиенту, несмотря на мои усилия убедить их, что это не лучший маршрут)
текущий код:
$('input').keyup(function()
{
var regex = /^(?:(0[1-9]|1[012])[/.](0[1-9]|[12][0-9]|3[01])[/.](18|19|20)[0-9]{2})$/;
$(this).toggleClass('invalid',!regex.test($(this).val()));
});
обновление:
Я должен отметить, что это в первую очередь, чтобы увидеть, возможно ли регулярное выражение (как использовать regex не мой выбор в данном вопросе). Я знаю о другом (, а лучше) параметры проверки даты, однако, как уже упоминалось ранее-это посмотреть, возможно ли это через регулярное выражение.
11 ответов
как уже упоминалось в другом месте, регулярные выражения почти наверняка не то, что вы хотите. Но, сказав это, если вы действительно хотите регулярное выражение, Вот как оно построено:
31 день месяца
(0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2}
30 день месяца
(0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2}
1-28 февраля всегда действует
(02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2}
29 февраля также действует на високосные годы
(02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)
что означает, что это было бы так, если бы вы собрали все это вместе:
((0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})|((0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2})|((02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))
эта версия немного короче, но немного сложнее понять.
((0[13578]|1[02])[\/.]31[\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\/.](29|30)[\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))
эти сценарии длинные и недостижимые. Должно быть ясно, что это не очень хорошая идея, но это возможно.
предостережения:
- диапазон 1800-2099 (больше можно добавить без особых трудностей, но требует изменений в 4-6 разрозненных местах)
- требует 2-значных месяцев и дней (строгость может быть удалена из выражения в ~8 места)
-
[\/.]
как разделители (8 мест) - не был протестирован (мы могли бы проверить его на все комбинации цифр и сравнить с функцией даты javascript? [доказательство того, что мы изобретаем колесо])
Я бы предложил вам отказаться от попытки использовать регулярные выражения для этого. Вам гораздо лучше разобрать дату на ее составные части (месяц, день, год), а затем использовать числовые сравнения, чтобы убедиться, что она находится в правильном диапазоне.
еще лучше, посмотрите, если Javascript дата.разбор функция сделает то, что вы хотите.
разбор дат с регулярными выражениями возможен, но разочаровывает. Это трудно понять, выражение трудно понять мастерам без регулярных выражений (что означает, что трудно доказать, что вещь правильная), и это медленно по сравнению с другими вариантами.
вот как я бы это сделал:
function validate( input ) {
var date = new Date( input );
input = input.split( '/' );
return date.getMonth() + 1 === +input[0] &&
date.getDate() === +input[1] &&
date.getFullYear() === +input[2];
}
использование:
validate( '2/1/1983' ) // true
validate( '2/29/1983' ) // false
validate( '2/29/1984' ) // true (1984 is a leap year)
очевидно, что регулярные выражения не являются идеальным способом сделать это. Кроме того, гораздо безопаснее работать с YYYY-MM-DD
(ISO 8601) формат, не MM/DD/YYYY
.
тем не менее, вот самое короткое полностью рабочее регулярное выражение для дат от 01/01/1800 до 12/31/2099:
^(((0[1-9]|1[012])\/(?!00|29)([012]\d)|(0[13-9]|1[012])\/(29|30)|(0[13578]|1[02])\/31)\/(18|19|20)\d{2}|02\/29\/((18|19|20)(0[48]|[2468][048]|[13579][26])|2000))$
длина: 162 символов.
поломки:
^ # start
(
( # non-leap months & days
(0[1-9]|1[012])/(?!00|29)([012]\d) # all months, days 01-28, uses negative lookahead
|
(0[13-9]|1[012])/(29|30) # all months except feb, days 29,30
|
(0[13578]|1[02])/31 # all 31 day months, day 31 only
)
/
(18|19|20)\d{2} # all years
|
02/29 # leap day
/
(
(18|19|20)(0[48]|[2468][048]|[13579][26]) # leap years not divisible by 100
|
2000 # leap years divisible by 100
)
)
$ # end
вот скрипка это проверяет все варианты использования от 00/00/1800 до 99/99/2099.
кроме того, для большего удовольствия,вот еще одна скрипка это генерирует самое жалкое регулярное выражение, которое все еще работает, длиной 1205306 символов. Это выглядит примерно так:
^(01/01/1800|01/02/1800|01/03/1800|...|12/29/2099|12/30/2099|12/31/2099)$
это регулярное выражение для формата гггг-ММ-ДД
((18|19|20)[0-9]{2}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]))|(18|19|20)[0-9]{2}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30)|(18|19|20)[0-9]{2}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8])|(((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)[\-.](02)[\-.]29
используя moment (не regex) я сделал следующее:
предполагая, что у вас есть дата ISO в качестве строкового значения:
var isoDate = '2016-11-10';
var parsedIsoDate = moment(isoDate, ['YYYY-MM-DD'], true).format('YYYY-MM-DD');
if (parsedIsoDate !== isoDate) {
// Invalid date.
}
^(((?:(?:1[6-9]|[2-9]\d)?\d{2})(-)(?:(?:(?:0?[13578]|1[02])(-)31)|(?:(?:0?[1,3-9]|1[0-2])(-)(?:29|30))))|(((?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))(-)(?:0?2(-)29))|((?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(-)(?:(?:0?[1-9])|(?:1[0-2]))(-)(?:0[1-9]|1\d|2[0-8]))))$
пожалуйста, попробуйте приведенное выше выражение Reg. Я попробовал несколько комбинаций и обнаружил, что они работают.
пожалуйста, проверьте, работает ли это для вас тоже.
формат принят : гггг-ММ-ДД
год принят от 1600
это регулярное выражение, которое я использую для проверки даты на стороне клиента. Он имеет ряд от 1000 до 2999, проверяет високосные годы и выборочно часть времени. Разве это не великолепно:)
var r = /^(0[1-9]|1\d|2[0-8]|29(?=-\d\d-(?!1[01345789]00|2[1235679]00)\d\d(?:[02468][048]|[13579][26]))|30(?!-02)|31(?=-0[13578]|-1[02]))-(0[1-9]|1[0-2])-([12]\d{3})(\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d))?$/gm;
r.test('20-02-2013 10:01:07'); // true
r.test('29-02-1700'); // false
r.test('29-02-1604 14:01:45'); // true
r.test('29-02-1900 20:10:50'); // false
r.test('31-12-2000'); // true
r.test('31-11-2008 05:05:05'); // false
r.test('29-02-2004 05:01:23'); // true
r.test('24-06-2014 24:10:05'); // false
Я пытался проверить гггг-ММ-ДД, где гггг может быть двузначным, а мм и ДД могут быть одним. Вот что я придумал. Он рассматривает все века как високосные годы.
((\d\d)?\d\d-((0?(1|3|5|7|8)|10|12)-(31|30|[21]\d|0?[1-9])|(0?(4|6|9)|11)-(31|30|[21]\d|0?[1-9])|0?2-((2[0-8]|1\d)|0?[1-9]))|(\d\d)?((0|2|4|6|8)(0|4|8)|(1|3|5|7|9)(2|6))-0?2-29)
добавление моего ответа только для спорта-в противном случае я полностью согласен с @Jim.
это будет соответствовать високосным годам, в том числе с цифрами меньше или больше 4.
^\d*((((^|0|[2468])[048])|[13579][26])00$)|((0[48]|(^0*|[2468])[048]|[13579][26]))$
мини - тестовый случай в Ruby (^
заменить \A
и $
С \Z
, потому что Руби):
r = /\A\d*((((\A|0|[2468])[048])|[13579][26])00\Z)|((0[48]|(\A0*|[2468])[048]|[13579][26]))\Z/
100000.times do |year|
leap = year % 4 == 0 && ((year % 100 != 0) || (year % 400 == 0))
leap_regex = !year.to_s[r].nil?
if leap != leap_regex
print 'Assertion broken:', year, leap, leap_regex, "\n"
end
end
((0[13578]|1[02])[/.]31/.[0-9]{2})|((01/0[3-9]|1[1-2])/./.[0-9]{2})|((0[1-9]|1[0-2])/./.[0-9]{2})|((02)[/.]29/.)
ответ короткой версии не работает для 10/29 и 10/30 в любой год длинная версия работает ниже-это простая программа Java script, которую я написал, чтобы проверить
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class RegxDateTest {
public static void main(String[] args) {
// String to be scanned to find the pattern.
String line = "This order was placed for QT3000! OK?";
String pattern ="((0[13578]|1[02])[\/.]31[\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\/.](29|30)[\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))";
// Create a Pattern object
Pattern r = Pattern.compile(pattern);
LocalDate startDate = new LocalDate("1950-01-01");
LocalDate endDate = new LocalDate("2020-01-01");
for (LocalDate date = startDate; date.isBefore(endDate); date = date.plusDays(1))
{
if (date.toString("MM/dd/yyyy").matches(pattern)) {
// System.out.println("This date does match: " + date.toString("MM/dd/yyyy") );
}else{
System.out.println("This date does not match: " + date.toString("MM/dd/yyyy") );
}
}
String baddate1="02/29/2016";
if (baddate1.matches(pattern)) {
System.out.println("This date does match: " + baddate1 );
}else{
System.out.println("This date does not match: " + baddate1 );
}
System.out.println("alldone: " );
}
}