※当サイトの記事には、広告・プロモーションが含まれます。

Javaの日付のパターンで年の部分にyyyyとuuuuのどちらを使うべきか

president.jp

⇧ ChatGPTは、ゲーム・チェンジャーになるんですかね。

検索する側からしたら、求める情報に辿り着ける仕組みを提供してくれるのであれば文句はありませんが...

Javaの日付のパターンで年の部分にyyyyとuuuuのどちらを使うべきか

業務で、とあるWebシステムの単体テストをしていて気付いたのですが、SimpleDateFormatに罠が多いように思う。

docs.oracle.com

setLenient

public void setLenient(boolean lenient)
日付/時刻解析を厳密に行うかどうかを設定します。厳密でない解析では、解析機能は、ヒューリスティックな方法を使って、このオブジェクトのフォーマットと完全には一致しない入力を解釈することがあります。厳密な解析では、入力はこのオブジェクトのフォーマットと一致する必要があります。
パラメータ:
lenient - true の場合は厳密ではない解析
関連項目:
Calendar.setLenient(boolean)

https://docs.oracle.com/javase/jp/6/api/java/text/DateFormat.html#setLenient(boolean)

⇧ ドキュメントが紛らわしいのだけど、

fantastic-works.com

qiita.com

⇧ setLenient(false)を実装していても日付/時刻解析を厳密に行ってくれないという...

試しに、EclipseJavaプロジェクトを作成して、

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/"
		};
		
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(checkDate(testDateStrList[index]));
		}
	}
	private static boolean checkDate(String strDate) {
		DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
}

⇧ 上記を実行してみると、

⇧ お分かりいただけただろうか?

何と、20230年も正常な日付とされてしまうのである!

まぁ、2万年後と考えれば正しい日付と言えるので、プログラム的には正常な動作なのかもしらんけど、いまいち、yyyyの正確な判定が分からんよね...

stackoverflowによると、

stackoverflow.com

18888 is a valid year. It's just really far in the future.

From the SimpleDateFormat documentation:

For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits.

(Emphasis mine)

You'll just have to range check the date to make sure it's within whatever bounds you want.

https://stackoverflow.com/questions/8762034/simpledateformat-error-with-year

⇧ ドキュメントに記載があると。

docs.oracle.com

年:フォーマッタのCalendarグレゴリオ暦の場合は、次に示すルールが適用されます。

  • パターン文字の数が2の場合、フォーマットには年が2桁に短縮されます。そうでない場合は、数値として解釈されます。
  • 解析には、パターン文字の数が2以上の場合、年は桁数にかかわらず文字どおりに解釈されます。「MM/dd/yyyy」のパターンを用いると、「01/11/12」はA.D. 12年1月11日に解釈されます。

https://docs.oracle.com/javase/jp/8/docs/api/java/text/SimpleDateFormat.html

⇧ なるほど、年のパターン文字の桁数と解析対象の日付の年の桁数は一致してる必要はないと。

つまり、5桁の年数を許容したくないのなら、独自にバリデーションを実装して、チェック処理を行ってください、ということですかね。

あと、2023/1/1aとかが正常な日付とされてるのも問題なのですが、yyyy/MM/ddの日付パターンで判定してるにも関わらず、2023/1/1とか月日が一桁の日付についても正常になってしまってるのも駄目なのではないかと。

一応、Javaの標準APIのSimpleDataFormatのドキュメントには、

docs.oracle.com

⇧ と説明があり、yyyy-MM-ddは、月と日はいずれも二桁になってるので。(一桁の月日の場合はzero paddingされて二桁になっているので。)

なので、残念ながら、Java8が使えない環境の場合は、日付の解析が実施される前に不正な日付を弾く処理が必要になってきそう。

で、Java8が使える環境であれば、Java8で導入されたDate and Time API (JSR-310)を使っていくのが良いらしいのですが、

bufferings.hatenablog.com

stackoverflow.com

⇧ 和暦を考慮しなくても良いのであれば、年月日の年の部分のパターンとしては、「uuuu」を使っておくのが無難ということみたい。

和暦などの対応は、

qiita.com

⇧ 上記サイト様が詳しいです。

というか、西暦で厳密に日付解析するには、「uuuu」指定が必須になるっぽい。

と、ここで漸く、謎の「uuuu」が出てきたのですが、この「uuuu」は何者なのか?

で、Javaの標準のAPIのドキュメントで、

docs.oracle.com

⇧ Java8で導入されたDate and Time API (JSR-310)の中で、java.time.format.DateTimeFormatterというものがあるのですが、そのドキュメントで「u」の記載がありますと。

というのも、 java.time.format.DateTimeFormatterのドキュメントとjava.text.SimpleDateFormatのドキュメントを見比べてたのだけど、

https://docs.oracle.com/javase/jp/8/docs/api/java/time/format/DateTimeFormatter.html

  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use

https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html

Letter	Date or Time Component	Presentation	Examples
G	Era designator	Text	AD
y	Year	Year	1996; 96
Y	Week year	Year	2009; 09
M	Month in year (context sensitive)	Month	July; Jul; 07
L	Month in year (standalone form)	Month	July; Jul; 07
w	Week in year	Number	27
W	Week in month	Number	2
D	Day in year	Number	189
d	Day in month	Number	10
F	Day of week in month	Number	2
E	Day name in week	Text	Tuesday; Tue
u	Day number of week (1 = Monday, ..., 7 = Sunday)	Number	1
a	Am/pm marker	Text	PM
H	Hour in day (0-23)	Number	0
k	Hour in day (1-24)	Number	24
K	Hour in am/pm (0-11)	Number	0
h	Hour in am/pm (1-12)	Number	12
m	Minute in hour	Number	30
s	Second in minute	Number	55
S	Millisecond	Number	978
z	Time zone	General time zone	Pacific Standard Time; PST; GMT-08:00
Z	Time zone	RFC 822 time zone	-0800
X	Time zone	ISO 8601 time zone	-08; -0800; -08:00

