원본 글: https://www.baeldung.com/java-string-valid-date
이 글에서는 String 객체가 유효한 날짜 문자열을 포함하고 있는지 확인하는 여러 방법에 대해 소개합니다.
Java8 버전 이전과 이후 그리고 Apache Commons Validator라는 라이브러리를 통한 방법들을 확인해보겠습니다.
1. Date Validation Overview
어떤 애플리케이션이든 특정 비즈니스 로직을 수행하기 전 입력 데이터를 검증하는 과정은 필요합니다.
만약 날짜 관련 데이터가 들어오면 아래와 같은 기준으로 검증이 필요할겁니다.
- MM/DD/YYYY와 같은 유효한 날짜 형식을 포함하고 있는지
- 다양한 입력들이 유효한 범위안에 있는지
- 입력한 데이터들이 달력의 유효한 날짜로 나타내질 수 있는지
정규표현식을 통하여 위 기준에 부합하는 형식을 만들 수 있습니다.
그러나, 다양한 입력과 locale에 부합하는 요건을 정규표현식으로 처리하는 경우 복잡하고 문제를 야기할 수 있습니다.
그렇기에 다른 유연하고 효과적인 날짜 검증 방식을 이야기할 것입니다.
다양한 방법을 제공하기 위한 인터페이스를 하나 정의합니다.
public interface DateValidator {
boolean isValid(String dateStr);
}
2. Validate Using DateFormat
Java에서는 기본으로 제공되는 DateFormat 추상 클래스와 그 구현인 SimpleDateFormat클래스가 있습니다.
DateFormat의 parse() 함수를 통해서 날짜검증을 진행해보겠습니다.
public class DateValidatorUsingDateFormat implements DateValidator {
private String dateformat;
public DateValidatorUsingDateFormat(String dateformat) {
this.dateformat = dateformat;
}
@Override
public boolean isValid(String dateStr) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateformat);
simpleDateFormat.setLenient(false); //비정상적인 날짜가 들어오면 에러 발생
try {
simpleDateFormat.parse(dateStr);
} catch (ParseException e) {
return false;
}
return true;
}
}
DateFormat클래스는 멀티쓰레드 환경에서 안전하지 않기때문에 별도의 인스턴스를 만들어서 사용해야합니다.
@Test
public void using_dateFormat_test() {
DateValidator dateValidator = new DateValidatorUsingDateFormat("MM/dd/yyyy");
Assertions.assertTrue(dateValidator.isValid("02/28/2019"));
Assertions.assertFalse(dateValidator.isValid("02/30/2019"));
}
추가) DateFormat클래스가 멀티쓰레드에서 안전하지 않은 이유는 내부적으로 Calendar객체를 공유하는데 이게 멀티쓰레드에 안전하지 않기 때문입니다. 그렇기에 한 쓰레드에서 parsing하는데 다른 쓰레드에서 format을 바꾸면 의도치않게 다른 결과가 나올 수 있습니다.
이 방법은 보통 Java8버전 이전에서 사용됩니다.
3. Validate Using LocalDate
Java8에서는 좀 더 나은 Date와 Time 유틸함수를 제공합니다. 그중 추가된 것이 LocalDate
클래스이고 이는 시간은 나타내지않고 날짜만 나타냅니다. 이 클래스는 불변성을 지니고 멀티쓰레드 환경에서도 안전하게 동작합니다.
LocalDate는 날짜 검증을 진행하는 2개의 정적 함수를 제공합니다. 두 함수 모두 DateTimeFormatter
를 사용하여 검증합니다.
public static LocalDate parse(CharSequence text)
// parses dates using using DateTimeFormatter.ISO_LOCAL_DATE
public static LocalDate parse(CharSequence text, DateTimeFormatter formatter)
// parses dates using the provided formatter
테스트 진행을 위해 구현체를 만듭니다.
public class DateValidatorUsingLocalDate implements DateValidator {
private DateTimeFormatter dateTimeFormatter;
public DateValidatorUsingLocalDate(DateTimeFormatter dateFormatter) {
this.dateTimeFormatter = dateFormatter;
}
@Override
public boolean isValid(String dateStr) {
try {
LocalDate.parse(dateStr, this.dateTimeFormatter);
} catch (DateTimeParseException e) {
return false;
}
return true;
}
}
멀티쓰레드에서 안전하기 때문에 같은 인스턴스를 다양한 함수에서 사용할 수 있습니다.
@Test
public void using_local_date_test() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.BASIC_ISO_DATE;
DateValidator dateValidator = new DateValidatorUsingLocalDate(dateTimeFormatter);
Assertions.assertTrue(dateValidator.isValid("20190228"));
Assertions.assertFalse(dateValidator.isValid("20190230"));
}
4. Validate Using DateTimeFormatter
이전 섹션에서는 DateTimeFormatter객체를 이용하는 LocalDate클래스를 통해 날짜검증을 진행하였습니다.
DateTimeFormatter객체를 직접 사용하여 날짜 검증을 진행할 수도 있습니다.
DateTimeFormatter는 2가지 단계로 검증을 진행합니다.
- 문자열을 개별적인 날짜와 시간의 필드로 분리합니다. (예 2024-01-01 => 년도: 2024, 월 01, 일 01)
- 분리한 값이 실제 유효한 값인지 검증
2번째 단계에서 유효날짜인지 검증할 때 ResolverStyle이라는 속성을 사용합니다. 이 속성은 3가지 값을 가지는데
- LENIENT : 날짜검증을 허술하게 맞춤 (예: 2024-02-31 => 2024-03-02로 변환)
- SMART: LENIENT보다는 엄격함. 위와같은 예제는 허용되지않음. 윤년은 허용
- STRICT - 좀 더 엄격하게 진행. 2023-02-29같은건 허용되지않음. 2023년에는 윤년이 아님.
테스트 하기 위해서 구현체를 만듭니다.
public class DateValidatorUsingDateTimeFormatter implements DateValidator {
private DateTimeFormatter dateTimeFormatter;
public DateValidatorUsingDateTimeFormatter(DateTimeFormatter dateTimeFormatter){
this.dateTimeFormatter = dateTimeFormatter;
}
@Override
public boolean isValid(String dateStr) {
try {
this.dateTimeFormatter.parse(dateStr);
} catch (DateTimeParseException e) {
return false;
}
return true;
}
}
이제 유닛 테스트를 추가합니다.
@Test
public void using_dateTimeFormatter_test() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd", Locale.US)
.withResolverStyle(ResolverStyle.STRICT);
DateValidator dateValidator = new DateValidatorUsingDateTimeFormatter(dateTimeFormatter);
Assertions.assertTrue(dateValidator.isValid("2019-02-28"));
Assertions.assertFalse(dateValidator.isValid("2019-02-30"));
}
해당 테스트에서는 STRICT정책을 사용하였고, locale도 추가하여 진행하였습니다.
5. Validate Using Apache Commons Validator
Apache Commons 프로젝트에서는 검증에 대한 프레임워크도 제공합니다. 날짜, 시간, 숫자, 동시성, 등등 다양한 검증 유틸을 제공합니다.
이 글에서는 GenericValidator
클래스를 사용합니다.
해당 클래스에서 제공되는 함수 형식은 다음과 같습니다.
public static boolean isDate(String value, Locale locale)
public static boolean isDate(String value, String datePattern, boolean strict)
이를 확인하기 위해 의존성을 추가합니다.
testImplementation 'commons-validator:commons-validator:1.9.0'
이후 테스트를 진행합니다.
@Test
public void apache_common_data_validator_test() {
Assertions.assertTrue(GenericValidator.isDate("2019-02-28", "yyyy-MM-dd", true));
Assertions.assertFalse(GenericValidator.isDate("2019-02-29", "yyyy-MM-dd", true));
}
6. 결론
날짜검증을 위한 여러 방법들을 살펴봤습니다.
일단 멀티쓰레드 환경에서 문제를 야기할 수 있는 DateFormat은 사용하지 않을듯합니다.
나머지는 상황에 맞춰서 사용하지 않을까합니다.
LcoalDate로 사용하는건 가장 간단하고, DateTimeFommater는 좀 더 복잡한 대신 상세한 테스트가 가능합니다.
Apache Commons은 내부 구현이 SimpleDateFormat으로 되어있다보니 LocalDate보다 최신 API를 사용하지 못하고 있습니다.
이를 감안하여 적재적소에 사용하면 될듯합니다.
'Baeldung번역&공부 > Java-string' 카테고리의 다른 글
공백을 지우는 여러 방법들(Remove Whitespace From a String in Java) (0) | 2025.02.07 |
---|---|
첫 문자를 대문자로 바꾸는 방법(Capitalize the First Letter of a String in Java) (0) | 2025.02.06 |
Java에서 문자를 숫자로 치환하는 여러 방법(Check If a String Is Numeric in Java) (0) | 2025.02.05 |
문자열 분리방법(Split a String in Java) (0) | 2025.02.02 |
문자열 비어있는지 확인법(Checking for Empty or Blank Strings in Java) (0) | 2025.02.02 |