Javaで日付パターンを照合するための正規表現
1. 前書き
正規表現は、適切に使用すると、さまざまな種類のパターンを照合するための強力なツールです。
この記事では、java.util.regexパッケージを使用して、特定のStringに有効な日付が含まれているかどうかを判断します。
正規表現の概要については、our Guide To Java Regular Expressions APIを参照してください。
2. 日付形式の概要
国際グレゴリオ暦に関連して有効な日付を定義します。 私たちのフォーマットは一般的なパターンに従います:YYYY-MM-DD.
2月29日の日を含む年であるleap年の概念も含めましょう。 According to the Gregorian calendar, we’ll call a year leap if the year number can be divided evenly by 4 except for those which are divisible by 100 but including those which are divisible by 400.
他のすべての場合、,を年regularと呼びます。
有効な日付の例:
-
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. 幅広いフォーマットとのマッチング
まず、マッチャーのフォーマット制約を処理する非常に単純なプロトタイプを作成します。
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();
}
}
ここでは、a valid date must consist of three groups of integers separated by a dash.を指定しています。最初のグループは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つのgroupsを紹介しました。
-
(19|2[0-9])[0-9]{2}
は、19または2Xで始まり、その後に2桁の数字が続く数値を照合することにより、制限された年の範囲をカバーします。 -
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日とのマッチング
うるう年を正しく一致させるには、最初にidentify when we have encountered a leap yearを実行してから、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
は、1900-2999の制限された範囲で、400の除算器を持つうるう年のセットと一致します -
19|2[0-9](0[48]|[2468][048]|[13579][26]))
は、4の仕切りがあり、100の仕切りがない年のすべてのwhite-listの組み合わせに一致します -
-02-29
はFebruary 2ndと一致します
一致する日付: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日と一致するだけでなく、we also need to match all other days of February (1 – 28) in all years:
^(((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. グレゴリオ暦の日付マッチャー
これで、すべての制約を満たすcombine all of the patterns above into a single matcher to have a complete 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つのブランチのWe’ve used an alternation character “|” to match at least one。 したがって、2月の有効日は、うるう年の2月29日の最初のブランチまたは1から28までの任意の日の2番目のブランチのいずれかに一致します。 残りの月の日付は、3番目と4番目のブランチと一致します。
このパターンは読みやすさを重視して最適化されていないため、自由に長さを試してみてください。
この時点で、すべての制約を満たしました。最初に紹介しました。
3.8. パフォーマンスに関する注記
Parsing complex regular expressions may significantly affect the performance of the execution flow.この記事の主な目的は、すべての可能な日付のセットで文字列のメンバーシップをテストする効率的な方法を学ぶことではありませんでした。
日付を検証するための信頼性が高く高速なアプローチが必要な場合は、Java8が提供するLocalDate.parse()の使用を検討してください。
4. 結論
この記事では、形式、範囲、月の長さのルールを提供することにより、グレゴリオ暦の厳密にフォーマットされた日付と一致させるための正規表現の使用方法を学習しました。
この記事に示されているすべてのコードは、over on Githubで利用できます。 これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。