java.time.format.DateTimeFormatterのドキュメントでは、年を表現する「u」が存在するんだけど、java.text.SimpleDateFormatのドキュメントには年を表現する「u」が存在しないっぽいのよね。

話が脱線しましたが、試しに、java.time.format.DateTimeFormatterで年の部分のパターンに「yyyy」を指定してみたところ、

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/"
		};
		
		// 日付チェックの実施と結果を表示
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(checkDate(testDateStrList[index]));
//		}
		
		System.out.println("■Date and Time API");
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(isCorrectDate(testDateStrList[index]));
		}
	}
	private static boolean checkDate(String strDate) {
		DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
	private static boolean isCorrectDate(String strDate) {
		try {
			DateTimeFormatter.ofPattern("yyyy/MM/dd")
			.withResolverStyle(ResolverStyle.STRICT)
			.parse(strDate, LocalDate::from);
		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			return false;
		}
		return true;
	}
	
}

⇧ 上記を実行したところ、

軒並み日付の解析でパースエラーになるっぽい...

日付のパターンの年の部分を「uuuu」にしたところ、

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/"
		};
		
		// 日付チェックの実施と結果を表示
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(checkDate(testDateStrList[index]));
//		}
		
		System.out.println("■Date and Time API");
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(isCorrectDate(testDateStrList[index]));
		}
	}
	private static boolean checkDate(String strDate) {
		DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
	private static boolean isCorrectDate(String strDate) {
		try {
			DateTimeFormatter.ofPattern("uuuu/MM/dd")
			.withResolverStyle(ResolverStyle.STRICT)
			.parse(strDate, LocalDate::from);
		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			return false;
		}
		return true;
	}
	
}

⇧ 正確に解析できてる模様。

Java8で導入されたDate and Time API (JSR-310)を利用して、厳密な日付の解析をするには、年のパターンには「uuuu」を使わざるを得ないようです、つまり、選択の余地が無かったというね...

ということは、既存のシステムで「yyyy」を利用してる個所が多いとリファクタリングが難しい気がする...

そもそもとして、SimpleDataFormatでは、年のパターンに「uuuu」を利用できないっぽいし...

なので、かなり無理やり感があるかもしれませんが、SimpleDataFormatを使わざるを得ない場合は、

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/",
				  "2023/1/001",
				  "2023/001/1",
				  "23/1/01"
		};
		
		String dateFormat = "yyyy/MM/dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(checkDate(testDateStrList[index], dateFormat));
		}
		
		String[] testDateStrList02 = {
				  "2023-1-1",
				  "2023-1-11",
				  "2023-11-1",
				  "2023-11-11",
				  "2023-01-1",
				  "2023-1-01",
				  "2023-01-01",
				  "2023-02-30",
				  "2023-1-1a",
				  "2023-111-111",
				  "20230-1-1",
				  "2023-1-1-",
				  "2023-1-001",
				  "2023-001-1",
				  "23-1-01"
		};
		
		System.out.println("■ハイフン区切り");
		String dateFormat02 = "yyyy-MM-dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList02.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList02[index] + ", ");
			System.out.println(checkDate(testDateStrList02[index], dateFormat02));
		}
		
		
//		System.out.println("■Date and Time API");
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(isCorrectDate(testDateStrList[index]));
//		}
	}

	private static boolean checkDate(String strDate, String dateFormat) {
		// インプットチェック
		if (Objects.isNull(strDate) || strDate.isEmpty() 
				|| Objects.isNull(dateFormat) || dateFormat.isEmpty()) {
			return false;
		}

 		// インプットの日付から半角数字のみ抽出した長さ
		int dateLength = strDate.replaceAll("[^0-9]", "").length();
		// 日付フォーマットからアルファベット部分のみ抽出した長さ
		int dateFormatLength = dateFormat.replaceAll("[^a-zA-Z]", "").length(); 
		// インプットの日付が日付のフォーマットと同じ桁数かと、年月日の値が数字のみかチェック
		if (strDate.length() != dateFormat.length() 
				|| dateLength != dateFormatLength) {
			return false;
		}

		// 年の桁数チェック
		String year = strDate.substring(0, 5);
		int correctYearLength = 4;

		// 年月日の区切りはスラッシュかハイフン
		Pattern pattern = Pattern.compile("[\\-\\/]+");
		Matcher matcher = pattern.matcher(year);
		while (matcher.find()) {
			try {
				if (matcher.start() != correctYearLength) {
					return false;
				}
			} catch (Exception e) {
				return false;
			}			
		}

		// 日付の解析を行う
		DateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
//	private static boolean isCorrectDate(String strDate) {
//		if (Objects.isNull(strDate) || strDate.isEmpty()) {
//			return false;
//		}
//		try {
//			DateTimeFormatter.ofPattern("uuuu/MM/dd")
//			.withResolverStyle(ResolverStyle.STRICT)
//			.parse(strDate, LocalDate::from);
//		} catch (Exception e) {
//			// TODO 自動生成された catch ブロック
//			return false;
//		}
//		return true;
//	}
	
}

⇧ 上記を実行すると、

⇧ 年については4桁をチェックすることはできたのですが、月日の方も桁数のチェックが必要ということが判明したしたという...

