Регулярное выражение | Високосные годы и многое другое

Недавно я искал регулярное выражение для проверки даты на стороне клиента, и я не смог найти тот, который может удовлетворять следующим критериям:

  • Имеет диапазон от 1800 - теперь
  • Выполняет правильную проверку даты с летальными годами
  • Форма MM/DD/YYYY
  • Неверная проверка даты

(Эти ограничения были вне моей сферы действия и являются требованием для клиента, несмотря на мои попытки убедить их, что это не лучший маршрут)

Текущий код:

$('input').keyup(function()
{
       var regex = /^(?:(0[1-9]|1[012])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})$/;
       $(this).toggleClass('invalid',!regex.test($(this).val()));    
});

Update:

Я должен отметить, что это прежде всего, чтобы увидеть, возможно ли регулярное выражение, подобное этому (поскольку использование Regex не является моим выбором в этом вопросе). Я знаю другие (и лучшие) варианты для проверки даты, однако, как уже упоминалось ранее, это означает, что это возможно с помощью регулярного выражения.

Ответ 1

Как упоминается в другом месте, регулярные выражения почти не соответствуют тому, что вы хотите. Но, сказав это, если вы действительно хотите регулярное выражение, вот как оно построено:

31-дневный месяц

(0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2}

30-дневные месяцы

(0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2}

Февраль 1-28 всегда действительный

(02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2}

29 февраля также действителен в високосный год

(02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)

что означает, что это было бы, если бы вы все вместе взяли:

((0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})|((0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2})|((02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))

Эта версия немного короче, но немного сложнее понять.

((0[13578]|1[02])[\/.]31[\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\/.](29|30)[\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))

Эти сценарии длинные и неподъемные. Должно быть ясно, что это не очень хорошая идея, но это возможно.

Предостережения:

  • диапазон 1800-2099 (больше можно добавить без особых трудностей, но требует изменений в 4-6 разрозненных местах)
  • требуется 2 цифры месяцев и дней (строгость может быть удалена из выражения в ~ 8 местах)
  • [\/.] в качестве разделителей (8 мест)
  • Не тестировался (мы могли проверить его на все комбинации цифр и сравнить с функцией даты javascript? [доказательство того, что мы изобретаем колесо])

Ответ 2

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

Еще лучше, посмотрите, будет ли функция Javascript Date.parse делать то, что вы хотите.

Синхронизация дат с регулярными выражениями возможна, но расстраивает. Трудно понять, выражение для мастеров, не относящихся к регулярному выражению, трудно понять (что означает, что трудно доказать, что вещь верна), и она медленна по сравнению с другими вариантами.

Ответ 3

Вот как я это сделаю:

function validate( input ) {
    var date = new Date( input );
    input = input.split( '/' );   
    return date.getMonth() + 1 === +input[0] && 
           date.getDate() === +input[1] && 
           date.getFullYear() === +input[2];
}

Использование:

validate( '2/1/1983' ) // true
validate( '2/29/1983' ) // false
validate( '2/29/1984' ) // true (1984 is a leap year)

Live demo: http://jsfiddle.net/9QNRx/

Ответ 4

Очевидно, что регулярные выражения не идеальный способ сделать это. Кроме того, гораздо безопаснее работать с форматом YYYY-MM-DD (ISO 8601), а не MM/DD/YYYY.

Таким образом, здесь идет кратчайшее полностью действующее регулярное выражение для дат с 01/01/1800 по 12/31/2099:

^(((0[1-9]|1[012])\/(?!00|29)([012]\d)|(0[13-9]|1[012])\/(29|30)|(0[13578]|1[02])\/31)\/(18|19|20)\d{2}|02\/29\/((18|19|20)(0[48]|[2468][048]|[13579][26])|2000))$

Длина: 162 символа.

Структура:

^ # start
  (
    ( # non-leap months & days
      (0[1-9]|1[012])/(?!00|29)([012]\\d) # all months, days 01-28, uses negative lookahead
    |
      (0[13-9]|1[012])/(29|30) # all months except feb, days 29,30
    |
      (0[13578]|1[02])/31 # all 31 day months, day 31 only
    )
    /
    (18|19|20)\\d{2} # all years
  |
    02/29 # leap day
    /
    (
      (18|19|20)(0[48]|[2468][048]|[13579][26]) # leap years not divisible by 100
    |
      2000 # leap years divisible by 100
    )
  )
$ # end

Здесь скрипка, которая проверяет все варианты использования с 00/00/1800 на 99/99/2099.

Кроме того, для большего удовольствия, вот еще одна скрипка, которая генерирует самое страшное возможное регулярное выражение, которое все еще работает, 1205306 символов. Это выглядит примерно так:

^(01/01/1800|01/02/1800|01/03/1800|...|12/29/2099|12/30/2099|12/31/2099)$

Ответ 5

это регулярное выражение для формата YYYY-MM-DD

((18|19|20)[0-9]{2}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]))|(18|19|20)[0-9]{2}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30)|(18|19|20)[0-9]{2}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8])|(((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)[\-.](02)[\-.]29

Ответ 6

Используя момент (не регулярное выражение), я сделал следующее:

Предполагая, что у вас есть дата ISO как строковое значение:

var isoDate = '2016-11-10';
var parsedIsoDate = moment(isoDate, ['YYYY-MM-DD'], true).format('YYYY-MM-DD');

if (parsedIsoDate !== isoDate) {
    // Invalid date.
}

Ответ 7

^(((?:(?:1[6-9]|[2-9]\d)?\d{2})(-)(?:(?:(?:0?[13578]|1[02])(-)31)|(?:(?:0?[1,3-9]|1[0-2])(-)(?:29|30))))|(((?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))(-)(?:0?2(-)29))|((?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(-)(?:(?:0?[1-9])|(?:1[0-2]))(-)(?:0[1-9]|1\d|2[0-8]))))$

Попробуйте приведенное выше выражение Reg. Я попробовал несколько комбинаций и нашел, что работает.

Пожалуйста, проверьте, подходит ли это для вас.

Принимаемый формат: ГГГГ-ММ-ДД

Год принят от 1600

Ответ 8

Здравствуйте, найдите RegEx для вашего требования

  • Имеет диапазон от 1800
  • Сейчас выполняет правильную проверку даты с високосными годами
  • Формат ДД/ММ/ГГГГ
  • Неверная дата проверки

^ (? :(?: 31 (/) (?: 0 [13578] | 1 [02]))\1 | (:(?: 29 |? 30) (/) (?: 0 [13-9] | 1 [0-2])\2)) (:(?: 18 |? 19 | 20)\д {2}) $ |? ^ (?: 29 (/) 02\3 (:( :(? :(?: 18 | 19 | 20)) (?: 0 [48] | [2468] [048] | [13579] [26])))) $ |? ^ (?: 0 [1-9] | 1\д | 2 [0-8]) (/) (:(?: 0 [1-9]) | (:??? 1 [0-2]))\4 (:(?: 18 | 19 | 20)\д {2}) $

enter image description here

Изображение и отладка RegEx на https://www.debuggex.com/

Тестирование:

  • DD/MM/YYYY
  • 01/12/190 Не соответствует
  • 29.02.1903 Не соответствует
  • 37/02/1903 не соответствует
  • 03.09.1703 Не соответствует
  • 03.09.2103 Не соответствует
  • 31.09.2103 Не соответствует
  • 29.02.1904 - Матч
  • 01.12.1988 - Матч

Ответ 9

Это RegEx, который я использую для проверки даты на стороне клиента. Он имеет диапазон от 1000 до 2999, проверяет високосные годы и, возможно, временную часть. Разве это не великолепно:)

var r = /^(0[1-9]|1\d|2[0-8]|29(?=-\d\d-(?!1[01345789]00|2[1235679]00)\d\d(?:[02468][048]|[13579][26]))|30(?!-02)|31(?=-0[13578]|-1[02]))-(0[1-9]|1[0-2])-([12]\d{3})(\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d))?$/gm;

