[Java]String 타입 날짜 및 시간 비교하기(String -> Date, Date -> String), ParseException 오류

문상우·2023년 4월 18일
0

JAVA

목록 보기
1/1
post-thumbnail

문제발생

예약시스템을 개발하는 중이였다. 따라서 해당 예약이 종료되는 시점을 비교하여 종료 시킬 수 있는지 없는지를 판단하는 if 문을 작성해야 했다. 하지만, 시간을 toString()을 통해 String 타입으로 저장했기에 현재 시간과 비교하기 위해서는 Date 로 변경시키는 과정이 필요했다.. 하지만,, String 에서 Date 로 변경하는 도중 ParseException 오류가 발생했다.

java.text.ParseException

java.text.ParseException: Unparseable date: "Tue Apr 18 18:46:40 KST 2023"

내가 받은 오류 코드이다. 먼저, java.text는 자연 언어와 독립적인 방식으로 텍스트, 날짜, 숫자 및 메시지를 처리하기 위한 클래스 및 인터페이스를 제공한다. 위 패키지는 구성으로 interface, class, enum, exception를 갖고 있고, 대표적인 예로 구현되어 있는 class 는 DecimalFormat, MessageFormat, SimpleDateFormat과 NumberFormat 등이 있다.

위 사진을 보면 알 수 있듯이 이 클래스가 패키지가 뱉는 오류는 ParseException이다. "구문 분석 중에 예기치 않게 오류에 도달했음을 알립니다." 어디서 문제가 생겼을까.. 일단 내 코드부터 분석해본다.

1. new Date()

.approvedTime(new Date(System.currentTimeMillis()).toString())
.expiredTime(new Date(System.currentTimeMillis() + 7200000).toString())

위는 내가 짠 코드이다. Builder 패턴을 이용하여 객체를 생성하고 있는 모습이다. Builder 는 간단하게 말해 순서 없이 생성자를 사용할 수 있는 것을 말한다(여기서 중요한 개념은 아니다). 나는 객체에 1. 예약이 승인된 시간과 2. 예약이 종료되는 시간을 담았다. 이후에는 expiredTime 을 꺼내어 현재 시간과 비교하면 된다(이렇게 생각했다 단순하게).
위에서 System.currentTimeMillis()를 사용하면 가뿐히 long 타입으로 현재 시간을 얻을 수 있지만, DB에 String 타입으로 저장시키기 위해 toString() 메서드를 지원하는 Date를 사용했다.

위 그림처럼 어차피 Date 생성자는 자동으로 System.currentTimeMillis()를 사용하지만, 나는 이 값에 추가로 밀리 초 단위의 시간 값을 줘서 유효시간이 끝나는 시간을 미리 계산하여야 하기에 생략하지 않았다.

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// reservation.getReservedTime() 을 통해 아까 계산하여 저장했던 expiredTime을 불러온다.
Date date = new Date(dateFormatParse
	.parse(reservation.getExpiredTime()).getTime());
// 아까와 코드와 동일하게 현재 시간을 계산해준다.
Date today = new Date(simpleDateFormat
	.parse(new Date(System.currentTimeMillis()).toString()).getTime());
    
if (today.compareTo(date) > 0) {
	return true;
}

위는 내가 처음으로 작성한 비교 코드이다. 그렇다.. 정말 아무것도 모르는 상태로 작성한 코드이다. 단지 인터넷에서 SimpleDateFormat를 사용하면 둘을 비교하여 작성할 수 있다는.. 희망 때문에,, 당연히 오류가 발생했다. 이 문제점은 바로 아래에서 설명한다.

2. SimpleDateFormat

우리는 위 코드의 문제점을 알기 위해서는 SimpleDateFormat을 반드시 알 필요가 있다. 이는 DateFormat을 확장한 클래스로서 날짜 형식을 지정하고 구문 분석하기 위한 구체적인 클래스이다. 서식 지정(날짜 -> 텍스트), 구문 분석(텍스트 -> 날짜) 및 정규화를 허용한다.

즉, 날짜-시간 형식에 대한 사용자 정의 패턴을 선택하여 시작할 수 있다. 우리가 어떤 형식으로 날짜 및 시간을 저장시킬지를 정할 수 있다. 이에 대한 자세한 정보는 공식문서에 있다. 이는 아래에 참조해두었다.

먼저 공식문서에 따르면 SimpleDateFormat 을 생성할 때, 위처럼 String 타입의 패턴을 넣을 수 있다.

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date today = new Date(simpleDateFormat
	.parse(new Date(System.currentTimeMillis()).toString()).getTime());

위 pattern을 하나하나 해석해본다.

  • yyyy (소문자 y 4개) - 년도를 4자리로 출력
  • MM (대문자 M 2개) - 월을 2자리로 출력
  • dd (소문자 d 2개) - 일을 2자리로 출력
  • HH (대문자 H 2개) - 시간을 2자리로 출력
  • mm (소문자 m 2개) - 분을 2자리로 출력
  • ss (소문자 s 2개) - 초를 2자리로 출력

이는 공식문서에 예시를 통해 충분히 나와 있으므로 참고하는데 어렵지 않을 것이다.