さらに頑張ってみたけど、

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Objects;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/",
				  "2023/1/001",
				  "2023/001/1",
				  "23/1/01",
				  "0023/01/01"
		};
		
		String dateFormat = "yyyy/MM/dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(checkDate(testDateStrList[index], dateFormat));
		}
		
		String[] testDateStrList02 = {
				  "2023-1-1",
				  "2023-1-11",
				  "2023-11-1",
				  "2023-11-11",
				  "2023-01-1",
				  "2023-1-01",
				  "2023-01-01",
				  "2023-02-30",
				  "2023-1-1a",
				  "2023-111-111",
				  "20230-1-1",
				  "2023-1-1-",
				  "2023-1-001",
				  "2023-001-1",
				  "23-1-01",
				  "0023-01-01"
		};
		
		System.out.println("■ハイフン区切り");
		String dateFormat02 = "yyyy-MM-dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList02.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList02[index] + ", ");
			System.out.println(checkDate(testDateStrList02[index], dateFormat02));
		}
		
		
//		System.out.println("■Date and Time API");
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(isCorrectDate(testDateStrList[index]));
//		}
	}

	private static boolean checkDate(String strDate, String dateFormat) {
		// インプットチェック
		if (Objects.isNull(strDate) || strDate.isEmpty() 
				|| Objects.isNull(dateFormat) || dateFormat.isEmpty()) {
			return false;
		}

 		// インプットの日付から半角数字のみ抽出した長さ
		int dateLength = strDate.replaceAll("[^0-9]", "").length();
		// 日付フォーマットからアルファベット部分のみ抽出した長さ
		int dateFormatLength = dateFormat.replaceAll("[^a-zA-Z]", "").length(); 
		// インプットの日付が日付のフォーマットと同じ桁数かと、年月日の値が数字のみかチェック
		if (strDate.length() != dateFormat.length() 
				|| dateLength != dateFormatLength) {
			return false;
		}

		// 年の桁数チェック
//		String year = strDate.substring(0, 5);
//		int correctYearLength = 4;
//
//		// 年月日の区切りはスラッシュかハイフン
//		Pattern pattern = Pattern.compile("[\\-\\/]+");
//		Matcher matcher = pattern.matcher(year);
//		while (matcher.find()) {
//			try {
//				if (matcher.start() != correctYearLength) {
//					return false;
//				}
//			} catch (Exception e) {
//				return false;
//			}			
//		}

		String year = strDate.substring(0, 4);
		int correctYearLength = 4;
		if (!year.matches("^[0-9]+$") || year.length() != correctYearLength) {
			return false;
		}

		// 月の桁数チェック
		String month = strDate.substring(5, 7);
		int correctMonthLength = 2;
		if (!month.matches("^[0-9]+$") || month.length() != correctMonthLength) {
			return false;
		}
		
		// 日の桁数チェック
		String day = strDate.substring(8, 10);
		int correctDayLength = 2;
		if (!day.matches("^[0-9]+$") || day.length() != correctDayLength) {
			return false;
		}

		// 日付の解析を行う
		DateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
//	private static boolean isCorrectDate(String strDate) {
//		if (Objects.isNull(strDate) || strDate.isEmpty()) {
//			return false;
//		}
//		try {
//			DateTimeFormatter.ofPattern("uuuu/MM/dd")
//			.withResolverStyle(ResolverStyle.STRICT)
//			.parse(strDate, LocalDate::from);
//		} catch (Exception e) {
//			// TODO 自動生成された catch ブロック
//			return false;
//		}
//		return true;
//	}
	
}

⇧ zero paddingされた年については、正常な日付となりますが、まぁ、問題ないかと。

残念ながら、SimpleDataFormatのみで厳密に日付の解析を行うには難ありということでしょうかね...

ちなみに、Java8で導入されたDate and Time API (JSR-310)のLocalDateのparseメソッドで、ResolverStyle.STRICTを指定しても、zero paddingされた年については正常な日付とされました。

許容する日付のフォーマットは、開発するシステムの要件で仕様次第というか、決めの問題になってくるんですかね?

手入力されたファイルの値とかは、兎も角、Webシステムなんかの画面から入力される値については、フロントエンド側でバリデーションしてもらうのも一つの手でしょうか。

ただ、stackoverflowを見る限り、

stackoverflow.com

⇧ フロントエンド側でもライブラリを使わない場合は、正規表現などで愚直にバリデーションしてる感じではあるっぽい...

JavaScriptだと、

qiita.com

⇧ zero paddingされた年は許容しない動きになることもあるんだとか。

フロントエンドとサーバーサイドで別々に開発してる場合は、このあたりの何を許容するかどうかの認識合わせをしておくのも必要そうですかね。

ちなみに、

devo.hatenablog.com

JavaのSimpleDateFormatには上記のような罠もあるようです。

ただ、

qiita.com

www.bold.ne.jp

閏年については、対応してくれるようです、日付パターンで年まで指定する必要はありますが。

2023年1月30日(月)追記:↓ ここから

ISO 8601の存在を失念していました。

ISO 8601は、日付時刻の表記に関するISOの国際規格である。この規格の主眼は、日付時刻の記述順序が国や文化によってまちまちであるものを、大→小の順序(ビッグエンディアン big-endian)を貫徹して、日付・時刻の記述順序をただ一種類に標準化していることにある。

ISO 8601 - Wikipedia

また、時刻表現は24時制だけに限定している。

  • 2017年7月17日を、2017-07-17(拡張形式)もしくは20170717(基本形式)と表記する。
  • 同日の時刻として 17時07分17.77秒 を併せて表記する場合は、2017-07-17T17:07:17.77(拡張形式)もしくは20170717T170717.77(基本形式)と表記する。すなわち記号 T で区切った後に時刻を続ける。

