ValiDate ISO 8601 firmy RX

16

Wyzwanie

Znajdź najkrótszą regex tego

  1. zatwierdza, tj. dopasowuje, każdą możliwą datę w proleptycznym kalendarzu gregoriańskim (która dotyczy również wszystkich dat przed jej pierwszym przyjęciem w 1582 r.) oraz
  2. nie pasuje do żadnej niepoprawnej daty.

Wynik

Wyniki są zatem zgodne z prawdą lub falsey.

Wejście

Dane wejściowe są w jednym z 3 rozszerzonych formatów daty ISO 8601 - bez żadnych czasów.

Pierwsze dwa to ±YYYY-MM-DD(rok, miesiąc, dzień) i ±YYYY-DDD(rok, dzień). Oba wymagają specjalnej obudowy na dzień przestępny. Są naiwnie dobierane osobno przez te rozszerzone RX:

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

Trzeci format wejściowy to ±YYYY-wWW-D(rok, tydzień, dzień). Jest to skomplikowane ze względu na skomplikowany wzór tygodnia przestępnego.

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

Podstawowa, ale niewystarczająca kontrola poprawności dla wszystkich trzech razem wyglądałaby mniej więcej tak:

[+-]?\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]))

Warunki

Rok przestępny w Proleptycznym kalendarzu gregoriańskim zawiera dzień przestępny, …-02-29 a zatem ma 366 dni, stąd też …-366istnieje. Dzieje się tak w każdym roku, którego liczba porządkowa jest podzielna przez 4, ale nie przez 100, chyba że jest również podzielna przez 400. Rok zerowy istnieje w tym kalendarzu i jest to rok przestępny.

Długie lata w tygodniu kalendarza ISO zawiera tydzień 53., które mogłyby jeden perspektywie „ skok tydzień ”. Dzieje się tak we wszystkich latach, w których 1 stycznia jest czwartkiem, a dodatkowo we wszystkich latach przestępnych, w których jest środa. Okazuje się, że występuje zwykle co 5 lub 6 lat, na pozór nieregularny wzór.

Rok ma co najmniej 4 cyfry. Lata z więcej niż 10 cyframi nie muszą być obsługiwane, ponieważ jest to wystarczająco blisko epoki wszechświata (około 14 miliardów lat). Wiodący znak plus jest opcjonalny, chociaż rzeczywisty standard sugeruje, że powinien być wymagany przez lata z więcej niż 4 cyframi.

Częściowe lub skrócone daty, tj. Z dokładnością poniżej jednego dnia, nie mogą być akceptowane.

Części zapisu daty, np. Miesiąc, nie muszą być dopasowywane przez grupę, do której można się odwoływać.

Zasady

To jest golf golfowy. Najkrótsze wyrażenie regularne bez wykonanego kodu wygrywa. Aktualizacja: Możesz używać funkcji takich jak rekurencja i zbalansowane grupy, ale zostaniesz ukarany grzywną 10-krotnie, przez co liczba postaci zostanie pomnożona! Jest to teraz inne niż zasady gry w golfa Hard Code: Regex dla podzielności przez 7 . Wcześniejsza odpowiedź wygrywa remis.

Przypadki testowe

Prawidłowe testy

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 

Ostatni jest opcjonalnie ważny, tzn. Można przyciąć początkowe i końcowe spacje w łańcuchach wejściowych.

Niepoprawne formaty

-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

Nieprawidłowe daty

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
Crissov
źródło
2
To pytanie nie jest dobrze zdefiniowane, ponieważ język wyrażeń regularnych nie jest określony.
orlp
1
@orlp Jeśli nie jest określony, wybór nie jest ograniczony. Celowo napisałem „regex” lub „RX”, aby można było używać dialektów umożliwiających rekurencję itp. (Tj. CFG, a nie RG).
Crissov,
Zdecydowanie zalecam ograniczenie języka regex, ponieważ zawodnik będzie godzinami pracował nad rozwiązaniem tylko po to, by zostać pobitym przez język, który jest zasadniczo potężniejszy. Jeśli ograniczysz język do faktycznej definicji wyrażeń regularnych w CS (np. DFA), problem stanie się interesującą odpowiedzią optymalizacyjną.
orlp
Sprawdzanie poprawności dat ISO-8601 za pomocą wyrażeń regularnych to coś, co musiałem zrobić w pracy. Ale zgadzam się z orlp, myślę, że tutaj potrzebny jest język.
Alex A.,
1
Regex dziedziczy po metodzie w Perlu 6, więc sam jest formą kodu wykonywalnego.
Brad Gilbert b2gills

Odpowiedzi:

4

PCRE (także Perl), 778 bajtów

/^([+-]?\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])$/

Dołączyłem ograniczniki do liczby bajtów, aby pokazać, że nie opiera się na żadnych flagach.

To nie nie pasuje do ważnych dat w ramach innych ciągów, takie jak1234-56-89 2016-02-29 9876-54-32 . Wyrażenie regularne jest krótsze, ponieważ nie sprawdza maksymalnie 10 cyfr dla roku.

Rozszerzony o komentarze:

