正規表現

正規表現で日付や時刻を表す方法 | 存在しない月日や閏年考慮

正規表現で日付と時刻を表す方法
記事の要約
  • 日付を簡易的に表すには「\d{1,4}/\d{1,2}/\d{1,2}」
  • 日付を厳密に表すには「(19|20)([0-9]{2}/(?!((0[2469]|11)/31)|02/(29|30))((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))|([02468][048]|[13579][26])/02/29)」
  • 時刻を表すには「([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]」

正規表現で日付を表す方法

正規表現で日付を表すには、簡易的な方法として「\d{1,4}/\d{1,2}/\d{1,2}」が使えます。この表現では「2020/12/31」のようにスラッシュで区切られた日付を表せます。ただし、この場合では次のパターンに対応できません。

  • 存在しない月や日を表しうる(例:00月, 44日)
  • 日常的には使わない西暦を含む(例:123年, 9999年)
  • 存在しない月日を表しうる(例:2月30日, 4月31日)
  • 存在しない年月日を表しうる(例:2021年2月29日。閏年でないため)

そのため、正規表現の用途に応じてどこまでの正確さが必要か判断しましょう。本記事では厳密なものも含め表現の仕方を解説します。

【参考】

日付にマッチさせる簡易的な正規表現

まずは1番シンプルな表現方法である「\d{1,4}/\d{1,2}/\d{1,2}」です。1〜4桁の年、1〜2桁の月、1〜2桁の日をつなげた表現になっています。自分用のツールや一時的な使い方であれば、この表現でも十分事足りるでしょう。

// 日付表現(簡易的)
\d{1,4}/\d{1,2}/\d{1,2}

// マッチさせたい日付
2000/01/01 // マッチ

// マッチさせたくない日付
2010/00/33 // マッチ
2010/11/33 // マッチ
9999/04/25 // マッチ
2020/02/30 // マッチ
2021/02/29 // マッチ
シャワーズ

環境によっては「/(スラッシュ)」にエスケープが必要です。その場合には「\/」となるように表記を調整しましょう!

しかしこの表現では下のように存在しない、ないしあまり用いられない数値も含まれています。

  • 年:0000〜9999
  • 月:00〜99
  • 日:00〜99

以下ではこれらの数値を除外する方法について触れていきます。

日付にマッチさせる複雑な正規表現(存在しない月と日を除く)

00月や44日のように存在しない2桁の月と日を除いた表現は、「\d{1,4}/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])」となります。年の部分は同じですが、月と日についてはそれぞれ01〜12月、01〜31日に置き換わっています。

// 日付表現(00月, 44日のように存在しない月と日を除く)
\d{1,4}/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])

// マッチさせたい日付
2000/01/01 // マッチ

// マッチさせたくない日付
2010/00/33 // マッチせず
2010/11/33 // マッチせず
9999/04/25 // マッチ
2020/02/30 // マッチ
2021/02/29 // マッチ

込み入ってきたので少し解説します。月の部分については次のとおりです。

  • 0[1-9]では01〜09月
  • 1[0-2]では10〜12月
  • ↑を( | )にて囲い、01〜09月または10〜12月を表現

また日付については次のとおりです。

  • 0[1-9]では01〜09日
  • [12][0-9]では10〜29日
  • 3[01]では30〜31日
  • ↑を( | )にて囲い、01〜09月または10〜12月を表現

これらを組み合わせることにより、当初よりは厳密な表現ができるようになりました。ただ、この表現でも「2月30日」のように存在しない月と日の組み合わせについては除外しきれていません。そちらはもう少しあとに紹介します。

【参考】

日付にマッチさせる複雑な正規表現(使わない年を除く)

0000年や9999年のように生年月日や今日の日付を表示する場面ではなかなか用いない数を排除するには、年を表す「\d{1,4}」を「(19|20)[0-9]{2}」に置き換えます。「(19|20)[0-9]{2}」は1900〜2099までを表すのでそこそこ実用的かと思われます。