ISO 8601 - Wikipedia

⇧ こやつのせいで、Tとか余計なものが入ってくるせいで、数字だけの桁数の比較が困難に...

ISO 8601の存在を考慮すると、年月日の桁数チェックが難しい...

かなり強引だけど、実装してみた。

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Objects;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/",
				  "2023/1/001",
				  "2023/001/1",
				  "23/1/01",
				  "0023/01/01"
		};
		
		String dateFormat = "yyyy/MM/dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(checkDate(testDateStrList[index], dateFormat));
		}
		
		System.out.println("■ハイフン区切り");
		String[] testDateStrList02 = {
				  "2023-1-1",
				  "2023-1-11",
				  "2023-11-1",
				  "2023-11-11",
				  "2023-01-1",
				  "2023-1-01",
				  "2023-01-01",
				  "2023-02-30",
				  "2023-1-1a",
				  "2023-111-111",
				  "20230-1-1",
				  "2023-1-1-",
				  "2023-1-001",
				  "2023-001-1",
				  "23-1-01",
				  "0023-01-01",
		};
		
		String dateFormat02 = "yyyy-MM-dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList02.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList02[index] + ", ");
			System.out.println(checkDate(testDateStrList02[index], dateFormat02));
		}
		
		System.out.println("■ISO 8601");
		String[] testDateStrList03 = {
			"2017-07-17T17:07:17.77",
			"2017-007-17T17:07:17.77",
			"2017-07-017T17:07:17.77",
			"20170-07-017T17:07:17.77",
			"2017-07-1TT17:07:17.77",
			"2017-07-1T17:07:17.77",
			"201a-07-17T17:07:17.77",
			"2017-0a-17T17:07:17.77",
			"2017-07-1aT17:07:17.77"
		};
		String dateFormat03 = "yyyy-MM-dd'T'HH:mm:ss.SSS";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList03.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList03[index] + ", ");
			System.out.println(checkDate(testDateStrList03[index], dateFormat03));
		}		

		System.out.println("■年月日と時刻がスペース区切り");
		String[] testDateStrList04 = {
				"2017-07-17 17:07:17",
				"2017-007-17 17:07:17",
				"2017-07-017 17:07:17",
				"20170-07-17 17:07:17",
				"2017-07-17T 17:07:17",
				"2017-07-1T 17:07:17",
				"201a-07-17 17:07:17",
				"2017-0a-17 17:07:17",
				"2017-07-1a 17:07:17"
		};
		String dateFormat04 = "yyyy-MM-dd HH:mm:ss";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList04.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList04[index] + ", ");
			System.out.println(checkDate(testDateStrList04[index], dateFormat04));
		}	
		
//		System.out.println("■Date and Time API");
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(isCorrectDate(testDateStrList[index]));
//		}
	}

	private static boolean checkDate(String strDate, String dateFormat) {
		// インプットチェック
		if (Objects.isNull(strDate) || strDate.isEmpty() 
				|| Objects.isNull(dateFormat) || dateFormat.isEmpty()) {
			return false;
		}

// 		// 日付から半角数字のみ抽出した長さ
//		int dateLength = strDate.replaceAll("[^0-9]", "").length();
//		// 日付フォーマットからアルファベット部分のみ抽出した長さ
//		int dateFormatLength = dateFormat.replaceAll("[^a-zA-Z]", "").length(); 
//		// インプットの日付が日付のフォーマットと同じ桁数かと、年月日の値が数字のみかチェック
//		if (strDate.length() != dateFormat.length() 
//				|| dateLength != dateFormatLength) {
//			return false;
//		}

		// 年の長さチェック
//		String year = strDate.substring(0, 5);
//		int correctYearLength = 4;
//
//		// 年月日の区切りはスラッシュかハイフン
//		Pattern pattern = Pattern.compile("[\\-\\/]+");
//		Matcher matcher = pattern.matcher(year);
//		while (matcher.find()) {
//			try {
//				if (matcher.start() != correctYearLength) {
//					return false;
//				}
//			} catch (Exception e) {
//				return false;
//			}			
//		}
		String tmpFormat = dateFormat.replaceAll("'", "");
		
		// ISO 8601対策、あと、年月日と時刻がスペース区切りになってる場合の対応
		String[] strDateArr = strDate.split("[\\s | T]");
		String[] strDateFromatArr = tmpFormat.split("[\\s | T]");
		
		if (strDateArr.length != strDateFromatArr.length) {
			return false;
		}		
		for (int index = 0; index < strDateArr.length; index++) {
			// 年、月、日、のみチェック
			if (strDateFromatArr[index].toUpperCase().contains("YYYY")
					|| strDateFromatArr[index].contains("MM")
					|| strDateFromatArr[index].toUpperCase().contains("DD")) {

				// 日付の区切り文字
				String splitKey = null;
				if(strDate.contains("-")) {
					splitKey = "-";
				} else if (strDate.contains("/")) {
					splitKey = "/";
				}
				if (Objects.nonNull(splitKey)) {
					if(!checkDateValue(strDateFromatArr[index], strDateArr[index], splitKey)) {
						return false;
					}
				}					
			}
		}
//		String year = strDate.substring(0, 4);
//		int correctYearLength = 4;
//		if (!year.matches("^[0-9]+$") || year.length() != correctYearLength) {
//			return false;
//		}
//
//		// 月の桁数チェック
//		String month = strDate.substring(5, 7);
//		int correctMonthLength = 2;
//		if (!month.matches("^[0-9]+$") || month.length() != correctMonthLength) {
//			return false;
//		}
//
//		// 日の桁数チェック
//		String day = strDate.substring(8, 10);
//		int correctDayLength = 2;
//		if (!day.matches("^[0-9]+$") || day.length() != correctDayLength) {
//			return false;
//		}

		// 日付の解析を行う
		DateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
//	private static boolean isCorrectDate(String strDate) {
//		if (Objects.isNull(strDate) || strDate.isEmpty()) {
//			return false;
//		}
//		try {
//			DateTimeFormatter.ofPattern("uuuu/MM/dd")
//			.withResolverStyle(ResolverStyle.STRICT)
//			.parse(strDate, LocalDate::from);
//		} catch (Exception e) {
//			// TODO 自動生成された catch ブロック
//			return false;
//		}
//		return true;
//	}
	
	// 年月日の桁数チェック
	private static boolean checkDateValue(String strDateFormat, String strDate, String splitKey) {
		// インプットのチェック
		if (Objects.isNull(strDateFormat) || strDateFormat.isEmpty()
				|| Objects.isNull(strDate) || strDate.isEmpty()) {
			return false;
		}

		// 年・月・日に分割
		String[] splitDateArr = strDate.split(splitKey);
		String[] splitFormatArr = strDateFormat.split(splitKey);
		
		// インプットの日付と日付フォーマットの桁数チェック
		if (splitDateArr.length != splitFormatArr.length) {
			return false;
		}
		
		// 年・月・日の値のチェック
		for (int index = 0; index < splitDateArr.length; index++) {
			// 桁数チェック
			if (splitDateArr[index].length() != splitFormatArr[index].length()) {
				return false;
			}
			// 数字かチェック
			if (!splitDateArr[index].matches("^[0-9]+$")) {
				return false;
			}
		}
		return true;
	}
}

