ValiDate ISO 8601 від RX


16

Виклик

Знайдіть найкоротший вираз, який

  1. підтверджує, тобто відповідність, усіх можливих дат у пролептичному григоріанському календарі (що стосується також усіх дат до його першого прийняття в 1582 р.) та
  2. не відповідає жодній недійсній даті.

Вихідні дані

Таким чином, вихідний результат є фальшивим або фальсийним.

Вхідні дані

Введіть будь-який із трьох розширених форматів дати 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, але не на 100, якщо він також не ділиться на 400. Рік нульовий У цьому календарі існує рік, і це високосний рік.

Довгий рік в тижневому календарі ISO містить 53 - й тиждень, що один може термін на « стрибок тиждень ». Це відбувається в усі роки, коли 1 січня - четвер, а також у всі високосні роки, де середа. Виявляється, це відбувається кожні 5 або 6 років, як правило, за нерегулярною схемою.

На рік принаймні 4 цифри. Роки з більш ніж 10 цифрами не повинні підтримуватися, оскільки це досить близько до віку Всесвіту (приблизно 14 мільярдів років). Провідний знак плюс необов’язковий, хоча фактичний стандарт передбачає, що його потрібно вимагати роками з більш ніж 4 цифрами.

Часткові або усічені дати, тобто з точністю до дня, не повинні прийматися.

Частини позначення дати, наприклад, місяць, не містять повинні відповідати групі, на яку можна посилатися.

Правила

Це код-гольф. Виграє найкоротший регулярний вираз без виконаного коду. Оновлення: Ви можете використовувати такі функції, як рекурсія та збалансовані групи, але штрафуватиметься в 10 разів, на який кількість символів потім множиться! Це тепер відрізняється від правил у жорсткому коді гольфу: Regex для поділу на 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", щоб можна було використовувати діалекти, які дозволяють рекурсію тощо (тобто CFG, а не RG).
Криссов

Я б настійно пропонував вам обмежити мову регулярних виразів, оскільки учаснику буде дуже кислим працювати протягом декількох годин над рішенням, щоб лише його тривіально бити мовою, яка є принципово потужнішою. Якщо ви обмежили мову фактичним визначенням CS регулярних виразів (наприклад, DFA), то проблема стає цікавою відповіддю на оптимізацію.
orlp

Перевірка дат ISO-8601 за допомогою регулярних виразів - це те, що я насправді повинен був зробити для роботи. Але погоджуюся з orlp, я думаю, тут потрібна мова.
Олексій А.

1
Regex успадковує від Method в Perl 6, тому сама є формою виконуваного коду.
Бред Гілберт 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

Я ще не все перевірив, але, здається, ви набираєте найбільше байтів за (?!…)виразами порівняно з моїм рішенням.
Криссов

1
@Crissov (?!…)Вирази зберігають лише кілька байтів. Я скоротив багато байтів, поєднавши в один один три позитивних / негативних тижня року / дня-тижня. Останні не відповідають один одному. Таким чином, я отримав 8 довгих суб-шаблонів до 5. Також, оскільки |20|25|це така ж довжина, як |2[05]|я пішов на більш читабельний варіант.
CJ Dennis

Цей вираз відповідає тестовому випадку -0000-08-10 і не збігається ␠2015-08-10␠з пробілом провідної та кінцевої пробілів, але оскільки обидва були довільними рішеннями або необов’язковими функціями, я дозволю його проскакувати.
Крісов

Я думаю, що це рішення має помилку щодо дати в межах W50.
Криссов

W(?!00)([0-4]\d|51|52)-[1-7]повинно бути щось рівнозначне W(?!00)([0-4]\d|5[0-2])-[1-7]. Це додає одного символу в довжину. 779
Криссов

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

Це, або зворотне, може бути узгоджене аналогічно простим регулярним виразом для всіх двоцифрових чисел з нульовим нулем:

(?<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 днем ​​- 01січень, 03березень, 05травень, 07липень, 08серпень, 10жовтень та 12грудень. Всього чотири місяці мають рівно 30 днів, 04квітень, 06червень, 09вересень та 11листопад. Нарешті, 02лютий має 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])

Довгі роки, що включають-W53 повторення в 400-річному циклі, наприклад, додайте 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.

Кожне з чотирьох століть має унікальний візерунок. Напевно, не так багато місця для оптимізації.

  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-ю цифрою.
    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]
  • Групується за 2-ю цифрою.
    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

Число століття легко знову узгоджується варіацією виразу поділення.

  • І століття: [02468][048]|[13579][26]
  • ІІ століття: [02468][159]|[13579][37]
  • 3 століття: [02468][26]|[13579][048]
  • 4 століття: [02468][37]|[13579][159]

Поки що це працює лише за позитивні роки, включаючи нуль року. За негативні роки нам доведеться віднімати значення зі списку вище від 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 Це правда, я зараз додаду два символи.
Криссов

Я також додав додаткові провідні та кінцеві пробіли \s*.
Криссов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.