// 日付表現(1900年〜2099年に限定)
(19|20)[0-9]{2}/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])

// マッチさせたい日付
2000/01/01 // マッチ

// マッチさせたくない日付
2010/00/33 // マッチせず
2010/11/33 // マッチせず
9999/04/25 // マッチせず
2020/02/30 // マッチ
2021/02/29 // マッチ

この表現についても少し細かく見ていきましょう。

  • (19|20)では19XXまたは20XX年
  • [0-9]{2}ではXX00〜XX99年
  • ↑をつなげることで1900〜2099年を表現

日付にマッチさせる複雑な正規表現(31日のない月を除く)

続いて、2月30日のように存在しない日付を除く(閏年以外の2月29日も含む)表現は、「(19|20)[0-9]{2}/(?!((0[2469]|11)/31)|02/30)((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))」などで表せます。具体的には4,6,9,11月の31日と、2月の30日,31日を除いた表現です。

// 日付表現(2月30日のように、4,6,9,11月の31日と、2月の30日,31日を除く)
// 月ごとにすべて列挙するパターン
(19|20)[0-9]{2}/(((0[13578]|10|12)/(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)/(0[1-9]|[12][0-9]|30]))|(02/(0[1-9]|[12][0-9])))
// 31日、30日、29日ごとにかき分けるパターン
(19|20)[0-9]{2}/(((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))|((0[13-9]|1[0-2])/30)|((0[13578]|10|12)/31))
// 否定先読みを使うパターン
(19|20)[0-9]{2}/(?!((0[2469]|11)/31)|02/30)((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))

// マッチさせたい日付
2000/01/01 // マッチ

// マッチさせたくない日付
2010/00/33 // マッチせず
2010/11/33 // マッチせず
9999/04/25 // マッチせず
2020/02/30 // マッチせず
2021/02/29 // マッチ

1番表現の短い否定先読みを使うパターン「(19|20)[0-9]{2}/(?!((0[2469]|11)/31)|02/30)((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]|3[01]))」について解説します。

  • (19|20)[0-9]{2}では、1900〜2099年を表す。「日付にマッチさせる複雑な正規表現(使わない年を除く)」参照
  • (?!((0[2469]|11)/31)|02/30)では、否定先読み(?! )を活用し02,04,06,09,11月の31日と2月の30日を除外
  • (0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])では、01〜12月の01〜31日を表す。「日付にマッチさせる複雑な正規表現(存在しない月と日を除く)」参照

これらを組み合わせることで、1900〜2099年のうち「02,04,06,09,11月の31日と2月の30日」以外の01〜12月の01〜31日を表現できます。

日付にマッチさせる複雑な正規表現(閏年の2月29日を含む)

閏年以外の2月29日を含めない日付の正規表現は「(19|20)([0-9]{2}/(?!((0[2469]|11)/31)|02/(29|30))((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))|([02468][048]|[13579][26])/02/29)」です。

// 日付表現(2月30日のように、4,6,9,11月の31日と、2月の30日,31日を除く)
(19|20)([0-9]{2}/(?!((0[2469]|11)/31)|02/(29|30))((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))|([02468][048]|[13579][26])/02/29)

// マッチさせたい日付
2000/01/01 // マッチ

// マッチさせたくない日付
2010/00/33 // マッチせず
2010/11/33 // マッチせず
9999/04/25 // マッチせず
2020/02/30 // マッチせず
2021/02/29 // マッチせず

かなり複雑になったので1つずつみていきましょう。

  • (19|20)では、19XX年,20XX年を表現。
  • ([0-9]{2}/(?!((0[2469]|11)/31)|02/(29|30))((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))では、閏年以外の日付を網羅
    • 「日付にマッチさせる複雑な正規表現(31日のない月を除く)」から2月29日を一律で除外
    • (19|20)と組み合わせて閏年以外の年月日を網羅
  • ([02468][048]|[13579][26])/02/29)では、閏年の場合の2月29日を表現
    • ([02468][048]|[13579][26])にて、閏年を表現
    • /02/29にて、2月29日を表現