とりあえず、ハイフンやスラッシュで区切られた年月日については、桁数チェックできたけども、yyyyMMddとかみたいに区切りの無い日付はどうしたもんか...

ちなみに、

qiita.com

⇧ ドットとかで区切られることもあるそうな...

mommysenglish.com

⇧ 日付のドット区切りって、欧米の仕様ってことですかね?

ドット(ピリオド)区切りも追加しました。

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Objects;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/",
				  "2023/1/001",
				  "2023/001/1",
				  "23/1/01",
				  "0023/01/01"
		};
		
		String dateFormat = "yyyy/MM/dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(checkDate(testDateStrList[index], dateFormat));
		}
		
		System.out.println("■ハイフン区切り");
		String[] testDateStrList02 = {
				  "2023-1-1",
				  "2023-1-11",
				  "2023-11-1",
				  "2023-11-11",
				  "2023-01-1",
				  "2023-1-01",
				  "2023-01-01",
				  "2023-02-30",
				  "2023-1-1a",
				  "2023-111-111",
				  "20230-1-1",
				  "2023-1-1-",
				  "2023-1-001",
				  "2023-001-1",
				  "23-1-01",
				  "0023-01-01",
		};
		
		String dateFormat02 = "yyyy-MM-dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList02.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList02[index] + ", ");
			System.out.println(checkDate(testDateStrList02[index], dateFormat02));
		}
		
		System.out.println("■ISO 8601");
		String[] testDateStrList03 = {
			"2017-07-17T17:07:17.77",
			"2017-007-17T17:07:17.77",
			"2017-07-017T17:07:17.77",
			"20170-07-017T17:07:17.77",
			"2017-07-1TT17:07:17.77",
			"2017-07-1T17:07:17.77",
			"201a-07-17T17:07:17.77",
			"2017-0a-17T17:07:17.77",
			"2017-07-1aT17:07:17.77"
		};
		String dateFormat03 = "yyyy-MM-dd'T'HH:mm:ss.SSS";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList03.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList03[index] + ", ");
			System.out.println(checkDate(testDateStrList03[index], dateFormat03));
		}		

		System.out.println("■年月日と時刻がスペース区切り");
		String[] testDateStrList04 = {
				"2017-07-17 17:07:17",
				"2017-007-17 17:07:17",
				"2017-07-017 17:07:17",
				"20170-07-17 17:07:17",
				"2017-07-17T 17:07:17",
				"2017-07-1T 17:07:17",
				"201a-07-17 17:07:17",
				"2017-0a-17 17:07:17",
				"2017-07-1a 17:07:17"
		};
		String dateFormat04 = "yyyy-MM-dd HH:mm:ss";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList04.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList04[index] + ", ");
			System.out.println(checkDate(testDateStrList04[index], dateFormat04));
		}	
		
		System.out.println("■ピリオド区切り");
		String[] testDateStrList05 = {
				"2023.01.30",
				"2023.001.30",
				"2023.01.030",
				"20230.01.30",
				"202a.01.30",
				"2023.0a.30",
				"2023.01.3a",
		};
		String dateFormat05 = "yyyy.MM.dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList05.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList05[index] + ", ");
			System.out.println(checkDate(testDateStrList05[index], dateFormat05));
		}	
