Регулярное выражение для соответствия действительным датам

StackOverflow https://stackoverflow.com/questions/51224

  •  09-06-2019
  •  | 
  •  

Вопрос

Я пытаюсь написать регулярное выражение, проверяющее дату.Регулярное выражение должно соответствовать следующему

  • М/Д/ГГГГ
  • ММ/ДД/ГГГГ
  • Месяцы, состоящие из одной цифры, могут начинаться с ведущего нуля (например:12.03.2008)
  • Дни, состоящие из одной цифры, могут начинаться с ведущего нуля (например:02.03.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 февраля 2008 г. и 31 февраля 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}$

Он соответствует всем допустимым месяцам в формате ММ/ДД/ГГГГ.

Спасибо всем за помощь.

Это было полезно?

Решение

Это некорректное использование регулярных выражений.Вам лучше использовать

[0-9]{2}/[0-9]{2}/[0-9]{4}

а затем проверка диапазонов на языке более высокого уровня.

Другие советы

Вот регулярный пример, который соответствует всем допустимым датам, включая високосные годы.Принимаемые форматы мм/дд/гггг или мм-дд-гггг или мм.дд.гггг

^(?:(?:(?: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})$

учтивость Асик Ахамед

Я попал сюда, потому что название этого вопроса широкое, и я искал регулярное выражение, которое можно было бы использовать для сопоставления с определенным форматом даты (например, 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 соответствует 12.01.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)

НО вы не сможете сказать, являются ли такие даты, как 9.06.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

Сопровождаемая версия 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

Несовпадения

2016-02-30 | 2012-04-31 | 2019/09/35

Вы можете настроить его, если хотите разрешить только разделители «/» или «-».Этот RegEx строго контролирует правильность даты и проверяет 28,30 и 31 день месяца, даже високосные годы с 29/02 месяца.

Попробуйте, это работает очень хорошо и предотвращает множество ошибок в вашем коде!

К вашему сведению:Я сделал вариант для даты и времени SQL.Вы найдете его там (ищите мое имя): Регулярное выражение для проверки временной метки

Обратная связь приветствуется :)

Похоже, вы слишком расширяете регулярное выражение для этой цели.Я бы использовал регулярное выражение для сопоставления нескольких форматов дат, а затем использовал бы отдельную функцию для проверки значений полей даты, извлеченных таким образом.

Расширенная версия 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 и 31.12.2099 с соответствующими разделителями.

^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$

Regex не предназначено для проверки диапазонов чисел (это число должно быть от 1 до 5, если предшествующее ему число равно 2, а предшествующее ему число меньше 6).Просто найдите закономерность размещения чисел в регулярном выражении.Если вам нужно проверить качество даты, поместите ее в объект даты js/c#/vb и запросите там числа.

Я знаю, что это не ответ на ваш вопрос, но почему бы вам не использовать процедуру обработки даты, чтобы проверить, является ли это допустимой датой?Даже если вы измените регулярное выражение с помощью утверждения отрицательного просмотра вперед, например (?!31/0?2) (т. е. не соответствует 31/2 или 31/02), у вас все равно возникнет проблема с принятием 29 02 в невисокосные годы. и о формате даты с одним разделителем.

Проблема непростая, если вы хотите действительно проверить дату, проверьте это ветка форума.

Для примера или лучшего способа на С# проверьте эта ссылка

Если вы используете другую платформу/язык, сообщите нам об этом.

Версия Перла 6

После того, как вы используете это для проверки ввода, значения доступны в $/ или индивидуально как $<month>, $<day>, $<year>.(это всего лишь синтаксис для доступа к значениям в $/ )

Не было предпринято никаких попыток проверить год или то, что он не соответствует 29 февраля в невисокосные годы.

Если вы собираетесь настаивать на том, чтобы делать это с помощью регулярного выражения, я бы порекомендовал что-то вроде:

( (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.Если введен ноль, он возвращает сегодняшний номер даты, поэтому вам сначала нужно проверить наличие ненулевой записи.

Подходит для моей ситуации, возможно, и для вашей?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top