鍵となるのが「([02468][048]|[13579][26])にて、閏年を表現」の部分かと思います。まず閏年というのは4の倍数の年が該当します。1900,1904,1908年などです。これを順番に書き出してみると

  • 1900
  • 1904
  • 1908
  • 1912
  • 1916
  • 1920
  • 1924
  • 1928
  • 1932
  • 1936…

といった形になります。これらの年の傾向をまとめるとXX0X年, XX2X年のように十の位が偶数のときは一の位が0,4,8に、XX1X年, XX3X年のように十の位が奇数のときは一の位が2,6になります。

これらをまとめると次のようになります。

  • 十の位が0,2,4,6,8のときは、一の位が0,4,8
    • 正規表現では「[02468][048]」
  • 十の位が1,3,5,7,9のときは、一の位が2,6
    • 正規表現では「[13579][26]」

これを千の位、百の位と組み合わせると次の表現が完成します。

// 1904,1908,1912のように4の倍数年を表す表現
(19|20)([02468][048]|[13579][26])

最後に、ここまでにでてきた表現をまとめあげると次のような網羅的な表現がかけます。

// (再掲)
// 日付表現(2月30日のように、4,6,9,11月の31日と、2月の30日,31日を除く)
(19|20)([0-9]{2}/(?!((0[2469]|11)/31)|02/(29|30))((0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01]))|([02468][048]|[13579][26])/02/29)

// マッチさせたい日付
2000/01/01 // マッチ

// マッチさせたくない日付
2010/00/33 // マッチせず
2010/11/33 // マッチせず
9999/04/25 // マッチせず
2020/02/30 // マッチせず
2021/02/29 // マッチせず

実務でここまで厳密な表現を求められる機会があるかは別として、表現の仕方を考えるいい教材になるのではないでしょうか。

正規表現での和暦を表す方法

日付を表す際に西暦ではなく和暦(年号)を用いて表現する際には、「((昭和(0[1-9]|[1-5][0-9]|6[0-4]))|(平成(0[1-9]|[12][0-9]|3[01]))|(令和(0[1-9]|1[0-9])))年」と記載しましょう。

// 厳密な表現
((昭和(0[1-9]|[1-5][0-9]|6[0-4]))|(平成(0[1-9]|[12][0-9]|3[01]))|(令和(0[1-9]|1[0-9])))年
// (参考)ゆるい表現
(昭和|平成|令和)\d{2}年

// 厳密な表現にマッチする文字列
昭和60年
平成30年
令和05年

// 厳密な表現にマッチしない文字列
昭和72年
平成81年
令和90年

昭和と平成と令和をそれぞれ次の範囲として考えると、

  • 昭和01〜64年
  • 平成01〜31年
  • 令和01〜19年(将来を見越して仮でおく)

のようになり、これらの各正規表現は次のとおりとなります。

  • 昭和(0[1-9]|[1-5][0-9]|6[0-4])
  • 平成(0[1-9]|[12][0-9]|3[01])
  • 令和(0[1-9]|1[1-9])

これをつなげたものが「((昭和(0[1-9]|[1-5][0-9]|6[0-4]))|(平成(0[1-9]|[12][0-9]|3[01]))|(令和(0[1-9]|1[0-9])))年」です。

シャワーズ

自分用にとりあえず使うぐらいであれば「(昭和|平成|令和)\d{2}年」で十分だと思います!その場合「昭和99年」のように存在しない年号にもマッチしてしまう点には注意しましょう

正規表現での時間を表す方法

正規表現にて時間を表すには「([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]」と記載します。これは00〜23時、00〜59分、00〜59秒を「:」にてつなげた表現です。

// 時間を表す正規表現
([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]

// マッチする時間の例
12:34:56
23:59:59
00:00:00

必要に応じて前述の日付の表現と組み合わせて使うとよいでしょう。