//		System.out.println("■Date and Time API");
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(isCorrectDate(testDateStrList[index]));
//		}
	}

	private static boolean checkDate(String strDate, String dateFormat) {
		// インプットチェック
		if (Objects.isNull(strDate) || strDate.isEmpty() 
				|| Objects.isNull(dateFormat) || dateFormat.isEmpty()) {
			return false;
		}

// 		// 日付から半角数字のみ抽出した長さ
//		int dateLength = strDate.replaceAll("[^0-9]", "").length();
//		// 日付フォーマットからアルファベット部分のみ抽出した長さ
//		int dateFormatLength = dateFormat.replaceAll("[^a-zA-Z]", "").length(); 
//		// インプットの日付が日付のフォーマットと同じ桁数かと、年月日の値が数字のみかチェック
//		if (strDate.length() != dateFormat.length() 
//				|| dateLength != dateFormatLength) {
//			return false;
//		}

		// 年の長さチェック
//		String year = strDate.substring(0, 5);
//		int correctYearLength = 4;
//
//		// 年月日の区切りはスラッシュかハイフン
//		Pattern pattern = Pattern.compile("[\\-\\/]+");
//		Matcher matcher = pattern.matcher(year);
//		while (matcher.find()) {
//			try {
//				if (matcher.start() != correctYearLength) {
//					return false;
//				}
//			} catch (Exception e) {
//				return false;
//			}			
//		}
		String tmpFormat = dateFormat.replaceAll("'", "");
		
		// ISO 8601対策
		String[] strDateArr = strDate.split("[\\s | T]");
		String[] strDateFromatArr = tmpFormat.split("[\\s | T]");
		
		if (strDateArr.length != strDateFromatArr.length) {
			return false;
		}		
		for (int index = 0; index < strDateArr.length; index++) {
			// 年、月、日、のみチェック
			if (strDateFromatArr[index].toUpperCase().contains("YYYY")
					|| strDateFromatArr[index].contains("MM")
					|| strDateFromatArr[index].toUpperCase().contains("DD")) {

				// 日付の区切り文字
				String splitKey = null;
				if(strDate.contains("-")) {
					splitKey = "-";
				} else if (strDate.contains("/")) {
					splitKey = "/";
				} else if (strDate.contains(".")) {
					splitKey = "\\.";
				}
				if (Objects.nonNull(splitKey)) {
					if(!checkDateValue(strDateFromatArr[index], strDateArr[index], splitKey)) {
						return false;
					}
				}					
			}
		}
//		String year = strDate.substring(0, 4);
//		int correctYearLength = 4;
//		if (!year.matches("^[0-9]+$") || year.length() != correctYearLength) {
//			return false;
//		}
//
//		// 月の桁数チェック
//		String month = strDate.substring(5, 7);
//		int correctMonthLength = 2;
//		if (!month.matches("^[0-9]+$") || month.length() != correctMonthLength) {
//			return false;
//		}
//
//		// 日の桁数チェック
//		String day = strDate.substring(8, 10);
//		int correctDayLength = 2;
//		if (!day.matches("^[0-9]+$") || day.length() != correctDayLength) {
//			return false;
//		}

		// 日付の解析を行う
		DateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
//	private static boolean isCorrectDate(String strDate) {
//		if (Objects.isNull(strDate) || strDate.isEmpty()) {
//			return false;
//		}
//		try {
//			DateTimeFormatter.ofPattern("uuuu/MM/dd")
//			.withResolverStyle(ResolverStyle.STRICT)
//			.parse(strDate, LocalDate::from);
//		} catch (Exception e) {
//			// TODO 自動生成された catch ブロック
//			return false;
//		}
//		return true;
//	}
	
	// 年月日の桁数チェック
	private static boolean checkDateValue(String strDateFormat, String strDate, String splitKey) {
		// インプットのチェック
		if (Objects.isNull(strDateFormat) || strDateFormat.isEmpty()
				|| Objects.isNull(strDate) || strDate.isEmpty()) {
			return false;
		}

		// 年・月・日に分割
		String[] splitDateArr = strDate.split(splitKey);
		String[] splitFormatArr = strDateFormat.split(splitKey);
		
		// インプットの日付と日付フォーマットの桁数チェック
		if (splitDateArr.length != splitFormatArr.length) {
			return false;
		}
		
		// 年・月・日の値のチェック
		for (int index = 0; index < splitDateArr.length; index++) {
			// 桁数チェック
			if (splitDateArr[index].length() != splitFormatArr[index].length()) {
				return false;
			}
			// 数字かチェック
			if (!splitDateArr[index].matches("^[0-9]+$")) {
				return false;
			}
		}
		return true;
	}
}

日付の区切り文字、統一して欲しいな...

と言うか、

docs.oracle.com

⇧ 改めて、SimpleDateFormatの例を見る限り、桁数チェックが厳しそうな日付パターンが多いんだが...

あらかじめ、インプットの際の日付パターンを決められるなら、シンプルな日付パターンに統一したいところですな...

2023年1月30日(月)追記:↑ここまで

2023年1月31日(火)追記:↓ ここから

区切りなしの日付のチェック処理も追加してみました。

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Objects;

public class CheckDateFormat {

	public static void main(String[] args) {
		// 
		String[] testDateStrList = {
				  "2023/1/1",
				  "2023/1/11",
				  "2023/11/1",
				  "2023/11/11",
				  "2023/01/1",
				  "2023/1/01",
				  "2023/01/01",
				  "2023/02/30",
				  "2023/1/1a",
				  "2023/111/111",
				  "20230/1/1",
				  "2023/1/1/",
				  "2023/1/001",
				  "2023/001/1",
				  "23/1/01",
				  "0023/01/01"
		};
		
		String dateFormat = "yyyy/MM/dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList[index] + ", ");
			System.out.println(checkDate(testDateStrList[index], dateFormat));
		}
		
		System.out.println("■ハイフン区切り");
		String[] testDateStrList02 = {
				  "2023-1-1",
				  "2023-1-11",
				  "2023-11-1",
				  "2023-11-11",
				  "2023-01-1",
				  "2023-1-01",
				  "2023-01-01",
				  "2023-02-30",
				  "2023-1-1a",
				  "2023-111-111",
				  "20230-1-1",
				  "2023-1-1-",
				  "2023-1-001",
				  "2023-001-1",
				  "23-1-01",
				  "0023-01-01",
		};
		
