ValiDate ISO 8601 bằng RX


16

Thử thách

Tìm regex ngắn nhất mà

  1. xác nhận, tức là khớp, mọi ngày có thể trong lịch Proleptic Gregorian (cũng áp dụng cho tất cả các ngày trước khi áp dụng lần đầu tiên vào năm 1582) và
  2. không khớp với bất kỳ ngày không hợp lệ.

Đầu ra

Do đó đầu ra là trung thực hoặc falsey.

Đầu vào

Đầu vào là bất kỳ trong 3 định dạng ngày ISO 8601 mở rộng - không có lần.

Hai cái đầu tiên là ±YYYY-MM-DD(năm, tháng, ngày) và ±YYYY-DDD(năm, ngày). Cả hai đều cần vỏ đặc biệt cho ngày nhuận. Chúng được kết hợp một cách ngây thơ bởi các RX mở rộng này:

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

Định dạng đầu vào thứ ba là ±YYYY-wWW-D(năm, tuần, ngày). Đây là một trong những phức tạp vì mô hình tuần nhuận phức tạp.

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

Một kiểm tra tính hợp lệ cơ bản, nhưng không đủ cho cả ba kết hợp sẽ trông giống như thế này:

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

Điều kiện

Một năm nhuận trong lịch Gregorian Proleptic chứa ngày nhuận …-02-29 và do đó nó dài 365 ngày, do đó …-366tồn tại. Điều này xảy ra trong bất kỳ năm nào có số thứ tự chia hết cho 4, nhưng không phải là 100 trừ khi nó cũng chia hết cho 400. Năm không tồn tại trong lịch này và đó là một năm nhuận.

Một năm dài trong lịch tuần ISO có tuần thứ 53, trong đó người ta có thể gọi là tuần nhuận nhuận . Điều này xảy ra trong tất cả các năm trong đó ngày 1 tháng 1 là thứ năm và thêm vào đó là tất cả các năm nhuận trong đó là thứ tư. Nó thường xảy ra cứ sau 5 hoặc 6 năm, theo một mô hình dường như không đều.

Một năm có ít nhất 4 chữ số. Không cần phải hỗ trợ nhiều năm với hơn 10 chữ số, vì nó đủ gần với tuổi của vũ trụ (khoảng 14 tỷ năm). Dấu cộng hàng đầu là tùy chọn, mặc dù tiêu chuẩn thực tế cho thấy nó cần được yêu cầu trong nhiều năm với hơn 4 chữ số.

Ngày một phần hoặc cắt ngắn, tức là với độ chính xác thấp hơn ngày, không được chấp nhận.

Các phần của ký hiệu ngày, ví dụ như tháng, không phải khớp với một nhóm có thể được tham chiếu.

Quy tắc

Đây là mã golf. Regex ngắn nhất mà không có mã thực thi sẽ thắng. Cập nhật: Bạn có thể sử dụng các tính năng như đệ quy và các nhóm cân bằng, nhưng sẽ bị phạt theo hệ số 10, số lượng ký tự sau đó được nhân với! Điều này bây giờ khác với các quy tắc trong Hard code golf: Regex cho phép chia hết cho 7 . Câu trả lời trước đó thắng một tie.

Các trường hợp thử nghiệm

Kiểm tra hợp lệ

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 

Cái cuối cùng là hợp lệ tùy chọn, tức là khoảng trắng ở đầu và cuối trong chuỗi đầu vào có thể được cắt bớt.

Định dạng không hợp lệ

-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

Ngày không hợp lệ

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
Câu hỏi này không được xác định rõ vì ngôn ngữ regex không được chỉ định.
orlp

1
@orlp Nếu không được chỉ định, sự lựa chọn không bị giới hạn. Tôi đã viết mục đích về regex và hoặc trên RX, vì vậy người ta có thể sử dụng các phương ngữ cho phép đệ quy, v.v. (ví dụ CFG, không phải là RG).
Crissov

Tôi thực sự khuyên bạn nên hạn chế ngôn ngữ regex, bởi vì sẽ rất khó để một thí sinh làm việc hàng giờ với một giải pháp chỉ để bị đánh bại một cách tầm thường bởi một ngôn ngữ mạnh hơn về cơ bản. Nếu bạn giới hạn ngôn ngữ trong định nghĩa CS thực tế của các biểu thức chính quy (như DFA), thì vấn đề sẽ trở thành một câu trả lời tối ưu hóa thú vị.
orlp

Xác thực ngày ISO-8601 bằng các biểu thức thông thường là điều tôi thực sự phải làm cho công việc. Nhưng đồng ý với orlp, tôi nghĩ một ngôn ngữ là cần thiết ở đây.
Alex A.

1
Regex kế thừa từ Phương thức trong Perl 6 vì vậy bản thân nó là một dạng mã thực thi.
Brad Gilbert b2gills 17/1/2016

Câu trả lời:


4

PCRE (cũng Perl), 778 byte

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

Tôi đã bao gồm các dấu phân cách trong số byte để cho thấy rằng nó không dựa vào bất kỳ cờ nào.

không khớp với ngày hợp lệ trong các chuỗi khác, chẳng hạn như 1234-56-89 2016-02-29 9876-54-32. Regex ngắn hơn bằng cách không kiểm tra tối đa 10 chữ số trong năm.

Mở rộng với ý kiến:

/^  # 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

