소개
Java 애플리케이션에서 날짜 및 시간 데이터를 다룰 때, 개발자는 종종 java.time.format.DateTimeParseException 예외를 마주하게 됩니다. 이 예외는 애플리케이션이 문자열을 날짜 또는 시간 객체로 파싱하려고 시도하지만, 문자열 형식이 예상 패턴과 일치하지 않거나 유효하지 않은 값을 포함할 때 발생합니다.
이 Lab 에서는 DateTimeParseException의 원인을 식별하고, 이를 해결하기 위한 효과적인 솔루션을 구현하며, Java 애플리케이션에서 안정적인 날짜 및 시간 파싱을 위한 모범 사례를 채택하는 방법을 배우게 됩니다.
DateTimeParseException 이해하기
java.time.format.DateTimeParseException는 Java 애플리케이션에서 날짜 및 시간 문자열을 파싱할 때 발생하는 일반적인 런타임 예외입니다. 이 예외를 효과적으로 처리하려면, 무엇이 원인인지 그리고 어떻게 식별하는지 이해해야 합니다.
DateTimeParseException 의 원인
DateTimeParseException은 일반적으로 다음 이유 중 하나로 발생합니다.
- 형식 불일치 (Format mismatch): 입력 문자열 형식이
DateTimeFormatter에 지정된 패턴과 일치하지 않습니다. - 잘못된 날짜/시간 값 (Invalid date/time values): 입력 문자열에 유효하지 않은 날짜 또는 시간을 나타내는 값 (예: 2 월 30 일) 이 포함되어 있습니다.
- 누락되거나 추가된 요소 (Missing or extra elements): 입력 문자열에 필수 요소가 누락되었거나 예상치 못한 추가 요소가 포함되어 있을 수 있습니다.
전형적인 DateTimeParseException 시나리오를 보여주기 위해 간단한 Java 프로그램을 만들어 보겠습니다. 먼저, WebIDE 를 열고 새 Java 파일을 만듭니다.
- 왼쪽 사이드바에서 파일 탐색기 섹션으로 이동합니다.
/home/labex/project폴더를 마우스 오른쪽 버튼으로 클릭합니다.- "New File"을 선택하고 이름을
ParseExceptionDemo.java로 지정합니다.
이제 다음 코드를 복사하여 파일에 붙여넣습니다.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public class ParseExceptionDemo {
public static void main(String[] args) {
// Example 1: Format mismatch
String dateStr1 = "2023/05/15";
try {
// Trying to parse with incorrect format (expects yyyy-MM-dd)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(dateStr1, formatter);
System.out.println("Parsed date: " + date);
} catch (DateTimeParseException e) {
System.out.println("Example 1 - Format mismatch error:");
System.out.println("Exception: " + e.getClass().getName());
System.out.println("Message: " + e.getMessage());
System.out.println("Error position: " + e.getErrorIndex());
}
System.out.println("\n----------------------------------------\n");
// Example 2: Invalid date value
String dateStr2 = "2023-02-30";
try {
// February 30th is an invalid date
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(dateStr2, formatter);
System.out.println("Parsed date: " + date);
} catch (DateTimeParseException e) {
System.out.println("Example 2 - Invalid date error:");
System.out.println("Exception: " + e.getClass().getName());
System.out.println("Message: " + e.getMessage());
System.out.println("Input string: " + e.getParsedString());
}
}
}
이 프로그램을 컴파일하고 실행하려면 WebIDE 에서 터미널을 열고 다음 명령을 실행합니다.
cd ~/project
javac ParseExceptionDemo.java
java ParseExceptionDemo
다음과 유사한 출력을 볼 수 있습니다.
Example 1 - Format mismatch error:
Exception: java.time.format.DateTimeParseException
Message: Text '2023/05/15' could not be parsed at index 4
Error position: 4
----------------------------------------
Example 2 - Invalid date error:
Exception: java.time.format.DateTimeParseException
Message: Text '2023-02-30' could not be parsed: Invalid date 'February 30'
Input string: 2023-02-30
DateTimeParseException 분석
예외 정보를 분석해 보겠습니다.
첫 번째 예제에서, 슬래시 (
/) 가 있는 날짜 (2023/05/15) 를 하이픈 (yyyy-MM-dd) 을 예상하는 포맷터로 파싱하려고 시도했기 때문에 예외가 발생합니다.error index 4는 파싱이 실패한 지점인 첫 번째 슬래시 문자를 가리킵니다.두 번째 예제에서, 2 월 30 일은 어떤 해에도 유효한 날짜가 아니기 때문에 예외가 발생합니다. 형식은 일치하지만 실제 날짜 값은 유효하지 않습니다.
DateTimeParseException은 파싱 문제를 진단하는 데 도움이 되는 유용한 정보를 제공합니다.
- 오류 메시지는 무엇이 잘못되었는지 설명합니다.
getParsedString()메서드는 파싱할 수 없었던 입력 문자열을 반환합니다.getErrorIndex()메서드는 파싱이 실패한 위치를 반환합니다.
이러한 세부 사항을 이해하는 것은 애플리케이션에서 날짜 파싱 문제를 효과적으로 디버깅하고 해결하는 데 필수적입니다.
형식 불일치 문제 해결
DateTimeParseException의 가장 흔한 원인 중 하나는 입력 문자열 형식과 DateTimeFormatter가 예상하는 형식 간의 불일치입니다. 이 단계에서는 이러한 유형의 문제를 해결하는 방법을 배우겠습니다.
올바른 형식 패턴 사용
DateTimeFormatter를 사용할 때는 입력 문자열의 실제 형식과 일치하는 패턴을 지정하는 것이 중요합니다. Java 는 날짜와 시간이 정확히 어떻게 형식화되는지 정의할 수 있는 유연한 패턴 구문을 제공합니다.
형식 불일치 문제를 해결하는 방법을 보여주기 위해 새 파일을 만들어 보겠습니다.
- WebIDE 에서
/home/labex/project폴더를 마우스 오른쪽 버튼으로 클릭합니다. - "New File"을 선택하고 이름을
FormatMismatchSolution.java로 지정합니다. - 다음 코드를 복사하여 붙여넣습니다.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public class FormatMismatchSolution {
public static void main(String[] args) {
// Different date string formats
String[] dateStrings = {
"2023/05/15", // format: yyyy/MM/dd
"15-05-2023", // format: dd-MM-yyyy
"May 15, 2023" // format: MMMM d, yyyy
};
for (String dateStr : dateStrings) {
parseWithCorrectFormatter(dateStr);
System.out.println("----------------------------------------");
}
}
private static void parseWithCorrectFormatter(String dateStr) {
System.out.println("Trying to parse: " + dateStr);
// Try with yyyy/MM/dd pattern
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("Success using 'yyyy/MM/dd': " + date);
return;
} catch (DateTimeParseException e) {
System.out.println("Failed with 'yyyy/MM/dd' pattern");
}
// Try with dd-MM-yyyy pattern
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("Success using 'dd-MM-yyyy': " + date);
return;
} catch (DateTimeParseException e) {
System.out.println("Failed with 'dd-MM-yyyy' pattern");
}
// Try with MMMM d, yyyy pattern
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy");
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("Success using 'MMMM d, yyyy': " + date);
return;
} catch (DateTimeParseException e) {
System.out.println("Failed with 'MMMM d, yyyy' pattern");
}
System.out.println("Could not parse date string with any of the available formatters");
}
}
이 프로그램을 컴파일하고 실행합니다.
cd ~/project
javac FormatMismatchSolution.java
java FormatMismatchSolution
다음과 유사한 출력을 볼 수 있습니다.
Trying to parse: 2023/05/15
Success using 'yyyy/MM/dd': 2023-05-15
----------------------------------------
Trying to parse: 15-05-2023
Failed with 'yyyy/MM/dd' pattern
Success using 'dd-MM-yyyy': 2023-05-15
----------------------------------------
Trying to parse: May 15, 2023
Failed with 'yyyy/MM/dd' pattern
Failed with 'dd-MM-yyyy' pattern
Success using 'MMMM d, yyyy': 2023-05-15
----------------------------------------
DateTimeFormatter 패턴 이해
DateTimeFormatter는 날짜 또는 시간의 다른 부분을 나타내기 위해 패턴 문자를 사용합니다. 다음은 가장 일반적인 패턴 문자 중 일부입니다.
y: 연도 (예:yyyy는 2023)M: 월 (예:MM은 05,MMM은 May,MMMM은 May)d: 일 (예:dd는 15)H: 시 (0-23)(예:HH는 14)m: 분 (예:mm은 30)s: 초 (예:ss는 45)
이러한 패턴 문자를 슬래시, 하이픈, 공백 및 쉼표와 같은 리터럴과 결합하여 날짜 문자열의 정확한 형식과 일치시킬 수 있습니다.
유연한 날짜 파서 만들기
이제 여러 형식을 자동으로 처리할 수 있는 더 유연한 날짜 파서를 만들어 보겠습니다.
- WebIDE 에서
FlexibleDateParser.java라는 새 파일을 만듭니다. - 다음 코드를 복사하여 붙여넣습니다.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.List;
public class FlexibleDateParser {
// List of common date formats
private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("yyyy/MM/dd"),
DateTimeFormatter.ofPattern("MM/dd/yyyy"),
DateTimeFormatter.ofPattern("dd-MM-yyyy"),
DateTimeFormatter.ofPattern("d MMM yyyy"),
DateTimeFormatter.ofPattern("MMMM d, yyyy")
);
public static void main(String[] args) {
// Test with different date formats
String[] dates = {
"2023-05-15",
"2023/05/15",
"05/15/2023",
"15-05-2023",
"15 May 2023",
"May 15, 2023"
};
for (String dateStr : dates) {
try {
LocalDate date = parseDate(dateStr);
System.out.println("Successfully parsed '" + dateStr + "' to: " + date);
} catch (IllegalArgumentException e) {
System.out.println("Failed to parse '" + dateStr + "': " + e.getMessage());
}
}
}
/**
* Tries to parse a date string using multiple formatters
* @param dateStr the date string to parse
* @return the parsed LocalDate
* @throws IllegalArgumentException if the string cannot be parsed with any formatter
*/
public static LocalDate parseDate(String dateStr) {
if (dateStr == null || dateStr.trim().isEmpty()) {
throw new IllegalArgumentException("Date string cannot be null or empty");
}
for (DateTimeFormatter formatter : FORMATTERS) {
try {
// Try to parse with this formatter
return LocalDate.parse(dateStr, formatter);
} catch (DateTimeParseException e) {
// This formatter didn't work, continue to the next one
continue;
}
}
// If we get here, none of the formatters worked
throw new IllegalArgumentException("Cannot parse date: " + dateStr +
". Supported formats: yyyy-MM-dd, yyyy/MM/dd, " +
"MM/dd/yyyy, dd-MM-yyyy, d MMM yyyy, MMMM d, yyyy");
}
}
이 프로그램을 컴파일하고 실행합니다.
cd ~/project
javac FlexibleDateParser.java
java FlexibleDateParser
다음과 같은 출력을 볼 수 있습니다.
Successfully parsed '2023-05-15' to: 2023-05-15
Successfully parsed '2023/05/15' to: 2023-05-15
Successfully parsed '05/15/2023' to: 2023-05-15
Successfully parsed '15-05-2023' to: 2023-05-15
Successfully parsed '15 May 2023' to: 2023-05-15
Successfully parsed 'May 15, 2023' to: 2023-05-15
이 유연한 날짜 파서는 여러 날짜 형식을 처리할 수 있으므로 다양한 소스에서 날짜 입력을 처리할 때 애플리케이션의 견고성을 높입니다. 작동하는 형식을 찾거나 모든 가능성을 소진할 때까지 각 포맷터를 순서대로 시도합니다.
알 수 없거나 가변적인 형식으로 날짜를 파싱해야 할 때는 이 방식을 사용하십시오.
잘못된 날짜 값 처리
DateTimeParseException의 또 다른 일반적인 원인은 입력 문자열에 2 월 30 일 또는 9 월 31 일과 같이 논리적으로 유효하지 않은 날짜 값이 포함된 경우입니다. 이 단계에서는 이러한 경우를 효과적으로 처리하는 방법을 살펴보겠습니다.
잘못된 날짜의 문제점
날짜 문자열의 형식이 지정된 패턴과 일치하더라도 날짜 자체가 유효하지 않으면 파싱이 실패합니다. 이는 Java 의 날짜 - 시간 API 가 유효한 날짜만 생성할 수 있도록 유효성 검사를 수행하기 때문입니다.
잘못된 날짜 처리를 실험하기 위해 새 파일을 만들어 보겠습니다.
- WebIDE 에서
/home/labex/project폴더를 마우스 오른쪽 버튼으로 클릭합니다. - "New File"을 선택하고 이름을
InvalidDateHandler.java로 지정합니다. - 다음 코드를 복사하여 붙여넣습니다.
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public class InvalidDateHandler {
public static void main(String[] args) {
// Array of date strings - some valid, some invalid
String[] dateStrings = {
"2023-04-30", // Valid - April has 30 days
"2023-04-31", // Invalid - April has only 30 days
"2023-02-28", // Valid - February 2023 has 28 days
"2023-02-29", // Invalid - 2023 is not a leap year
"2024-02-29", // Valid - 2024 is a leap year
"2023-13-01" // Invalid - Month 13 doesn't exist
};
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println("Testing dates with simple parsing:");
for (String dateStr : dateStrings) {
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("Valid date: " + dateStr + " => " + date);
} catch (DateTimeParseException e) {
System.out.println("Invalid date: " + dateStr + " => " + e.getMessage());
}
}
System.out.println("\nTesting dates with manual validation:");
for (String dateStr : dateStrings) {
if (isValidDate(dateStr)) {
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
System.out.println("Valid date: " + dateStr + " => " + date);
} catch (DateTimeParseException e) {
// This should not happen if isValidDate returns true
System.out.println("Unexpected error for " + dateStr + ": " + e.getMessage());
}
} else {
System.out.println("Invalid date detected: " + dateStr);
}
}
}
/**
* Validates a date string by checking if it represents a valid date.
*
* @param dateStr the date string in yyyy-MM-dd format
* @return true if the date is valid, false otherwise
*/
public static boolean isValidDate(String dateStr) {
try {
// Split the date string into components
String[] parts = dateStr.split("-");
if (parts.length != 3) {
return false;
}
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
int day = Integer.parseInt(parts[2]);
// Check basic ranges
if (year < 1 || month < 1 || month > 12 || day < 1 || day > 31) {
return false;
}
// Validate the date by trying to create it
LocalDate.of(year, month, day);
return true;
} catch (DateTimeException | NumberFormatException e) {
// DateTimeException is thrown for invalid dates
// NumberFormatException is thrown if parts aren't numbers
return false;
}
}
}
이 프로그램을 컴파일하고 실행합니다.
cd ~/project
javac InvalidDateHandler.java
java InvalidDateHandler
다음과 유사한 출력을 볼 수 있습니다.
Testing dates with simple parsing:
Valid date: 2023-04-30 => 2023-04-30
Invalid date: 2023-04-31 => Text '2023-04-31' could not be parsed: Invalid date 'April 31'
Valid date: 2023-02-28 => 2023-02-28
Invalid date: 2023-02-29 => Text '2023-02-29' could not be parsed: Invalid date 'February 29' as '2023' is not a leap year
Valid date: 2024-02-29 => 2024-02-29
Invalid date: 2023-13-01 => Text '2023-13-01' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 13
Testing dates with manual validation:
Valid date: 2023-04-30 => 2023-04-30
Invalid date detected: 2023-04-31
Valid date: 2023-02-28 => 2023-02-28
Invalid date detected: 2023-02-29
Valid date: 2024-02-29 => 2024-02-29
Invalid date detected: 2023-13-01
강력한 날짜 파서 구현
이제 형식 불일치와 잘못된 날짜를 모두 적절하게 처리할 수 있는 더 강력한 날짜 파서를 만들어 보겠습니다. 이전 단계의 유연한 형식 처리와 잘못된 날짜 유효성 검사를 결합합니다.
- WebIDE 에서
RobustDateParser.java라는 새 파일을 만듭니다. - 다음 코드를 복사하여 붙여넣습니다.
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class RobustDateParser {
// List of common date formats
private static final List<DateTimeFormatter> FORMATTERS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("yyyy/MM/dd"),
DateTimeFormatter.ofPattern("MM/dd/yyyy"),
DateTimeFormatter.ofPattern("dd-MM-yyyy"),
DateTimeFormatter.ofPattern("d MMM yyyy"),
DateTimeFormatter.ofPattern("MMMM d, yyyy")
);
public static void main(String[] args) {
// Test with various dates, including invalid ones
List<String> testDates = Arrays.asList(
"2023-05-15", // valid, standard format
"2023/05/15", // valid, with slashes
"05/15/2023", // valid, US format
"15-05-2023", // valid, European format
"15 May 2023", // valid, with month name
"May 15, 2023", // valid, with month name first
"2023-02-29", // invalid: 2023 is not a leap year
"2023-04-31", // invalid: April has 30 days
"2023-13-01", // invalid: month 13 doesn't exist
"random text", // invalid format
"" // empty string
);
for (String dateStr : testDates) {
Optional<LocalDate> result = parseDate(dateStr);
if (result.isPresent()) {
System.out.println("Successfully parsed '" + dateStr + "' to: " + result.get());
} else {
System.out.println("Could not parse '" + dateStr + "' - Invalid or unsupported format");
}
}
}
/**
* Attempts to parse a date string using multiple formatters.
* Returns an Optional containing the parsed date if successful,
* or an empty Optional if parsing fails with all formatters.
*
* @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) {
// Guard against null or empty input
if (dateStr == null || dateStr.trim().isEmpty()) {
return Optional.empty();
}
List<String> errors = new ArrayList<>();
// Try each formatter
for (DateTimeFormatter formatter : FORMATTERS) {
try {
LocalDate date = LocalDate.parse(dateStr, formatter);
return Optional.of(date);
} catch (DateTimeParseException e) {
errors.add("Failed with pattern " + formatter.toString() + ": " + e.getMessage());
}
}
// If we get here, all formatters failed
System.out.println("Debug - All formatters failed for '" + dateStr + "'");
for (String error : errors) {
System.out.println(" " + error);
}
return Optional.empty();
}
}
이 프로그램을 컴파일하고 실행합니다.
cd ~/project
javac RobustDateParser.java
java RobustDateParser
출력은 어떤 날짜 문자열을 성공적으로 파싱할 수 있고 어떤 날짜 문자열이 오류를 일으키는지 보여줍니다.
Successfully parsed '2023-05-15' to: 2023-05-15
Successfully parsed '2023/05/15' to: 2023-05-15
Successfully parsed '05/15/2023' to: 2023-05-15
Successfully parsed '15-05-2023' to: 2023-05-15
Successfully parsed '15 May 2023' to: 2023-05-15
Successfully parsed 'May 15, 2023' to: 2023-05-15
Debug - All formatters failed for '2023-02-29'
Failed with pattern ... Text '2023-02-29' could not be parsed: Invalid date 'February 29' as '2023' is not a leap year
...
Could not parse '2023-02-29' - Invalid or unsupported format
Debug - All formatters failed for '2023-04-31'
...
Could not parse '2023-04-31' - Invalid or unsupported format
Debug - All formatters failed for '2023-13-01'
...
Could not parse '2023-13-01' - Invalid or unsupported format
Debug - All formatters failed for 'random text'
...
Could not parse 'random text' - Invalid or unsupported format
Could not parse '' - Invalid or unsupported format
잘못된 날짜 처리를 위한 주요 기술
이 솔루션은 강력한 날짜 파싱을 위한 몇 가지 중요한 기술을 사용합니다.
- 다중 형식 지원: 다양한 입력 형식을 처리하기 위해 다른 포맷터로 파싱을 시도합니다.
- Optional 반환 유형: 파싱이 실패할 때 명확하게 나타내기 위해 Java 의
Optional을 사용합니다. - 자세한 오류 로깅: 디버깅을 위해 오류 정보를 수집합니다.
- 입력 유효성 검사: 파싱을 시도하기 전에 null 또는 빈 입력을 확인합니다.
- 정상적인 실패: 예외를 발생시키는 대신 빈 Optional 을 반환합니다.
이러한 기술을 구현하면 잘못된 날짜가 포함될 수 있는 사용자 입력 또는 외부 시스템의 데이터와 관련된 경우에도 애플리케이션에서 날짜 파싱을 보다 강력하게 처리할 수 있습니다.
날짜 및 시간 파싱 모범 사례 구현
이 마지막 단계에서는 날짜와 시간 값을 모두 처리하기 위한 업계 모범 사례를 구현하는 포괄적인 날짜 - 시간 파서를 만들 것입니다. 또한 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)
월 이름 및 오전/오후 표시기에 대해 일관된 파싱 동작을 보장하려면 항상 로케일을 지정하십시오.
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()));
}
}
복잡한 파싱 작업을 더 간단한 구성 요소로 나누면 더 나은 결과를 얻을 수 있습니다.
이러한 모범 사례를 구현하면 광범위한 입력을 정상적으로 처리하고 파싱이 실패할 때 명확한 피드백을 제공하는 강력한 날짜 및 시간 파싱 로직을 만들 수 있습니다.
요약
이 Lab 에서는 Java 애플리케이션에서 java.time.format.DateTimeParseException을 효과적으로 처리하는 방법을 배웠습니다. 다음 사항에 대한 실질적인 경험을 얻었습니다.
DateTimeParseException 이해: 형식 불일치 및 잘못된 날짜 값을 포함하여 이 예외의 일반적인 원인에 대해 배웠습니다.
형식 불일치 문제 해결: 여러 날짜 형식을 처리하고 형식 불일치를 적절하게 처리할 수 있는 유연한 날짜 파서를 구현했습니다.
잘못된 날짜 값 처리: 2 월 30 일 또는 4 월 31 일과 같은 잘못된 날짜 값을 감지하고 처리하기 위한 강력한 솔루션을 만들었습니다.
모범 사례 구현: 업계 모범 사례를 통합하는 포괄적인 날짜 - 시간 파서 유틸리티를 구축했습니다. 여기에는 다음이 포함됩니다.
- STRICT 리졸버 스타일 사용
- 기본 로케일 지정
- Optional 값 반환
- 여러 형식 지원
- 입력 유효성 검사
- 정상적인 오류 처리 구현
- 복잡한 파싱 작업 분해
이러한 방식을 따르면 다양한 소스에서 날짜 및 시간 데이터를 처리할 때 Java 애플리케이션을 더욱 강력하게 만들 수 있으며, 예기치 않은 예외 발생 가능성을 줄이고 사용자에게 더 나은 경험을 제공할 수 있습니다.
효과적인 날짜 및 시간 파싱은 특히 사용자 입력, 데이터 가져오기 또는 외부 시스템과의 통합을 처리하는 많은 애플리케이션에 매우 중요하다는 점을 기억하십시오. 이 Lab 에서 배운 기술은 Java 프로젝트에서 이러한 시나리오를 보다 효과적으로 처리하는 데 도움이 될 것입니다.