		String dateFormat02 = "yyyy-MM-dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList02.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList02[index] + ", ");
			System.out.println(checkDate(testDateStrList02[index], dateFormat02));
		}
		
		System.out.println("■ISO 8601");
		String[] testDateStrList03 = {
			"2017-07-17T17:07:17.77",
			"2017-007-17T17:07:17.77",
			"2017-07-017T17:07:17.77",
			"20170-07-017T17:07:17.77",
			"2017-07-1TT17:07:17.77",
			"2017-07-1T17:07:17.77",
			"201a-07-17T17:07:17.77",
			"2017-0a-17T17:07:17.77",
			"2017-07-1aT17:07:17.77"
		};
		String dateFormat03 = "yyyy-MM-dd'T'HH:mm:ss.SSS";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList03.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList03[index] + ", ");
			System.out.println(checkDate(testDateStrList03[index], dateFormat03));
		}		

		System.out.println("■年月日と時刻がスペース区切り");
		String[] testDateStrList04 = {
				"2017-07-17 17:07:17",
				"2017-007-17 17:07:17",
				"2017-07-017 17:07:17",
				"20170-07-17 17:07:17",
				"2017-07-17T 17:07:17",
				"2017-07-1T 17:07:17",
				"201a-07-17 17:07:17",
				"2017-0a-17 17:07:17",
				"2017-07-1a 17:07:17"
		};
		String dateFormat04 = "yyyy-MM-dd HH:mm:ss";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList04.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList04[index] + ", ");
			System.out.println(checkDate(testDateStrList04[index], dateFormat04));
		}	
		
		System.out.println("■ピリオド区切り");
		String[] testDateStrList05 = {
				"2023.01.30",
				"2023.001.30",
				"2023.01.030",
				"20230.01.30",
				"202a.01.30",
				"2023.0a.30",
				"2023.01.3a",
		};
		String dateFormat05 = "yyyy.MM.dd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList05.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList05[index] + ", ");
			System.out.println(checkDate(testDateStrList05[index], dateFormat05));
		}

		System.out.println("■区切りなし年月日");
		String[] testDateStrList06 = {
				"20230130",
				"202300130",
				"202301030",
				"202300130",
				"202a0130",
				"20230a30",
				"2023013a",
		};
		String dateFormat06 = "yyyyMMdd";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList06.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList06[index] + ", ");
			System.out.println(checkDate(testDateStrList06[index], dateFormat06));
		}
		
		System.out.println("■区切りなし年月日時分秒");
		String[] testDateStrList07 = {
				"20230130215500",
				"20230013093300",
				"20230103083028",
				"20230013090005",
				"202a0130113030",
				"20230a30213045",
				"2023013a220000",
		};
		String dateFormat07 = "yyyyMMddHHmmss";
		// 日付チェックの実施と結果を表示
		for (int index = 0; index < testDateStrList07.length; index++) {
			System.out.print(index + ", ");
			System.out.print(testDateStrList07[index] + ", ");
			System.out.println(checkDate(testDateStrList07[index], dateFormat07));
		}
//		System.out.println("■Date and Time API");
//		for (int index = 0; index < testDateStrList.length; index++) {
//			System.out.print(index + ", ");
//			System.out.print(testDateStrList[index] + ", ");
//			System.out.println(isCorrectDate(testDateStrList[index]));
//		}
	}

	private static boolean checkDate(String strDate, String dateFormat) {
		// インプットチェック
		if (Objects.isNull(strDate) || strDate.isEmpty() 
				|| Objects.isNull(dateFormat) || dateFormat.isEmpty()) {
			return false;
		}

// 		// 日付から半角数字のみ抽出した長さ
//		int dateLength = strDate.replaceAll("[^0-9]", "").length();
//		// 日付フォーマットからアルファベット部分のみ抽出した長さ
//		int dateFormatLength = dateFormat.replaceAll("[^a-zA-Z]", "").length(); 
//		// インプットの日付が日付のフォーマットと同じ桁数かと、年月日の値が数字のみかチェック
//		if (strDate.length() != dateFormat.length() 
//				|| dateLength != dateFormatLength) {
//			return false;
//		}

		// 年の長さチェック
//		String year = strDate.substring(0, 5);
//		int correctYearLength = 4;
//
//		// 年月日の区切りはスラッシュかハイフン
//		Pattern pattern = Pattern.compile("[\\-\\/]+");
//		Matcher matcher = pattern.matcher(year);
//		while (matcher.find()) {
//			try {
//				if (matcher.start() != correctYearLength) {
//					return false;
//				}
//			} catch (Exception e) {
//				return false;
//			}			
//		}
		String tmpFormat = dateFormat.replaceAll("'", "");
		
		// ISO 8601対策
		String[] strDateArr = strDate.split("[\\s | T]");
		String[] strDateFromatArr = tmpFormat.split("[\\s | T]");
		
		if (strDateArr.length != strDateFromatArr.length) {
			return false;
		}		
		for (int index = 0; index < strDateArr.length; index++) {
			// 年、月、日、のみチェック
			if (strDateFromatArr[index].toUpperCase().contains("YYYY")
					|| strDateFromatArr[index].contains("MM")
					|| strDateFromatArr[index].toUpperCase().contains("DD")) {

				// 日付の区切り文字
				String splitKey = null;
				if(strDate.contains("-")) {
					splitKey = "-";
				} else if (strDate.contains("/")) {
					splitKey = "/";
				} else if (strDate.contains(".")) {
					splitKey = "\\.";
				}
				if (Objects.nonNull(splitKey)) {
					if(!checkDateValue(strDateFromatArr[index], strDateArr[index], splitKey)) {
						return false;
					}
				} else {
					if (!checkDateValueWithoutDelimiter(strDateFromatArr[index], strDateArr[index])) {
						return false;
					}
				}
			}
		}
//		String year = strDate.substring(0, 4);
//		int correctYearLength = 4;
//		if (!year.matches("^[0-9]+$") || year.length() != correctYearLength) {
//			return false;
//		}
//
//		// 月の桁数チェック
//		String month = strDate.substring(5, 7);
//		int correctMonthLength = 2;
//		if (!month.matches("^[0-9]+$") || month.length() != correctMonthLength) {
//			return false;
//		}
//
//		// 日の桁数チェック
//		String day = strDate.substring(8, 10);
//		int correctDayLength = 2;
//		if (!day.matches("^[0-9]+$") || day.length() != correctDayLength) {
//			return false;
//		}

		// 日付の解析を行う
		DateFormat df = new SimpleDateFormat(dateFormat);
		df.setLenient(false);
		try {
			df.parse(strDate);
		} catch (ParseException e) {
			// 不正な日付
			return false;
		}
		return true;
	}
	
