1前書き

正規表現は、適切に使用されるとさまざまな種類のパターンを照合するための強力なツールです。

この記事では、

java.util.regex

パッケージを使用して、特定の

String

に有効な日付が含まれているかどうかを判断します。

正規表現の概要については、

Java正規表現APIガイド

を参照してください。


2日付フォーマットの概要

国際グレゴリオ暦に関連して有効な日付を定義します。私たちのフォーマットは一般的なパターンに従います。

__YYYY-MM-DD

2月29日の日を含む年である

leap

年の概念も含めてみましょう。

グレゴリオ暦によると、

100

で割り切れるものを除いて、

4

で均等に分割できる場合は

leap

と呼びます。ただし、__400で割り切れるものも含みます。

その他すべての場合







年を

通常

と呼びます。

有効な日付の例:


  • 2017-12-31


  • 2020-02-29


  • 2400-02-29

無効な日付の例:


  • 2017/12/31

    :誤ったトークン区切り文字


  • 2018-1-1

    :先行ゼロがありません


  • 2018-04-31

    :4月の間違った日数


  • 2100-02-29

    :値が

    100

    で割られるので今年は飛躍しません

2月は28日に制限されています


3ソリューションの実装

正規表現を使用して日付を一致させるので、最初に

DateMatcher

インターフェースをスケッチしてみましょう。これは単一の

matches

メソッドを提供します。

public interface DateMatcher {
    boolean matches(String date);
}

以下では、実装を段階的に説明し、最後に完全なソリューションを目指します。


3.1. ブロードフォーマットのマッチング

まず、Matcherのフォーマット制約を処理する非常に単純なプロトタイプを作成します。

class FormattedDateMatcher implements DateMatcher {

    private static Pattern DATE__PATTERN = Pattern.compile(
      "^\\d{4}-\\d{2}-\\d{2}$");

    @Override
    public boolean matches(String date) {
        return DATE__PATTERN.matcher(date).matches();
    }
}

  • 最初のグループは4つの整数で構成され、残りの2つのグループはそれぞれ2つの整数を持ちます。

一致する日付:

2017-12-31



2018-01-31



0000-00-00



1029-99-72

日付の不一致:

2018-01



2018-01-XX



2020/02/29


3.2. 特定の日付フォーマットのマッチング

2番目の例では、フォーマットの制約と同様に日付トークンの範囲を受け入れます。簡単にするために、私達は私達の興味を1900年から2999年までに制限しました。

一般的な日付形式との一致に成功したので、これをさらに制限する必要があります。日付が実際に正しいことを確認するためです。

^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$

ここでは、一致する必要がある3つの整数グループを紹介します。

  • `(19 | 2[0-9])[0-9]{2}`は、にマッチさせることで制限された年の範囲をカバーします。


19

または

2X

で始まり、その後に任意の数桁の数字が続く番号。

  • `0[1-9]| 1[012]`は

    01-12

    の範囲の月数と一致します

  • `0[1-9]|[12][0-9]| 3[01]`は

    01-31

    の範囲の日数に一致します

一致する日付:

1900-01-01



2205-02-31



2999-12-31

一致しない日付:

1899-12-31



2018-05-35



2018-13-05



3000-01-01



2018-01-XX


3.3. 2月29日のマッチング

うるう年を正確に一致させるには、まずうるう年に遭遇した時期を特定し、次に2月29日をそれらの年の有効な日付として受け入れるようにする必要があります。

制限範囲内のうるう年の年数は十分に大きいので、それらをフィルタリングするために適切な可分性規則を使用する必要があります。

  • __数字の最後の2桁の数字が割り切れる数

4で、元の数は4で割り切れる
** __数字の下2桁が00の場合、その数字は割り切れます

100__までに

これが解決策です。

^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$

パターンは以下の部分で構成されています。

  • `2000 | 2400 | 2800`は閏年の集合と

    400

    の区切り文字で一致


1900-2999

の範囲内
** `19 | 2[0-9](0[48]|[2468][048]|[13579][26])`はすべての

white-list

にマッチします


4

を分割していない年の組み合わせ

100

の区切り文字
** `-02-29`は

2月2日

に一致

マッチング日:

2020-02-29



2024-02-29



2400-02-29

一致しない日:

2019-02-29



2100-02-29



3200-02-29

、__2020/02/29


3.4. 2月の一般日のマッチング

うるう年の2月29日に一致するだけでなく、

2年の他のすべての日(1 – 28)にも一致する必要があります

^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$

一致する日付:

2018-02-01



2019-02-13



2020-02-25

日付の不一致:

2000-02-30



2400-02-62



2018/02/28


3.5. 31日間のマッチング

1月、3月、5月、7月、8月、10月、12月は、1日から31日の間で一致します。

^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$

一致する日付:

2018-01-31



2021-07-31



2022-08-31

日付の不一致:

2018-01-32



2019-03-64



2018/01/31


3.6. 30日の月のマッチング

4月、6月、9月、11月は1日から30日の間で一致するはずです。

^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$

日付の一致:

2018-04-30



2019-06-30



2020-09-30

日付の不一致:

2018-04-31



2019-06-31



2018/04/30


3.7. グレゴリオ暦日付マッチャー

これで、上記のすべてのパターンを単一のマッチャーに

結合して、すべての制約を満たす完全な

GregorianDateMatcher


を得ることができます。

class GregorianDateMatcher implements DateMatcher {

    private static Pattern DATE__PATTERN = Pattern.compile(
      "^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$"
      + "|^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$"
      + "|^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$"
      + "|^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$");

    @Override
    public boolean matches(String date) {
        return DATE__PATTERN.matcher(date).matches();
    }
}

  • 4つのブランチのうち少なくとも1つに一致させるために

    代替文字「|」を使用しました。したがって、2月の有効日はうるう年の2月29日の最初の枝、または

    1

    から

    28__のいずれかの日の2番目の枝にも一致します。残りの月の日付は3番目と4番目の枝に一致します。

読みやすさを優先してこのパターンを最適化していないので、試してみてください。

現時点で、私たちはすべての制約を満たしています。はじめに紹介しました。


3.8. パフォーマンスに関する注意

複雑な正規表現を解析することは実行フローのパフォーマンスに大きな影響を与えるかもしれません。

信頼性が高く、日付を検証するための迅速なアプローチが必要な場合は、Java 8によって提供される

LocalDate.parse()

の使用を検討してください。


4結論

この記事では、グレゴリオ暦の厳密にフォーマットされた日付と一致させるための正規表現の使用方法、形式、範囲、および月の長さの規則も説明しました。

この記事に掲載されているコードはすべてhttps://github.com/eugenp/tutorials/tree/master/java-dates[Githubで利用可能]です。これはMavenベースのプロジェクトなので、そのままインポートして実行するのは簡単なはずです。