Я попал сюда, потому что название этого вопроса широкое, и я искал регулярное выражение, которое я мог бы использовать для сопоставления с определенным форматом даты (например, OP). Но затем я обнаружил, что, как всесторонне подчеркнуто во многих ответах и комментариях, существует много подводных камней, которые делают создание эффективного шаблона очень сложным при извлечении дат, смешанных с некачественными или неструктурированными исходными данными.
В своем исследовании проблем я придумал систему, которая позволяет вам создавать регулярные выражения, объединяя четыре более простых подвыражения, которые соответствуют разделителю, и допустимые диапазоны для полей года, месяца и дня. в порядке, который вам требуется.
Это: -
* 1008 разделители *
[^\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 июня. Я изо всех сил пытаюсь придумать сценарий, в котором это не вызовет проблемы где-то в будущем, это плохая практика, и вам не нужно так с этим бороться - найдите владельца данных и бейте его молотком управления. .
Наконец, если вы хотите сопоставить строку ГГГГММДД без разделителей, вы можете устранить некоторую неопределенность, и выражение будет выглядеть так
\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