日時パースのベストプラクティスの実装
この最終ステップでは、日付と時刻の両方の値を処理するための業界のベストプラクティスを実装する、包括的な日時パーサーを作成します。また、DateTimeParseExceptionを回避および処理するために、プロの Java 開発者が使用する追加のテクニックについても探ります。
本番環境対応の日時パーサーの作成
これまで学んだすべてのベストプラクティスを組み込んだユーティリティクラスを作成しましょう。
- WebIDE で、
/home/labex/projectフォルダーを右クリックします。
- 「New File」を選択し、
DateTimeParserUtil.javaという名前を付けます。
- 以下のコードをコピーして貼り付けます。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
/**
* Utility class for parsing date and time strings in a robust manner.
*/
public class DateTimeParserUtil {
// Common date formatters with STRICT resolver for validation
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd"),
createFormatter("yyyy/MM/dd"),
createFormatter("MM/dd/yyyy"),
createFormatter("dd-MM-yyyy"),
createFormatter("d MMM yyyy"),
createFormatter("MMMM d, yyyy")
);
// Common time formatters
private static final List<DateTimeFormatter> TIME_FORMATTERS = Arrays.asList(
createFormatter("HH:mm:ss"),
createFormatter("HH:mm"),
createFormatter("h:mm a"),
createFormatter("h:mm:ss a")
);
// Common date-time formatters
private static final List<DateTimeFormatter> DATETIME_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd HH:mm:ss"),
createFormatter("yyyy-MM-dd'T'HH:mm:ss"),
createFormatter("yyyy-MM-dd HH:mm"),
createFormatter("MM/dd/yyyy HH:mm:ss"),
createFormatter("dd-MM-yyyy HH:mm:ss")
);
/**
* Creates a DateTimeFormatter with strict resolver style.
*/
private static DateTimeFormatter createFormatter(String pattern) {
return new DateTimeFormatterBuilder()
.appendPattern(pattern)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter()
.withResolverStyle(ResolverStyle.STRICT)
.withLocale(Locale.US);
}
/**
* Parses a string into a LocalDate.
*
* @param dateStr the date string to parse
* @return an Optional containing the parsed LocalDate, or empty if parsing fails
*/
public static Optional<LocalDate> parseDate(String dateStr) {
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
for (DateTimeFormatter formatter : DATE_FORMATTERS) {
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
// Try next formatter
}
}
return Optional.empty();
}
/**
* Parses a string into a LocalTime.
*
* @param timeStr the time string to parse
* @return an Optional containing the parsed LocalTime, or empty if parsing fails
*/
public static Optional<LocalTime> parseTime(String timeStr) {
if (timeStr == null || timeStr.trim().isEmpty()) {
return Optional.empty();
}
for (DateTimeFormatter formatter : TIME_FORMATTERS) {
try {
LocalTime time = LocalTime.parse(timeStr, formatter);
return Optional.of(time);
} catch (DateTimeParseException e) {
// Try next formatter
}
}
return Optional.empty();
}
/**
* Parses a string into a LocalDateTime.
*
* @param dateTimeStr the date-time string to parse
* @return an Optional containing the parsed LocalDateTime, or empty if parsing fails
*/
public static Optional<LocalDateTime> parseDateTime(String dateTimeStr) {
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
return Optional.empty();
}
// First try with combined date-time formatters
for (DateTimeFormatter formatter : DATETIME_FORMATTERS) {
try {
LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, formatter);
return Optional.of(dateTime);
} catch (DateTimeParseException e) {
// Try next formatter
}
}
// Then try to split into date and time parts
String[] parts = dateTimeStr.split(" ", 2);
if (parts.length == 2) {
Optional<LocalDate> date = parseDate(parts[0]);
Optional<LocalTime> time = parseTime(parts[1]);
if (date.isPresent() && time.isPresent()) {
return Optional.of(LocalDateTime.of(date.get(), time.get()));
}
}
return Optional.empty();
}
/**
* Attempts to parse a string as a date, time, or date-time value.
*
* @param input the string to parse
* @return a string describing the parsed result or an error message
*/
public static String parseAny(String input) {
Optional<LocalDate> date = parseDate(input);
if (date.isPresent()) {
return "Parsed as date: " + date.get();
}
Optional<LocalTime> time = parseTime(input);
if (time.isPresent()) {
return "Parsed as time: " + time.get();
}
Optional<LocalDateTime> dateTime = parseDateTime(input);
if (dateTime.isPresent()) {
return "Parsed as date-time: " + dateTime.get();
}
return "Could not parse: " + input;
}
}
次に、ユーティリティを実演するためのテストクラスを作成しましょう。
- WebIDE で、
DateTimeParsingDemo.javaという名前の新しいファイルを作成します。
- 以下のコードをコピーして貼り付けます。
public class DateTimeParsingDemo {
public static void main(String[] args) {
// Test with various date, time, and date-time strings
String[] inputs = {
// Valid dates
"2023-05-15",
"05/15/2023",
"15 May 2023",
// Valid times
"14:30:00",
"2:30 PM",
"14:30",
// Valid date-times
"2023-05-15 14:30:00",
"2023-05-15T14:30:00",
"05/15/2023 14:30:00",
"15-05-2023 14:30:00",
// Invalid examples
"2023-02-30",
"25:30:00",
"2023-13-01",
"Not a date or time",
""
};
for (String input : inputs) {
String result = DateTimeParserUtil.parseAny(input);
System.out.println("Input: \"" + input + "\" → " + result);
}
}
}
このプログラムをコンパイルして実行します。
cd ~/project
javac DateTimeParserUtil.java DateTimeParsingDemo.java
java DateTimeParsingDemo
次のような出力が表示されるはずです。
Input: "2023-05-15" → Parsed as date: 2023-05-15
Input: "05/15/2023" → Parsed as date: 2023-05-15
Input: "15 May 2023" → Parsed as date: 2023-05-15
Input: "14:30:00" → Parsed as time: 14:30
Input: "2:30 PM" → Parsed as time: 14:30
Input: "14:30" → Parsed as time: 14:30
Input: "2023-05-15 14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "2023-05-15T14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "05/15/2023 14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "15-05-2023 14:30:00" → Parsed as date-time: 2023-05-15T14:30
Input: "2023-02-30" → Could not parse: 2023-02-30
Input: "25:30:00" → Could not parse: 25:30:00
Input: "2023-13-01" → Could not parse: 2023-13-01
Input: "Not a date or time" → Could not parse: Not a date or time
Input: "" → Could not parse:
Java での日時パースのベストプラクティス
ユーティリティクラスに実装されている主要なベストプラクティスを見てみましょう。
1. ResolverStyle.STRICT設定を使用する
.withResolverStyle(ResolverStyle.STRICT)
STRICT リゾルバースタイルは、有効な日付と時刻のみが受け入れられるようにします。これにより、「2 月 31 日」を 3 月 3 日にパースするような問題を回避できます。
2. デフォルトのロケールを指定する
.withLocale(Locale.US)
常にロケールを指定して、特に月名と AM/PM インジケーターについて、一貫したパース動作を確保します。
3. Optional の戻り値の型を使用する
public static Optional<LocalDate> parseDate(String dateStr) {
// ...
return Optional.empty();
}
Optionalを使用すると、パースが失敗する可能性があることを明確に伝え、呼び出し元のコードでこのケースを明示的に処理する必要があります。
4. 複数のフォーマットをサポートする
private static final List<DateTimeFormatter> DATE_FORMATTERS = Arrays.asList(
createFormatter("yyyy-MM-dd"),
createFormatter("yyyy/MM/dd"),
// ...
);
複数のフォーマットをサポートすることで、さまざまな入力ソースを扱う際のパースロジックの堅牢性が向上します。
5. 欠落している時刻フィールドをデフォルトにする
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
欠落している時刻フィールドをデフォルトにすることで、特に異なる日時型間の変換を行う場合に、より柔軟なパースが可能になります。
6. 入力検証
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
不要な例外を回避するために、常にパースを試みる前に、入力を検証します。
7. 正常なエラー処理
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
// Try next formatter
}
例外を適切に処理し、パースが失敗した場合は明確なフィードバックを提供します。
8. スマートな複合パース
// Then try to split into date and time parts
String[] parts = dateTimeStr.split(" ", 2);
if (parts.length == 2) {
Optional<LocalDate> date = parseDate(parts[0]);
Optional<LocalTime> time = parseTime(parts[1]);
if (date.isPresent() && time.isPresent()) {
return Optional.of(LocalDateTime.of(date.get(), time.get()));
}
}
複雑なパースタスクをより単純なコンポーネントに分割すると、多くの場合、より良い結果が得られます。
これらのベストプラクティスを実装することにより、さまざまな入力を適切に処理し、パースが失敗した場合は明確なフィードバックを提供する、堅牢な日付と時刻のパースロジックを作成できます。