RX의 ValiDate ISO 8601


16

도전

가장 짧은 정규식을 찾으십시오.

  1. Proleptic Gregorian 달력 (1582 년에 처음 채택되기 전의 모든 날짜에도 적용됨)에서 가능한 모든 날짜를 확인하고 일치시킵니다.
  2. 유효하지 않은 날짜와 일치하지 않습니다.

산출

그러므로 결과는 진실되거나 거짓이다.

입력

입력은 3 가지 확장 된 ISO 8601 날짜 형식 중 하나로 시간이 없습니다.

처음 두 가지는 ±YYYY-MM-DD(년, 월, 일)과 ±YYYY-DDD(년, 일)입니다. 둘 다 도약의 날을 위해 특별한 케이스가 필요합니다. 이 확장 RX와는 별도로 순진하게 일치합니다.

(?<year>[+-]?\d{4,})-(?<month>\d\d)-(?<day>\d\d)
(?<year>[+-]?\d{4,})-(?<doy>\d{3})

세 번째 입력 형식은 ±YYYY-wWW-D(년, 주, 일)입니다. 복잡한 도약 주간 패턴으로 인해 복잡한 것입니다.

(?<year>-?\d{4,})-W(?<week>\d\d)-(?<dow>\d)

세 가지 조합에 대한 기본이지만 불충분 한 유효성 검사는 다음과 같습니다.

[+-]?\d{4,}-((0\d|1[0-2])-([0-2]\d|3[01]) ↩
            |([0-2]\d\d|3[0-5]\d|36[0-6]) ↩
            |(W([0-4]\d|5[0-3])-[1-7]))

정황

윤년 예 기적 그레고리 안 달력에는 포함 윤일 …-02-29 따라서 그것은 따라서 366 일 긴하다 …-366존재한다. 이는 서수를 4로 나눌 수 있지만 400으로 나눌 수있는 경우가 아니라면 100이 아닌 해에 발생 합니다.이 달력에 0 년이 있으며 윤년입니다.

ISO 주 달력 의 긴 연도 에는 53 주가 포함되며, 이는“ 윤리 주 ” 라고 할 수 있습니다. 이것은 1 월 1 일이 목요일 인 모든 년과 추가로 수요일 인 모든 윤년에 발생합니다. 일반적으로 5-6 년마다 불규칙적으로 보이는 것으로 나타났습니다.

1 년은 최소 4 자리 숫자입니다. 10 자리가 넘는 연도는 우주의 나이 (약 140 억 년)에 가깝기 때문에 지원할 필요가 없습니다. 실제 표준에 따르면 4 자리 이상의 숫자가 필요하지만 선행 더하기 부호는 선택 사항입니다.

일부 또는 잘린 날짜 (예 : 일 정밀도 미만)는 허용되지 않아야합니다.

날짜 표기의 일부 (예 : 월)는 참조 할 수있는 그룹과 일치 하지 않아도됩니다.

규칙

이것은 코드 골프입니다. 실행 된 코드가없는 가장 짧은 정규식이 이깁니다. 업데이트 : 재귀 및 균형 그룹과 같은 기능을 사용할 수 있지만 10을 곱하면 문자 수에 곱할 수 있습니다! 이것은 이제 Hard code golf : Regex for 7 의 규칙과 다릅니다 . 조기 답변이 동점입니다.

테스트 사례

유효한 테스트

2015-08-10
2015-10-08
12015-08-10
-2015-08-10
+2015-08-10
0015-08-10
1582-10-10
2015-02-28
2016-02-29
2000-02-29
0000-02-29
-2000-02-29
-2016-02-29
200000-02-29
2016-366
2000-366
0000-366
-2016-366
-2000-366
2015-081
2015-W33-1
2015-W53-7
 2015-08-10 

마지막 것은 선택적으로 유효합니다. 즉, 입력 문자열의 선행 및 후행 공백이 잘릴 수 있습니다.

잘못된 형식