그렇다면 위 코드에서 문제가 발생한 이유는 무엇일까 ?

애초에 new Date().toString() 은 내가 위에서 지정한 형식으로 출력되지 않는다. 이는 기본으로 지정되어 있는 형식으로 출력된다. 바로 "Tue Apr 18 18:46:40 KST 2023" 이렇게 말이다. 하지만, 이는 내가 위에서 지정해준 yyyy-MM-dd HH:mm:ss" 패턴과 다르다. 따라서 이 패턴으로 paser() 메서드를 실행하던 도중ParseException 문제가 발생한 것이다.


문제 해결

문제를 알았으니 해결은 금방 할 수 있다. 우리는 위에서 new Date()가 뱉는 형식대로 SimpleDateFormat에 올바른 패턴을 파라미터로 넘겨 인스턴스를 만들고 이를 통해 parse 하면 오류는 끝이다. 즉, String 타입의 패턴만 변경해주면 되는 문제였다. ("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH) 패턴은 이렇게 작성하면 된다. 나도 공식문서에 있는 예시 코드를 보며 작성한 것이다. 이때, 중요한것은 오버로딩 되어있는 생성자중, Locale을 추가로 넣을 수 있는 생성자가 있는데 이를 사용해야만 우리가 원하는 결과를 얻을 수 있다.

아래는 내가 이 모든 것들을 시험해보기 위해 작성한 Test 코드이다.

Example

public class TimeTest {
    public static void main(String[] args) throws ParseException {

        // case : 1
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm");

        String pastCase1 = dateFormat.format(new Date(System.currentTimeMillis() - 7200000));
        String presentCase1 = dateFormat.format(new Date(System.currentTimeMillis() + 7200000));

        timeTest(dateFormat, pastCase1, presentCase1);


        // case : 2
        SimpleDateFormat basicFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);

        String pastCase2 = new Date(System.currentTimeMillis() - 7200000).toString();
        String presentCase2 = new Date(System.currentTimeMillis() + 7200000).toString();

        timeTest(basicFormat, pastCase2, presentCase2);
    }

    private static void timeTest(SimpleDateFormat simpleDateFormat, String time1, String time2) throws ParseException {

        // String 출력
        System.out.println("time1 = " + time1);
        System.out.println("time2 = " + time2);

        // String -> Date 파싱 (동일한 포멧으로 변경)
        Date parsedTime1 = new Date(simpleDateFormat.parse(time1).getTime());
        Date parsedTime2 = new Date(simpleDateFormat.parse(time2).getTime());

        // Date 출력
        System.out.println("parsedTime1 = " + parsedTime1);
        System.out.println("parsedTime2 = " + parsedTime2);

        // 검증
        if (parsedTime1.after(parsedTime2)) {
            System.out.println("parsedTime1 가 parsedTime2 보다 미래이다.");
        } else {
            System.out.println("parsedTime1 가 parsedTime2 보다 과거이다.");
        }

        /*
         - after 함수의 원형
         public boolean after(Date when) {
             return getMillisOf(this) > getMillisOf(when);
         }
         this 즉 메서드를 호출한 주체(parsedTime1)가 
         인자로 넘겨준(parsedTime2) 보다 크나요 ? 라는 문제이다.
         
         답은 ? 당연히 parsedPresent 가 크므로 이는 else 문을 실행할 것이다.
         내가 헷갈려서 작성해봤다..
         */
    }
}

위에서 제공한 테스트 코드를 작성해보면 시간 비교에 대한 문제는 더 이상 어렵지 않을 것이다.

  • case1. 생성 자체를 지정해준 형식으로 만들고 이를 통해 두 값을 비교한다.
  • case2. new Date()를 통해 기본 형식으로 만들어진 pattern을 그대로 만들어 parse() 한 후 두 값을 비교한다.

이곳에서도 마찬가지로 new Date() 안에 System.currentTimeMillis()를 생략해도 되지만, 나는 임의로 밀리초를 변경해야 하기 때문에 작성했다.

결론

아무생각 없이 시간만 비교하면 될 줄 알았지만,, 결국은 공식 문서를 보고서야 원하는 결과에 도달할 수 있었다. 간단한 기능이라도 결국 공식 문서를 봐야 해결책이 나오는 것 같다.

참고로.. 기본 형식 new Date()를 사용할거라면 생성할 때 Locale.ENGLISH를 반드시 해주기를 권장한다.. 아님 한글로 나오는 불상사가..

참고자료

[Oracle] - java.text
https://docs.oracle.com/javase/7/docs/api/java/text/package-summary.html
[Oracle] - System
https://docs.oracle.com/javase/7/docs/api/java/lang/System.html
[Oracle] - Date
https://docs.oracle.com/javase/8/docs/api/java/util/Date.html
[Oracle] - SimpleDateFormat
https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
[JAVA] Date(), System.currentTimeMillis()를 SimpleDateFormat사용하여 원하는 형태로 날짜 출력
https://mynameisleeminee.tistory.com/31
[JAVA] JAVA 시간차이구하기, 시간 비교하기
https://taesikman1.tistory.com/81

profile
평범한 대학생의 코딩일기

0개의 댓글