Регулярное выражение для соответствия действительным датам
Я пытаюсь написать регулярное выражение, которое проверяет дату. Регулярное выражение должно соответствовать следующему
- M / D / YYYY
- MM/DD/YYYY
- Однозначные месяцы могут начинаться с нуля (например, 03/12/2008)
- Дни, состоящие из одной цифры, могут начинаться с нуля (например: 3/02/2008)
- НЕ МОЖЕТ включать 30 февраля или 31 февраля (например: 31.02.2008)
Пока у меня есть
^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d)|((1[012]|0[1-9])(3[01]|2\d|1\d|0[1-9])(19|20)\d\d)|((1[012]|0[1-9])[-/.](3[01]|2\d|1\d|0[1-9])[-/.](19|20)\d\d)$
Это соответствует должным образом, за исключением того, что оно все еще включает 30/02/2008 и 31.02.2008.
У кого-нибудь есть лучшее предложение?
Изменить: я нашел ответ на 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.
Спасибо всем за помощь.
17 ответов
Это не правильное использование регулярных выражений. Вы бы лучше использовать
[0-9]{2}/[0-9]{2}/[0-9]{4}
а затем проверка диапазонов на языке более высокого уровня.
Вот Reg ex, который соответствует всем действительным датам, включая високосные годы. Форматы принимаются в формате мм / дд / гггг или мм-дд-гггг или мм.дд.гггг
^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?: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])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$
любезно предоставлено Asiq Ahamed
Я попал сюда, потому что название этого вопроса широкое, и я искал регулярное выражение, которое я мог бы использовать для сопоставления с определенным форматом даты (например, OP). Но затем я обнаружил, что, как всесторонне подчеркнуто во многих ответах и комментариях, существует много подводных камней, которые делают создание эффективного шаблона очень сложным при извлечении дат, смешанных с некачественными или неструктурированными исходными данными.
В своем исследовании проблем я придумал систему, которая позволяет вам создавать регулярные выражения, объединяя четыре более простых подвыражения, которые соответствуют разделителю, и допустимые диапазоны для полей года, месяца и дня в заказе. вам требуется.
Это:-
разделители
[^\w\d\r\n:]
Это будет соответствовать всему, что не является символом слова, символом цифры, переводом каретки, новой строкой или двоеточием. Двоеточие должно быть там, чтобы предотвратить сопоставление в моменты времени, похожие на даты (см. Мои данные теста)
Вы можете оптимизировать эту часть шаблона для ускорения сопоставления, но это хорошая основа, которая обнаруживает большинство допустимых разделителей.
Обратите внимание, однако; Он будет соответствовать строке со смешанными разделителями, подобными этим 2/12-73, которые могут на самом деле не быть действительной датой.
Год значения
(\d{4}|\d{2})
Это соответствует группе из двух или четырех цифр, в большинстве случаев это приемлемо, но если вы имеете дело с данными за 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. Это проблема качества данных, и, как говорили другие, вашему регулярному выражению не нужно проверять данные.
Поскольку вы (как разработчик) не можете гарантировать качество исходных данных, которые вам необходимо выполнить и выполнить дополнительную проверку в своем коде, если вы попытаетесь сопоставить и проверить данные в RegEx, это станет очень грязным и станет трудным для понимания. поддержка без очень лаконичной документации.
Мусор на входе, мусор на выходе.
Сказав это, если у вас есть смешанные форматы, где значения даты меняются, и вы должны извлечь как можно больше; Вы можете объединить пару выражений вместе, как это;
Это (катастрофическое) выражение соответствует датам 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
12\24\1974
12\30\1974
1\31\1974
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
Поддерживаемая версия 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}";
(Не было предпринято никаких попыток ограничить значения для года.)
Чтобы контролировать срок действия даты в следующем формате:
ГГГГ / ММ / ДД или ГГГГ-ММ-ДД
Я бы порекомендовал вам использовать следующее регулярное выражение:
(((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
Non-Спички
2016-02-30 | 2012-04-31 | 2019/09/35
Вы можете настроить его, если хотите разрешить только разделители '/' или '-'. Этот RegEx строго контролирует срок действия даты и проверяет 28,30 и 31 день месяца, даже високосного года с 29/02 месяцем.
Попробуйте, это работает очень хорошо и предотвратит ваш код от множества ошибок!
К вашему сведению: я сделал вариант для даты и времени SQL. Вы найдете его там (ищите мое имя): Регулярное выражение для проверки метки времени
Отзывы приветствуются:)
Похоже, для этой цели вы расширяете регулярное выражение. Я хотел бы использовать регулярное выражение для сопоставления нескольких форматов даты, а затем использовать отдельную функцию для проверки значений извлеченных полей даты.
Это регулярное выражение проверяет даты между 01-01-2000 и 12-31-2099 с соответствующими разделителями.
^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$
Если вы не сработали вышеупомянутые предложения, я использую это, поскольку он получает любую дату, когда я пропускал это выражение по 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])$
Расширенная версия 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}$
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;
}
}
Версия Perl 6
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 и соедините числа там.
Я знаю, что это не отвечает на ваш вопрос, но почему бы вам не использовать процедуру обработки даты, чтобы проверить, является ли она действительной датой? Даже если вы измените регулярное выражение с отрицательным прогнозным утверждением типа (?!31/0?2) (т. Е. Не совпадете с 31/2 или 31/02), у вас все равно будет проблема принятия 29 02 в не високосные годы. и о формате с одним разделителем даты.
Проблема не простая, если вы хотите действительно проверить дату, проверьте эту ветку форума.
Для примера или лучшего способа, в C#, проверьте эту ссылку
Если вы используете другую платформу / язык, сообщите нам
import re
line = "We wake upat 8:02"
match = re.findall(r'\d*\d:\d*\d:*\d*\d*', line)
match
/(([1-9]{1}|0[1-9]|1[0-2])\/(0[1-9]|[1-9]{1}|[12]\d|3[01])\/[12]\d{3})/
Это подтвердит следующее -
- Однозначный и двузначный день с диапазоном от 1 до 31. Например, 1, 01, 11, 31.
- Однозначный и двузначный месяц в диапазоне от 1 до 12. Напр. 1, 01, 12.
- 4-значный год. Например. 2021, 1980 год.
Если вы собираетесь настаивать на этом с помощью регулярного выражения, я бы порекомендовал что-то вроде:
( (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, оно возвращает номер сегодняшней даты, поэтому вам сначала нужно проверить ненулевую запись.
Работает для моей ситуации, возможно, и для вас?