-0000-08-10     # that's an arbitrary decision
15-08-10        # year is at least 4 digits long
2015-8-10       # month (and day) is exactly two digits long, i.e. leading zero is required
015-08-10       # year is at least 4 digits long
20150810        # though a valid ISO format, we require separators; could also be interpreted as a 8-digit year
2015 08 10      # separator must be hyphen-minus
2015.08.10      # separator must be hyphen-minus
2015–08–10      # separator must be hyphen-minus
2015-0810
201508-10       # could be October in the year 201508
2015 - 08 - 10  # no internal spaces allowed
2015-w33-1      # letter ‘W’ must be uppercase
2015W33-1       # it would be unambiguous to omit the separator in front of a letter, but not in the standard
2015W331        # though a valid ISO format we require separators
2015-W331
2015-W33        # a valid ISO date, but we require day-precision
2015W33

유효하지 않은 날짜

2015        # a valid ISO format, but we require day-precision
2015-08     # a valid ISO format, but we require day-precision
2015-00-10  # month range is 1–12
2015-13-10  # month range is 1–12
2015-08-00  # day range is 1–28 through 31
2015-08-32  # max. day range is 1–31
2015-04-31  # day range for April is 1–30
2015-02-30  # day range for February is 1–28 or 29
2015-02-29  # day range for common February is 1–28
2100-02-29  # most century years are non-leap
-2100-02-29 # most century years are non-leap
2015-000    # day range is 1–365 or 366
2015-366    # day range is 1–365 in common years
2016-367    # day range is 1–366 in leap years
2100-366    # most century years are non-leap
-2100-366   # most century years are non-leap
2015-W00-1  # week range is 1–52 or 53
2015-W54-1  # week range is 1–53 in long years
2016-W53-1  # week range is 1–52 in short years
2015-W33-0  # day range is 1–7
2015-W33-8  # day range is 1–7

2
정규식 언어가 지정되지 않았기 때문에이 질문은 잘 정의되지 않았습니다.
orlp

1
@orlp 지정하지 않으면 선택이 제한되지 않습니다. 나는 의도적으로“regex”또는“RX”를 썼으므로 재귀 등을 허용하는 방언을 사용할 수있다 (즉, RG가 아닌 CFG).
Crissov

나는 정규 표현식 언어를 제한 할 것을 강력히 권합니다. 근본적으로 더 강력한 언어로 사소하게 구타되는 해결책에 대해 참가자가 몇 시간 동안 일하는 것이 매우 신맛이 나기 때문입니다. DFA와 같은 정규 표현식의 실제 CS 정의로 언어를 제한하면 문제는 흥미로운 최적화 답변이됩니다.
orlp

정규 표현식을 사용하여 ISO-8601 날짜를 검증하는 것은 실제로 작업을 위해해야했던 일입니다. 그러나 orlp에 동의합니다. 언어가 여기에 필요하다고 생각합니다.
Alex A.

1
정규식은 Perl 6의 Method를 상속하므로 그 자체가 실행 가능한 코드 형식입니다.
Brad Gilbert b2gills

답변:


4

PCRE (Perl), 778 바이트

/^([+-]?\d*((([02468][048]|[13579][26]|\d\d(?!00))([02468][048]|[13579][26]))|\d{4}(?!-02-29|-366))-((?!02-3|(0[469]|11)-31|000)((0[1-9]|1[012])-(0[1-9]|[12]\d|30|31)|([012]\d\d|3([0-5]\d|6[0-6])))|(W(?!00)([0-4]\d|51|52)-[1-7]))|((\+?\d*([02468][048]|[13579][26])|-\d*([02468][159]|[13579][37]))(04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99)|(\+?\d*([02468][159]|[13579][37])|-\d*([02468][26]|[13579][048]))(05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95)|(\+?\d*([02468][26]|[13579][048])|-\d*([02468][37]|[13579][159]))(01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96)|\+?\d*(([02468][37]|[13579][159])(03|14|20|25|31|36|42|53|59|64|70|76|81|87|92|[049]8))|-\d*(([02468][048]|[13579][26])([059]2|08|13|19|24|30|36|41|47|58|64|69|75|80|86|97)))-W53-[1-7])$/