//	private static boolean isCorrectDate(String strDate) {
//		if (Objects.isNull(strDate) || strDate.isEmpty()) {
//			return false;
//		}
//		try {
//			DateTimeFormatter.ofPattern("uuuu/MM/dd")
//			.withResolverStyle(ResolverStyle.STRICT)
//			.parse(strDate, LocalDate::from);
//		} catch (Exception e) {
//			// TODO 自動生成された catch ブロック
//			return false;
//		}
//		return true;
//	}
	
	// 年月日の桁数チェック
	private static boolean checkDateValue(String strDateFormat, String strDate, String splitKey) {
		// インプットのチェック
		if (Objects.isNull(strDateFormat) || strDateFormat.isEmpty()
				|| Objects.isNull(strDate) || strDate.isEmpty()) {
			return false;
		}

		// 年・月・日に分割
		String[] splitDateArr = strDate.split(splitKey);
		String[] splitFormatArr = strDateFormat.split(splitKey);
		
		// インプットの日付と日付フォーマットの桁数チェック
		if (splitDateArr.length != splitFormatArr.length) {
			return false;
		}
		
		// 年・月・日の値のチェック
		for (int index = 0; index < splitDateArr.length; index++) {
			// 桁数チェック
			if (splitDateArr[index].length() != splitFormatArr[index].length()) {
				return false;
			}
			// 数字かチェック
			if (!splitDateArr[index].matches("^[0-9]+$")) {
				return false;
			}
		}
		return true;
	}
	
	// 区切りなし日付のチェック
	private static boolean checkDateValueWithoutDelimiter(String strDateFormat, String strDate) {
		// インプットのチェック
		if (Objects.isNull(strDateFormat) || strDateFormat.isEmpty()
				|| Objects.isNull(strDate) || strDate.isEmpty()) {
			return false;
		}
		
		// 日付パターンの桁数とインプットの日付の桁数のチェック
		if (strDateFormat.length() != strDate.length()) {
			return false;
		}
		
		final char charYear = 'Y';
		final char charMonth = 'M';
		final char charDay = 'D';
		
		int countCharYear = 0;
		int countCharMonth = 0;
		int countCharDay = 0;
		
		// 日付パターンのチェック
		for (int index = 0; index < strDateFormat.length(); index++) {
			// 年のチェック
			if (strDateFormat.toUpperCase().charAt(index) == charYear) {
				countCharYear++;
			}
			// 月のチェック
			if  (strDateFormat.charAt(index) == charMonth) {
				countCharMonth++;
			}
			// 日のチェック
			if (strDateFormat.toUpperCase().charAt(index) == charDay) {
				countCharDay++;
			}
		}
		
		// 年のチェック
		if (countCharYear != 0) {
			int yearPosition = strDateFormat.toUpperCase().indexOf(String.valueOf(charYear));
			String strYear = strDate.substring(yearPosition, yearPosition+countCharYear);
			if (!strYear.matches("^[0-9]{" + countCharYear + "}$")) {
				return false;
			}
		}
		
		// 月のチェック
		if (countCharMonth != 0) {
			int monthPosition = strDateFormat.indexOf(String.valueOf(charMonth));
			String strMonth = strDate.substring(monthPosition, monthPosition+countCharMonth);
			if (!strMonth.matches("(0[1-9]|1[0-2])")) {
				return false;
			}			
		}
		
		// 日のチェック
		if (countCharDay != 0) {
			int dayPosition = strDateFormat.toUpperCase().indexOf(String.valueOf(charDay));
			String strDay = strDate.substring(dayPosition, dayPosition+countCharDay);
			if (!strDay.matches("(0[1-9]|[12][0-9]|3[01])")) {
				return false;
			}
		}
		return true;
	}
}

⇧ 「2023年1月31日」みたいな漢字で区切りられた日付には対応できてないですが...

あと、「yyyyMd」みたいな月日が一桁の日付パターンが対応できてないです...

SimpleDateFormatを使うと不毛な闘いを強いられることになるということですかね...

Java8で導入されたDate and Time API (JSR-310)が使える環境であるなら使った方が良い気がしますね...

とりあえず、日付の区切りはハイフンで統一して欲しいかな...

2023年1月31日(火)追記:↑ ここまで

毎度モヤモヤ感が半端ない...

今回はこのへんで。