Регулярное выражение для соответствия действительным датам - PullRequest
56 голосов
/ 09 сентября 2008

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

  • M / D / YYYY * * +1004
  • 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 и 2/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}$

Соответствует всем действительным месяцам, следующим за форматом MM / DD / YYYY.

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

Ответы [ 15 ]

126 голосов
/ 09 сентября 2008

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

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

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

47 голосов
/ 07 января 2012

Вот 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})$

вежливость Асик Ахамед

19 голосов
/ 28 октября 2016

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

Поддерживаемая версия 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}";

(Не предпринимались попытки ограничить значения для года.)

5 голосов
/ 12 апреля 2013

Для контроля срока действия даты в следующем формате:

ГГГГ / ММ / ДД или ГГГГ-ММ-ДД

Я бы порекомендовал вам использовать следующее регулярное выражение:

(((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. Вы найдете его там (поищите мое имя): Регулярное выражение для проверки метки времени

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

4 голосов
/ 09 сентября 2008

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

3 голосов
/ 21 января 2012

Если вы не сработали вышеупомянутые предложения, я использую это, поскольку он получает любую дату, когда я пропускал это выражение по 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])$ 
3 голосов
/ 14 сентября 2008

расширенная версия 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}$
2 голосов
/ 29 апреля 2013

Это регулярное выражение проверяет даты между 01-01-2000 и 12-31-2099 с соответствующими разделителями.

^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$
2 голосов
/ 23 ноября 2012
    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;
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...