바이트 수에 구분 기호를 포함하여 플래그에 의존하지 않음을 보여줍니다.

와 같은 다른 문자열 내에서 유효한 날짜와 일치 하지 않습니다1234-56-89 2016-02-29 9876-54-32 . 정규식은 해당 연도에 최대 10 자리를 확인하지 않아 더 짧습니다.

주석으로 확장 :

/^  # Start of pattern (no leading space)
  (
    # YEAR
    # Optional sign and digits if more than 4 in year
    [+-]?\d*(
      # Years 00??, 04??, 08?? ... 92??, 96?? OR dd not followed by 00
      # followed by 00, 04, 08 ... 92, 96 OR
      (([02468][048]|[13579][26]|\d\d(?!00))([02468][048]|[13579][26])) |
      # any year not followed by 29 February or day 366
      \d{4}(?!-02-29|-366)
    # dash
    ) -
    # MONTH AND DAY, or DAY OF YEAR, or WEEK OF YEAR AND DAY if less than 53 weeks
    (
      # Not (30 or 31 February OR 31 April, June, September or December OR day 0)
      (?!02-3|(0[469]|11)-31|000)
      (
        # Month         dash         day         OR
        (0[1-9]|1[012]) - (0[1-9]|[12]\d|30|31) |
        # 001-299 OR 300-359 OR 360-366
        ([012]\d\d | 3([0-5]\d | 6[0-6]))
      # OR
      ) |
      (
        # W    01-52    dash    1-7
        W(?!00)([0-4]\d|51|52)-[1-7]
      )
    # OR
    ) |
    # WEEK OF YEAR AND DAY only if week is 53
    (
      # Optional plus and extra year digits
      \+?\d*(
        # Years +0303 - +9998
        ([02468][37]|[13579][159])(03|14|20|25|31|36|42|53|59|64|70|76|81|87|92|[049]8)
      ) |
      # Minus and extra year digits
      -\d*(
        # Years -0002 - -9697
        ([02468][048]|[13579][26])([059]2|08|13|19|24|30|36|41|47|58|64|69|75|80|86|97)
      ) |
      # Years +0004 - +9699, -0104 - -9799
      (\+?\d*([02468][048]|[13579][26])|-\d*([02468][159]|[13579][37]))
          (04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99) |
      # Years +0105 - +9795, -0205 - -9895
      (\+?\d*([02468][159]|[13579][37])|-\d*([02468][26]|[13579][048]))
          (05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95) |
      # Years +0201 - +9896, -0301 - -9996
      (\+?\d*([02468][26]|[13579][048])|-\d*([02468][37]|[13579][159]))
          (01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96)
    # dash W 53 dash 1-7
    )-W53-[1-7]
  # End of pattern (no trailing space)
  )$/x

아직 모든 것을 확인하지는 않았지만 (?!…)내 솔루션과 비교하여 표현식으로 가장 많은 바이트를 얻는 것 같습니다 .
Crissov

1
@Crissov (?!…)표현식은 각각 몇 바이트 만 저장합니다. 양수 / 음수 요일 / 요일 패턴을 각각 하나로 결합하여 많은 바이트를 줄였습니다. 마지막 것은 서로 일치하지 않습니다. 그래서 8 개의 긴 하위 패턴을 5로 줄였습니다. 또한 더 읽기 쉬운 옵션을 사용 |20|25|했을 때와 같은 길이 |2[05]|입니다.
CJ Dennis

이 표현은 테스트 사례 -0000-08-10 와 일치 ␠2015-08-10␠하며 선행 및 후행 공백과 일치하지 않지만 둘 다 임의의 결정 또는 선택적 기능이므로 슬라이드하겠습니다.
Crissov

이 솔루션에는 50 원 안에 날짜에 버그가 있다고 생각합니다.
Crissov