r.test('20-02-2013 10:01:07'); // true
r.test('29-02-1700');          // false
r.test('29-02-1604 14:01:45'); // true
r.test('29-02-1900 20:10:50'); // false
r.test('31-12-2000');          // true
r.test('31-11-2008 05:05:05'); // false
r.test('29-02-2004 05:01:23'); // true
r.test('24-06-2014 24:10:05'); // false

Ответ 10

Я пытался проверить YYYY-MM-DD, где YYYY может быть двухзначным, а MM и DD могут быть одинаковыми. Это то, что я придумал. Он рассматривает все века как високосные годы.

((\d\d)?\d\d-((0?(1|3|5|7|8)|10|12)-(31|30|[21]\d|0?[1-9])|(0?(4|6|9)|11)-(31|30|[21]\d|0?[1-9])|0?2-((2[0-8]|1\d)|0?[1-9]))|(\d\d)?((0|2|4|6|8)(0|4|8)|(1|3|5|7|9)(2|6))-0?2-29)

Ответ 11

Добавление моего ответа только для спорта - иначе я полностью согласен с @Jim.

Это будет соответствовать високосным годам, в том числе тем, у которых цифры меньше или больше 4.

^\d*((((^|0|[2468])[048])|[13579][26])00$)|((0[48]|(^0*|[2468])[048]|[13579][26]))$

Мини-тестовый пример в Ruby (^ заменен на \A и $ на \Z, потому что Ruby):

r = /\A\d*((((\A|0|[2468])[048])|[13579][26])00\Z)|((0[48]|(\A0*|[2468])[048]|[13579][26]))\Z/
100000.times do |year|
  leap = year % 4 == 0 && ((year % 100 != 0) || (year % 400 == 0))
  leap_regex = !year.to_s[r].nil?
  if leap != leap_regex
    print 'Assertion broken:', year, leap, leap_regex, "\n"
  end
end

Ответ 12

Ниже Regex не работает на 29.10.1956

((0 [13578] | 1 [02]) 31/[0-9] {2} [/.].) | ((01 | 0 [3-9] | 1 [1-2])/./. [0-9] {2}) | ((0 [1-9] | 1 [0-2])/./[0-9] {2}) |. ((02) [/.] 29/.)

?

Ответ 13

((0 [13578] | 1 [02]) 31/[0-9] {2} [/.].) | ((01 | 0 [3-9] | 1 [1-2])/./.[0-9]{2})|((0[1-9]|1[0-2])/./.[0-9]{2})|((02)[/.] 29/.)

Ответ на короткую версию не работает в течение 10/29 и 10/30 в год, когда длинная версия работает ниже - это простая программа java script, которую я написал для тестирования

import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class RegxDateTest {

public static void main(String[] args) {
    // String to be scanned to find the pattern.
      String line = "This order was placed for QT3000! OK?";
        String pattern ="((0[13578]|1[02])[\\/.]31[\\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\\/.](29|30)[\\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\\/.](0[1-9]|1[0-9]|2[0-8])[\\/.](18|19|20)[0-9]{2})|((02)[\\/.]29[\\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))";
      // Create a Pattern object
      Pattern r = Pattern.compile(pattern);
      LocalDate startDate = new LocalDate("1950-01-01");
      LocalDate endDate = new LocalDate("2020-01-01");
      for (LocalDate date = startDate; date.isBefore(endDate); date = date.plusDays(1))
      {
          if (date.toString("MM/dd/yyyy").matches(pattern)) {
             // System.out.println("This date does  match:  " + date.toString("MM/dd/yyyy") );
            }else{
                  System.out.println("This date does not match:  " + date.toString("MM/dd/yyyy") );
            }

      }
      String baddate1="02/29/2016";
      if (baddate1.matches(pattern)) {
          System.out.println("This date does  match:  " + baddate1 );
      }else{
          System.out.println("This date does not match:  " + baddate1 );
      }
      System.out.println("alldone:  "  );

}

}