/^  # 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
CJ Dennis
źródło
Nie sprawdziłem jeszcze wszystkiego, ale wydaje się, że zyskujesz najwięcej bajtów dzięki (?!…)wyrażeniom w porównaniu do mojego rozwiązania.
Crissov,
1
@ Crissov (?!…)Wyrażenia zapisują tylko kilka bajtów. Zmniejszyłem wiele bajtów, łącząc trzy pozytywne / negatywne wzorce tygodnia / dnia tygodnia w jeden. Te ostatnie nie pasują do siebie. Mam więc 8 długich pod-wzorów do 5. Ponadto, ponieważ |20|25|jest tej samej długości, co |2[05]|wybrałem dla bardziej czytelnej opcji.
CJ Dennis
To wyrażenie pasuje do przypadku testowego -0000-08-10 i nie pasuje ␠2015-08-10␠do początkowych i końcowych białych znaków, ale ponieważ obie były przypadkowymi decyzjami lub opcjonalnymi funkcjami, pozwolę mu się przesunąć.
Crissov,
Myślę, że to rozwiązanie ma błąd dat w W50.
Crissov
W(?!00)([0-4]\d|51|52)-[1-7]musi być czymś równoważnym W(?!00)([0-4]\d|5[0-2])-[1-7]. Dodaje to jeden znak do długości. 779
Crissov,
9

PCRE: 603 940 947 949 956 bajtów

^\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*$

Uwaga: niektóre pary nawiasów mogłyby zostać upuszczone.

Podzielność przez 4

Wielokrotności 4 powtarzają się w prostym wzorze:

  • 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, …

To, lub odwrotnie, może być dopasowane również przez proste wyrażenie regularne dla wszystkich liczb dwucyfrowych z wiodącym zerem:

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

Mógłby zaoszczędzić trochę bajtów, gdyby istniały klasy znaków dla cyfr nieparzystych i parzystych (jak \oi \e), ale nie są tak daleko, jak mi wiadomo.

Lat

To wyrażenie wystarczyłoby dla kalendarza juliańskiego, ale wykrycie gregoriańskiego roku przestępnego musi w szczególnym przypadku poprzedzać 00podzielność wieku przez 4:

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

Wymagałoby to pewnych zmian w celu wprowadzenia zakazu -0000-…(wraz z -00000-…itp.) Lub wymuszenia znaku plus dla dodatnich liczb roku z więcej niż 4 cyframi. To ostatnie byłoby raczej proste, ale nie jest wymagane:

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

Dzień roku

Trzycyfrowe daty porządkowe są raczej proste, musimy ograniczyć się -366do lat przestępnych (i nie zezwalać -000).

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

Dzień miesiąca roku

Siedem miesięcy z 31 dniami to 01styczeń, 03marzec, 05maj, 07lipiec, 08sierpień, 10październik i 12grudzień. Tylko cztery miesiące mają dokładnie 30 dni, 04kwiecień, 06czerwiec, 09wrzesień i 11listopad. Wreszcie 02luty ma 28 dni wspólnych lat i 29 dni przestępnych. Możemy najpierw skonstruować wyrażenie regularne na zawsze ważnych dni 01przez 28, a następnie dodać szczególne przypadki.

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

Nie może być ani miesiąc, ani dzień, 00który nie był objęty wcześniejszą wersją.

Dzień tygodnia roku

Wszystkie lata obejmują 52 tygodnie

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

Długie lata, które obejmują-W53 powtarzanie w cyklu 400-letnim, np. Dodaj 2000 dla bieżącego cyklu i znajdź bieżący rok w trzeciej pozycji:

  • 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.

Każdy z czterech stuleci ma unikalny wzór. Prawdopodobnie nie ma zbyt wiele miejsca na optymalizację.

  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

Możemy pogrupować według dowolnej cyfry, aby dowiedzieć się, że możemy zapisać około dwóch bajtów:

  • Pogrupowane według 1. cyfry.
    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]
  • Pogrupowane według 2. cyfry.
    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

Liczbę stuleci można łatwo dopasować ponownie poprzez zmianę wyrażenia podzielności.

  • 1 wiek: [02468][048]|[13579][26]
  • II wiek: [02468][159]|[13579][37]
  • III wiek: [02468][26]|[13579][048]
  • IV wiek: [02468][37]|[13579][159]

Jak dotąd działa to tylko dla pozytywnych lat, w tym dla roku zerowego. W przypadku lat ujemnych musimy odjąć wartości z powyższej listy od 400, a resztę wykonać ponownie, ponieważ wzorzec nie jest symetryczny.

  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

lub

  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]

Kładąc wszystko razem

Dowolny rok

[+-]?\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])

Dodatki do roku przestępnego

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

Dodatki do roku przestępnego

+?\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]
Crissov
źródło
Twój wzorzec nie jest zakotwiczony na początku i na końcu, więc będzie pasował do prawidłowych dat wewnątrz niepoprawnego łańcucha.
CJ Dennis
@CJDennis To prawda, dodam teraz dwie postacie.
Crissov,
Dodałem również opcjonalne spacje wiodące i końcowe \s*.
Crissov,