W(?!00)([0-4]\d|51|52)-[1-7]와 동등한 것이어야합니다 W(?!00)([0-4]\d|5[0-2])-[1-7]. 길이에 한 문자가 추가됩니다. 779
Crissov

9

PCRE : 603 940 947 949 956 바이트

^\s*[+-]?(\d{4,10}-((00[1-9]|0[1-9]\d|[12]\d\d|3[0-5]\d|36[0-5])|(0[1-9]|1[0-2])-(0[1-9]|1\d|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31|W(0[1-9]|[1-4]\d|5[0-2])-[1-7]))|((\d{2,8}([13579][26]|[2468][048]|0[48])|(\d{0,6}([13579][26]|[02468][048])00))-(366|02-29))|(\+?\d{0,6}(([02468][048]|[13579][26])([26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9)|([02468][159]|[13579][37])(50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9)|([02468][26]|[13579][048])([48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29)|([02468][37]|[13579][159])([27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9))|-\d{0,6}(([02468][048]|[13579][26])(0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27])|([02468][159]|[13579][37])(0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39])|([02468][26]|[13579][048])(0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95)|([02468][37]|[13579][159])(0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16])))-W53-[1-7]\s*$

참고 : 일부 괄호 쌍을 제거 할 수 있습니다.

4로 나눌 수 있음

4의 배수는 간단한 패턴으로 반복됩니다.

  • 00, 04, 08, 12, 16,
    20, 24, 28, 32, 36,
    40, 44, 48, 52, 56,
    60, 64, 68, 72, 76,
    80, 84, 88, 92, 96, …

이것 또는 그 반대는 0이 앞에 오는 두 자리 숫자 모두에 대해 마찬가지로 간단한 정규식과 일치시킬 수 있습니다.

(?<divisible-by-four>[13579][26]|[02468][048])
(?<not-divisible-by-four>[13579][048]|[02468][26]|\d[13579])

그것은 (같은 짝수와 홀수 숫자에 대한 문자 클래스가 있다면 몇 바이트를 절약 할 수 \o\e),하지만 지금까지 내가 알고 있어요 거기되지 않습니다.

연령

이 표현은 율리우스 력으로는 충분하지만, 그레고리력 윤년 탐지는 00세기를 4로 나눌 수있는 특별한 경우가 필요합니다 .

(?<leap-year>[+-]?(\d{2,8}([13579][26]|[2468][048]|0[48])|(\d{0,6}([13579][26]|[02468][048])00))
(?<year>[+-]?\d{4,10})

이를 위해서는 무법자 변경 -0000-…(등과 함께 -00000-…) 또는 4 자리 이상의 양수 연도에 더하기 부호를 적용해야합니다. 후자는 다소 단순하지만 필수는 아닙니다.

(?<leap-year>([+-]?(\d\d([13579][26]|[2468][048]|0[48])|(([13579][26]|[02468][048])00)))|([+-](\d{3,8}([13579][26]|[2468][048]|0[48])|(\d{1,6}([13579][26]|[02468][048])00))))
(?<year>([+-]?\d{4})|([+-]\d{5,10}))

년의 날

세 자리 서수는 다소 간단 -366합니다. 윤년 으로 제한해야합니다 -000.

(?<ordinal-day>-(00[1-9]|0[1-9]\d|[12]\d\d|3[0-5]\d|36[0-5]))
(?<ordinal-leap-day>-366)

년의 일

31 일의 7 개월은 011 월, 033 월, 055 월, 077 월, 088 월, 1010 월 및 1212 월입니다. 4 월은 정확히 30 일, 044 월, 066 월, 099 월 및 1111 월입니다. 마지막으로 022 월은 공통 연도는 28 일, 윤년은 29 일입니다. 우리는 먼저 항상 올바른 일 동안 정규 표현식을 구성 할 수 있습니다 01통해 28다음 특별한 경우를 추가합니다.

(?<month-day>-(0[1-9]|1[0-2])-(0[1-9]|1\d|2[0-8]))
(?<short-month-day>-(0[13-9]|1[0-2])-(29|30))
(?<long-month-day>-(0[13578]|1[02])-31)
(?<month-leap-day>-02-29)

00이전 버전에서 다루지 않은 월이나 일이 없어야합니다 .

요일

모든 연도에는 52 주가 포함됩니다

(?<week-day>-W(0[1-9]|[1-4]\d|5[0-2])-[1-7])

-W53400 년 주기로 반복되는 긴 기간 ( 예 : 현재주기에 2000을 추가하고 세 번째 항목에서 현재 연도 찾기) :

  • 004, 009, 015, 020, 026, 032, 037, 043, 048, 054, 060, 065, 071, 076, 082, 088, 093, 099,
  • 105, 111, 116, 122, 128, 133, 139, 144, 150, 156, 161, 167, 172, 178, 184, 189, 195,
  • 201, 207, 212, 218, 224, 229, 235, 240, 246, 252, 257, 263, 268, 274, 280, 285, 291, 296,
  • 303, 308, 314, 320, 325, 331, 336, 342, 348, 353, 359, 364, 370, 376, 381, 387, 392, 398.

4 세기는 각각 독특한 패턴을 가지고 있습니다. 최적화의 여지가 많지 않을 것입니다.

  1. 04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99
  2. 05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95
  3. 01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96
  4. 03|08|14|20|25|31|36|42|48|53|59|64|70|76|81|87|92|98

우리는 두 자릿수를 절약 할 수 있음을 알기 위해 숫자로 그룹화 할 수 있습니다.

  • 첫 번째 숫자로 그룹화됩니다.
    1. 0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39]
    2. 05|1[16]|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95
    3. 0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]
    4. 0[38]|14|2[05]|3[16]|4[28]|5[39]|64|7[06]|8[17]|9[28]
  • 두 번째 숫자로 그룹화됩니다.
    1. [26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9
    2. 50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9
    3. [48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29
    4. [27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9

세기 수는 나누기 식의 변형으로 쉽게 다시 일치됩니다.

  • 1 세기 : [02468][048]|[13579][26]
  • 2 세기 : [02468][159]|[13579][37]
  • 3 세기 : [02468][26]|[13579][048]
  • 4 세기 : [02468][37]|[13579][159]

지금까지 이것은 0 년을 포함하여 긍정적 인 년 동안 만 작동합니다. 음수 인 경우 패턴이 대칭이 아니므로 위 목록에서 값을 400에서 빼고 나머지를 다시 수행해야합니다.

  1. 02|08|13|19|24|30|36|41|47|52|58|64|69|75|80|86|92|97
  2. 04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99
  3. 05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95
  4. 01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96

또는

  1. 0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27]
  2. 0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39]
  3. 0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95
  4. 0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]

