RXによるValiDate ISO 8601


16

チャレンジ

最短の正規表現を見つける

  1. Prolepticグレゴリオ暦のすべての可能な日付(1582年の最初の採用前のすべての日付にも適用されます)を検証、つまり一致させます。
  2. 無効な日付と一致しません。

出力

したがって、出力は真実または偽です。

入力

入力は、3つの拡張ISO 8601日付形式のいずれかであり、時間はありません。

最初の2つは±YYYY-MM-DD(年、月、日)と±YYYY-DDD(年、日)です。両方ともうるう日に特別なケーシングが必要です。これらは、これらの拡張RXによって個別に単純にマッチングされます。

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

3番目の入力形式は±YYYY-wWW-D(年、週、日)です。複雑なうるう週パターンのため、複雑なものです。

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

3つすべてを組み合わせた場合の基本的な、しかし不十分な妥当性チェックは、次のようになります。

[+-]?\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で割り切れません。 このカレンダーにはゼロ年が存在し、うるう年です。

長い年のISO週のカレンダーでは、1つの「という用語ができた、第53週が含まうるう週を」。これは、1月1日が木曜日であるすべての年に発生し、さらに水曜日であるすべてのうるう年に発生します。通常、5年または6年ごとに、一見不規則なパターンで発生することが判明しています。

年は少なくとも4桁です。10桁を超える年は、宇宙の年齢(約140億年)に十分近いため、サポートする必要はありません。先頭のプラス記号はオプションですが、実際の標準では、4桁を超える年数は必須であることを示しています。

部分的な日付または切り捨てられた日付、つまり日精度未満の日付は受け入れられません。

日付表記の一部、たとえば月は、参照可能なグループと一致する必要ありませ

ルール

これはコードゴルフです。実行されたコードのない最短の正規表現が優先されます。更新:再帰やバランスのとれたグループなどの機能を使用できますが、10倍の罰金が科せられ、その後文字数が乗算されます!これは、ハードコードゴルフの規則とは異なります。分割可能性の正規表現は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 

最後の1つはオプションで有効です。つまり、入力文字列の先頭と末尾のスペースは削除されます。

無効な形式

-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指定しない場合、選択は制限されません。意図的に「正規表現」または「RX」を書いたので、再帰などを許可する方言を使用できます(つまり、RGではなくCFG)。
クリソフ

正規表現の言語を制限することを強くお勧めします。なぜなら、競技者が基本的により強力な言語によって些細なことで解決されるだけで何時間も作業するのは非常に辛いからです。言語を(DFAなどの)正規表現の実際のCS定義に制限する場合、問題は興味深い最適化の答えになります。
-orlp

正規表現を使用してISO-8601の日付を検証することは、実際に仕事のためにやらなければならなかったことです。しかし、orlpには同意します。ここでは言語が必要だと思います。
アレックスA.

1
正規表現はPerl 6のMethodを継承するため、それ自体が実行可能コードの形式です。
ブラッドギルバート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 (?!…)式はそれぞれ数バイトしか保存しません。正/負の週/週の年パターンのうち3つをそれぞれ1つに組み合わせることで、多くのバイトを削減しました。最後のものは互いに対応していません。それで、5つまでの8つの長いサブパターンを取得しました。また、より読みやすいオプションにし|20|25|たのと同じ長さなので|2[05]|
CJデニス

この式はテストケース -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]。これにより、長さに1文字が追加されます。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、 …

これ、またはその逆は、先頭にゼロが付いたすべての2桁の数字に対する同様に単純な正規表現で一致させることができます。

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

年の日

3桁の序数の日付はかなり単純で、-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か月、044月、066月、099月、1111 月のちょうど30日間です。最後に、022月は平年で28日、le年で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を追加し、3番目のエントリで現在の年を見つけます。

  • 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

どちらかの数字でグループ化して、2バイト程度保存できることがわかります。

  • 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

世紀数は、分割可能性の表現のバリエーションによって再び簡単に一致します。

  • 1世紀: [02468][048]|[13579][26]
  • 2世紀: [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デニス

@CJDennisそれは本当です。2文字を追加します。
クリソフ

また、オプションの先頭と末尾のスペースを追加しました\s*
クリソフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.