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

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

  • M/D/YYYY
  • MM/DD/YYYY
  • Однозначные месяцы могут начинаться с начального нуля (например: 03/12/2008)
  • Однозначные дни могут начинаться с начального нуля (например: 3/02/2008)
  • НЕ МОЖЕТ включать 30 февраля или 31 февраля (например: 2/31/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)$

Это соответствует правилу EXCEPT, он по-прежнему включает в себя 2/30/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.

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

Ответ 1

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

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

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

Ответ 2

Здесь находится Reg ex, который соответствует всем действительным датам, включая високосные годы. Форматы принимаются в формате mm/dd/yyyy или mm-dd-yyyy или mm.dd.yyyy

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

любезность Асик Ахмед

Ответ 3

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

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

Ответ 4

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

Данные тестирования

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

Я надеюсь, что это кому-то полезно.

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

Ответ 5

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

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

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

(((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 месяцем.

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

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

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

Ответ 6

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

Ответ 7

расширенная версия 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}$

Ответ 8

версия 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-м февраля в непиковые годы.

Ответ 9

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

Ответ 10

    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;
        }
    }

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

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

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

Ответ 14

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

( (0?1|0?3| <...> |10|11|12) / (0?1| <...> |30|31) |
  0?2 / (0?1| <...> |28|29) ) 
/ (19|20)[0-9]{2}

Это может сделать возможным читать и понимать.

Ответ 15

Несколько иной подход, который может или не может быть полезен для вас.

Я нахожусь в php.

В проекте, к которому это относится, никогда не будет датироваться до 1 января 2008 года. Итак, я беру слово "дата" и использую strtotime(). Если ответ будет = = 1199167200, то у меня есть дата, которая мне полезна. Если вводится то, что не похоже на дату, возвращается -1. Если введено значение null, оно возвращает номер сегодняшней даты, поэтому сначала вам нужно проверить непустую запись.

Работает для моей ситуации, возможно, и у вас?