함께 모아서

매년

[+-]?\d{4,10}-((00[1-9]|0[1-9]\d|[12]\d\d|3[0-5]\d|36[0-5])|(0[1-9]|1[0-2])-(0[1-9]|1\d|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31|W(0[1-9]|[1-4]\d|5[0-2])-[1-7])

윤년 추가

[+-]?(\d{2,8}([13579][26]|[2468][048]|0[48])|(\d{0,6}([13579][26]|[02468][048])00))-(366|02-29)

윤년 추가

+?\d{0,6}(([02468][048]|[13579][26])([26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9)|([02468][159]|[13579][37])(50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9)|([02468][26]|[13579][048])([48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29)|([02468][37]|[13579][159])([27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9))-W53-[1-7]
-\d{0,6}(([02468][048]|[13579][26])(0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27])|([02468][159]|[13579][37])(0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39])|([02468][26]|[13579][048])(0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95)|([02468][37]|[13579][159])(0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]))-W53-[1-7]

패턴은 시작과 끝에 고정되어 있지 않으므로 유효하지 않은 문자열 내의 유효한 날짜와 일치합니다.
CJ Dennis

@CJDennis 사실, 이제 두 문자를 추가하겠습니다.
Crissov

또한 선택적인 선행 및 후행 공백을 추가했습니다 \s*.
Crissov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.