Tôi chưa kiểm tra tất cả mọi thứ, nhưng có vẻ như bạn đạt được nhiều byte nhất bằng các (?!…)biểu thức so với giải pháp của tôi.
Crissov

1
@Crissov Các (?!…)biểu thức chỉ lưu một vài byte mỗi. Tôi đã giảm rất nhiều byte bằng cách kết hợp ba mẫu năm dương / âm của năm / ngày trong tuần thành một mẫu. Những cái cuối cùng không tương ứng với nhau. Vì vậy, tôi đã có 8 mẫu con dài xuống còn 5. Ngoài ra, vì |20|25|nó có cùng độ dài như |2[05]|tôi đã chọn cho tùy chọn dễ đọc hơn.
CJ Dennis

Biểu thức này khớp với trường hợp thử nghiệm -0000-08-10 và không khớp ␠2015-08-10␠với khoảng trắng hàng đầu và dấu kiểm, nhưng vì cả hai đều là các quyết định tùy ý hoặc các tính năng tùy chọn, tôi sẽ để nó trượt.
Crissov

Tôi nghĩ rằng giải pháp này có một lỗi cho ngày trong W50.
Crissov

W(?!00)([0-4]\d|51|52)-[1-7]phải là một cái gì đó tương đương với W(?!00)([0-4]\d|5[0-2])-[1-7]. Điều này thêm một ký tự cho chiều dài. 779
Crissov

9

PCRE: 603 940 947 949 956 byte

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

Lưu ý: Một số cặp dấu ngoặc đơn có thể bị loại bỏ.

Chia hết cho 4

Bội số của 4 lặp lại trong một mẫu đơn giản:

  • 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, Giáo dục

Điều này, hoặc nghịch đảo, có thể được khớp bởi một biểu thức chính quy đơn giản tương tự cho tất cả các số có hai chữ số có số 0 đứng đầu:

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

Nó có thể lưu một số byte nếu có các lớp ký tự cho các chữ số lẻ và chẵn (như \o\e), nhưng không xa như tôi biết.

Năm

Biểu hiện đó sẽ đủ cho lịch Julian, nhưng phát hiện năm nhuận của Gregorian cần theo dõi trường hợp đặc biệt 00với tính phân chia thế kỷ bằng 4:

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

Điều này sẽ cần một số thay đổi đối với ngoài vòng pháp luật -0000-…(cùng với -00000-…v.v.) hoặc để thực thi dấu cộng cho số năm dương với hơn 4 chữ số. Cái sau sẽ khá đơn giản, nhưng không bắt buộc:

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

Ngày trong năm

Ngày thứ tự ba chữ số khá đơn giản, chúng ta chỉ cần hạn chế -366năm nhuận (và không cho phép -000).

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

Ngày trong tháng

Bảy tháng với 31 ngày là 01tháng một, 03tháng ba, 05tháng năm, 07tháng bảy, 08tháng tám, 10tháng 12mười và tháng mười hai. Chỉ cần bốn tháng có đúng 30 ngày, 04tháng Tư, 06tháng Sáu, 09tháng Chín và 11tháng Mười Một. Cuối cùng, 02tháng hai có 28 ngày trong năm chung và 29 ngày trong năm nhuận. Trước tiên chúng ta có thể xây dựng một biểu thức chính quy cho những ngày luôn hợp lệ cho 01đến 28sau đó thêm các trường hợp đặc biệt.

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

Cả tháng và ngày đều 00không được bao gồm trong phiên bản trước.

Ngày trong tuần

Tất cả các năm bao gồm 52 tuần

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

Những năm dài bao gồm-W53 lặp lại trong chu kỳ 400 năm, ví dụ thêm 2000 cho chu kỳ hiện tại và tìm năm hiện tại trong mục thứ ba:

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

Mỗi thế kỷ trong bốn thế kỷ có một mẫu độc đáo. Có lẽ không có nhiều chỗ để tối ưu hóa.

  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

Chúng ta có thể nhóm theo một trong hai chữ số để tìm ra rằng chúng ta có thể lưu hai byte hoặc hơn:

  • Được nhóm theo chữ số 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]
  • Được nhóm theo chữ số 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

Số thế kỷ dễ dàng được khớp một lần nữa bằng một biến thể của biểu thức chia hết.

  • Thế kỷ 1: [02468][048]|[13579][26]
  • Thế kỷ thứ 2: [02468][159]|[13579][37]
  • Thế kỷ thứ 3: [02468][26]|[13579][048]
  • Thế kỷ thứ 4: [02468][37]|[13579][159]

Cho đến nay, điều này chỉ hoạt động trong những năm tích cực, bao gồm cả năm không. Trong những năm âm, chúng ta phải trừ các giá trị khỏi danh sách trên từ 400 và làm lại phần còn lại, vì mẫu không đối xứng.

  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

hoặc là

  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]

Để tất cả chúng cùng nhau

Bất kỳ năm nào

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

Bổ sung năm nhuận

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

Bổ sung năm nhuận

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

Mẫu của bạn không được neo ở đầu và cuối, do đó, mẫu sẽ khớp với các ngày hợp lệ bên trong một chuỗi không hợp lệ.
CJ Dennis

@CJDennis Điều đó đúng, tôi sẽ thêm hai nhân vật ngay bây giờ.
Crissov

Tôi cũng đã thêm các không gian hàng đầu và dấu tùy chọn \s*.
Crissov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.