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

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

  • имеет диапазон от 1800 - теперь
  • выполняет правильную проверку даты с високосными годами
  • ММ/ДД / гггг форма
  • Неверная Проверка Даты

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

текущий код:

$('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()));    
});

обновление:

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

11 ответов


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

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? [доказательство того, что мы изобретаем колесо])

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

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

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


вот как я бы это сделал:

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)

демо: http://jsfiddle.net/9QNRx/


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

это регулярное выражение для формата гггг-ММ-ДД

((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

используя moment (не regex) я сделал следующее:

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

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

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

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


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

Я пытался проверить гггг-ММ-ДД, где гггг может быть двузначным, а мм и ДД могут быть одним. Вот что я придумал. Он рассматривает все века как високосные годы.

((\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)

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

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

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

мини - тестовый случай в Ruby (^ заменить \A и $ С \Z, потому что Руби):

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

((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:  "  );

}

}