Регулярное выражение для сопоставления допустимых дат
Я пытаюсь написать регулярное выражение, которое проверяет дату. Регулярное выражение должно соответствовать следующему
- М/Д/ГГГГ
- ММ/ДД/ГГГГ
- одноразрядные месяцы могут начинаться с нуля (например: 03/12/2008)
- однозначные дни могут начинаться с нуля (например: 3/02/2008)
- не может включать 30 февраля или 31 февраля (например: 2/31/2008)
пока у меня есть
^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)dd)|((1[012]|0[1-9])(3[01]|2d|1d|0[1-9])(19|20)dd)|((1[012]|0[1-9])[-/.](3[01]|2d|1d|0[1-9])[-/.](19|20)dd)$
эти матчи правильно, за исключением того, что он по-прежнему включает 2/30/2008 и 2/31/2008.
у кого-нибудь есть лучшие предложения?
Edit: нашел ответ на RegExLib
^((((0[13578])|([13578])|(1[02]))[/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[/](([1-9])|([0-2][0-9])|(30)))|((2|02)[/](([1-9])|([0-2][0-9]))))[/]d{4}$|^d{4}$
он соответствует всем действительным месяцам, которые следуют за форматом MM/DD/YYYY.
спасибо всем за помощь.
15 ответов:
это не подходящее использование регулярных выражений. Вам было бы лучше использовать
[0-9]{2}/[0-9]{2}/[0-9]{4}и затем проверка диапазонов на языке более высокого уровня.
вот Reg ex, который соответствует всем действительным датам, включая високосные годы. Принятые форматы ММ/ДД / гггг или ММ-ДД-гггг или мм. ДД.формат гггг
^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])(?:(?:1[6-9]|[2-9]\d)?\d{2})$вежливость Ашуга Ахамед
ремонтопригодная версия Perl 5.10
/ (?: (?<month> (?&mon_29)) [\/] (?<day>(?&day_29)) | (?<month> (?&mon_30)) [\/] (?<day>(?&day_30)) | (?<month> (?&mon_31)) [\/] (?<day>(?&day_31)) ) [\/] (?<year> [0-9]{4}) (?(DEFINE) (?<mon_29> 0?2 ) (?<mon_30> 0?[469] | (11) ) (?<mon_31> 0?[13578] | 1[02] ) (?<day_29> 0?[1-9] | [1-2]?[0-9] ) (?<day_30> 0?[1-9] | [1-2]?[0-9] | 30 ) (?<day_31> 0?[1-9] | [1-2]?[0-9] | 3[01] ) ) /xвы можете получить элементы по имени в этой версии.
say "Month=$+{month} Day=$+{day} Year=$+{year}";(никаких попыток ограничить значения за год не предпринималось. )
Я приземлился здесь, потому что название этого вопроса является широким, и я искал регулярное выражение, которое я мог бы использовать для сопоставления в определенном формате даты (например, OP). Но затем я обнаружил, что, поскольку многие ответы и комментарии были всесторонне выделены, есть много подводных камней, которые делают построение эффективного шаблона очень сложным при извлечении дат, которые смешиваются с некачественными или неструктурированными исходными данными.
в моем исследовании вопросов, я придумал система, позволяющая построить регулярное выражение путем объединения четырех более простых вложенных выражений, соответствующих разделителю, и допустимых диапазонов для полей год, месяц и день в требуемом порядке.
это-
разделители
[^\w\d\r\n:]это будет соответствовать всему, что не является символом слова, символом цифры, возвратом каретки, новой строкой или двоеточием. Двоеточие должно быть там, чтобы предотвратить совпадение по времени, которые выглядят так даты (см. мои тестовые данные)
вы можете оптимизировать эту часть шаблона для ускорения согласования, но это хорошая основа, которая определяет наиболее допустимые разделители.
Примечание однако; он будет соответствовать строке со смешанными разделителями, как это 2/12-73, которые на самом деле не может быть действительной датой.
Год
(\d{4}|\d{2})это соответствует группе из двух или 4 цифр, в большинстве случаев это приемлемо, но если вы имеете дело с данными из годы 0-999 или за 9999, вы должны решить, как справиться с этим, потому что в большинстве случаев 1, 3 или >4 года цифры-это фигня.
Месяц Значения
(0?[1-9]|1[0-2])соответствует любому числу от 1 до 12 С или без ведущего нуля - Примечание: 0 и 00 не соответствует.
Дата
(0?[1-9]|[12]\d|30|31)соответствует любому числу от 1 до 31 С или без ведущего нуля-Примечание: 0 и 00 не является соответствие.
это выражение соответствует дате, месяцу, году форматированные даты
(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})но он также будет соответствовать некоторым из года, месяца даты те. Он также должен быть записан с граничными операторами, чтобы гарантировать, что вся строка даты выбрана и предотвращает допустимые поддатки, извлекаемые из данных, которые не являются хорошо сформированными, т. е. без граничных тегов 20/12/194 соответствует 20/12/19 и 101/12/1974 соответствует 01/12/1974
сравнить результаты следующего выражения к приведенному выше с тестовыми данными в разделе нонсенс (ниже)
\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\bв этом регулярном выражении нет проверки, поэтому будет сопоставлена хорошо сформированная, но недействительная дата, такая как 31/02/2001. Это проблема качества данных, и, как говорили другие, ваше регулярное выражение не должно проверять данные.
потому что вы (как разработчик) не можете гарантировать качество исходных данных, которые вам нужны для выполнения и обработки дополнительной проверки в вашем код, если вы пытаетесь соответствовать и проверка данных в регулярном выражении становится очень грязным и становится трудно поддерживать без очень документация лаконичным.
мусор внутрь, мусор наружу.
сказав это, если у вас есть смешанные форматы, где значения дат различаются, и вы должны извлечь столько, сколько сможете; вы можете объединить несколько выражений вместе;
это (катастрофическое) выражение соответствует DMY и YMD даты
(\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](\d{4}|\d{2})\b)но вы не сможете сказать, если даты, как 6/9/1973 являются 6 сентября или 9 июня. Я изо всех сил пытаюсь придумать сценарий, в котором это не вызовет проблемы где - то внизу, это плохая практика, и вам не нужно иметь дело с этим так-найти владельца данных и ударить их молотком управления.
наконец, если вы хотите сопоставить строку YYYYMMDD без разделителей, вы можете взять некоторые из неопределенность и выражение выглядит так
\b(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|30|31)\bно обратите внимание, что он будет соответствовать хорошо сформированным, но недопустимым значениям, таким как 20010231 (31 февраля!):)
тестовых данных
экспериментируя с решениями в этом потоке, я получил набор тестовых данных, который включает в себя множество допустимых и недопустимых дат и некоторые сложные ситуации, когда вы можете или не хотите совпадать, т. е. время, которое может совпадать как даты и даты на нескольких русло.
Я надеюсь, что это кому-то пригодится.
Valid Dates in various formats Day, month, year 2/11/73 02/11/1973 2/1/73 02/01/73 31/1/1973 02/1/1973 31.1.2011 31-1-2001 29/2/1973 29/02/1976 03/06/2010 12/6/90 month, day, year 02/24/1975 06/19/66 03.31.1991 2.29.2003 02-29-55 03-13-55 03-13-1955 1274 1274 174 03/31/2001 01/21/2001 12/13/2001 Match both DMY and MDY 12/12/1978 6/6/78 06/6/1978 6/06/1978 using whitespace as a delimiter 13 11 2001 11 13 2001 11 13 01 13 11 01 1 1 01 1 1 2001 Year Month Day order 76/02/02 1976/02/29 1976/2/13 76/09/31 YYYYMMDD sortable format 19741213 19750101 Valid dates before Epoch 12/1/10 12/01/660 12/01/00 12/01/0000 Valid date after 2038 01/01/2039 01/01/39 Valid date beyond the year 9999 01/01/10000 Dates with leading or trailing characters 12/31/21/ 31/12/1921AD 31/12/1921.10:55 12/10/2016 8:26:00.39 wfuwdf12/11/74iuhwf fwefew13/11/1974 01/12/1974vdwdfwe 01/01/99werwer 12321301/01/99 Times that look like dates 12:13:56 13:12:01 1:12:01PM 1:12:01 AM Dates that runs across two lines 1/12/19 74 01/12/19 74/13/1946 31/12/20 08:13 Invalid, corrupted or nonsense dates 0/1/2001 1/0/2001 00/01/2100 01/0/2001 0101/2001 01/131/2001 31/31/2001 101/12/1974 56/56/56 00/00/0000 0/0/1999 12/01/0 12/10/-100 74/2/29 12/32/45 20/12/194 2/12-73
для управления сроком действия даты в следующем формате:
гггг / ММ / ДД или гггг-ММ-ДД
Я бы рекомендовал вам использовать следующее регулярное выражение:
(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))игр
2016-02-29 | 2012-04-30 | 2019/09/31
Не Соответствует
2016-02-30 | 2012-04-31 | 2019/09/35
вы можете настроить его, если вы хотите разрешить только '/' или '-' сепараторы. Это регулярное выражение строго контролирует срок действия даты и проверяет 28,30 и 31 дней месяцев, даже високосные годы с 29/02 месяца.
попробуйте, он работает очень хорошо и предотвратить ваш код от большого количества ошибок !
FYI: я сделал вариант для SQL datetime. Вы найдете его там (ищите мое имя):регулярное выражение для проверки метку
обратная связь приветствуются :)
похоже, что вы перенапрягаете регулярное выражение для этой цели. Я бы использовал регулярное выражение для сопоставления нескольких форматов дат, а затем использовал отдельную функцию для проверки значений полей дат, извлеченных таким образом.
на Perl Расширенная версия
внимание
/xмодификатор./^( ( ( # 31 day months (0[13578]) | ([13578]) | (1[02]) ) [\/] ( ([1-9]) | ([0-2][0-9]) | (3[01]) ) ) | ( ( # 30 day months (0[469]) | ([469]) | (11) ) [\/] ( ([1-9]) | ([0-2][0-9]) | (30) ) ) | ( # 29 day month (Feb) (2|02) [\/] ( ([1-9]) | ([0-2][0-9]) ) ) ) [\/] # year \d{4}$ | ^\d{4}$ # year only /xОригинал
^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$
Если вы не получили эти выше предложения работают, я использую это, так как он получает любую дату я запустил это выражение через 50 ссылок, и он получил все даты на каждой странице.
^20\d\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$
var dtRegex = new RegExp(/[1-9\-]{4}[0-9\-]{2}[0-9\-]{2}/); if(dtRegex.test(date) == true){ var evalDate = date.split('-'); if(evalDate[0] != '0000' && evalDate[1] != '00' && evalDate[2] != '00'){ return true; } }
это регулярное выражение проверяет даты между 01-01-2000 и 12-31-2099 с соответствующими разделителями.
^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])(19|20)\d\d$
Perl 6 version
rx{ ^ $<month> = (\d ** 1..2) { $<month> <= 12 or fail } '/' $<day> = (\d ** 1..2) { given( +$<month> ){ when 1|3|5|7|8|10|12 { $<day> <= 31 or fail } when 4|6|9|11 { $<day> <= 30 or fail } when 2 { $<day> <= 29 or fail } default { fail } } } '/' $<year> = (\d ** 4) $ }после того, как вы используете это, чтобы проверить входные значения доступны в
$/или$<month>,$<day>,$<year>. ( это просто синтаксис для доступа к значениям в$/)не было предпринято никаких попыток проверить год, или что он не соответствует 29 февраля на не високосные годы.
регулярное выражение не предназначалось для проверки диапазонов чисел (это число должно быть от 1 до 5, когда число, предшествующее ему, оказывается 2, а число, предшествующее, оказывается ниже 6). Просто посмотрите на шаблон размещения чисел в регулярном выражении. Если вам нужно проверить качества даты, поместите ее в объект даты js/c#/vb и interogate числа там.
Я знаю, что это не ответ на ваш вопрос, но почему бы вам не использовать процедуру обработки даты, чтобы проверить, если это действительная дата? Даже если вы измените регулярное выражение с отрицательным условное выражение как (?!31/0?2) (т. е. не совпадают 31/2 или 31/02) у вас все равно будет проблема принятия 29 02 на не високосных годах и о формате даты с одним разделителем.
проблема не проста если вы хотите действительно проверить дату, проверьте это ветке форума.
для пример или лучший способ в C#, проверить этой ссылке
Если вы используете другую платформу/язык, дайте нам знать
Если вы собираетесь настаивать на этом с регулярным выражением, я бы рекомендовал что-то вроде:
( (0?1|0?3| <...> |10|11|12) / (0?1| <...> |30|31) | 0?2 / (0?1| <...> |28|29) ) / (19|20)[0-9]{2}этой может сделать возможным читать и понимать.
немного другой подход, который может или может не быть полезным для вас.
Я в php.
проект, к которому это относится, никогда не будет иметь даты до 1 января 2008 года. Итак, я беру "дата" ввода и использования функции strtotime(). Если ответ >= 1199167200, то у меня есть дата, которая мне полезна. Если что-то, что не похоже на дату вводится -1 возвращается. Если введен null, он возвращает номер сегодняшней даты, поэтому вам нужна проверка на ненулевое значение вход первый.
работает для моей ситуации, возможно, ваш тоже?
Comments