"오늘의 문제를, 내일의 기록으로 남깁니다."

막연한 이론보다, 구체적인 코드가 필요할 때. 직접 겪고 해결한 문제들을 기록합니다. 실무에서 부딪히는 진짜 이슈와, 내가 이해한 방식 그대로 정리한 가이드입니다.

웹개발/Java

『Java 날짜 포맷』 SimpleDateFormat 대신 DateTimeFormatter 써야 하는 이유 (코드 포함)

자바를잡아 2025. 7. 8. 22:48
반응형

자바 날짜 포맷 처리, 실무에서 쓰는 가장 깔끔한 방법

자바를 다루다 보면 날짜와 시간을 처리하는 일은 너무나 흔한 일이다. 로그 기록을 남기거나, 데이터를 DB에 저장할 때도 거의 항상 날짜가 들어간다. 그런데 이 날짜 포맷 처리가 간단한 듯 하면서도 꽤 많은 개발자를 힘들게 한다. 특히나 기존 레거시 프로젝트를 유지보수하면서 이런 문제를 자주 겪었는데, 대표적으로 SimpleDateFormat이 스레드 세이프하지 않다는 문제가 있었다.

최근 맡았던 레거시 시스템에서 한 번은 고객들이 신고한 날짜 관련 오류를 살펴보다가 무척 당황스러운 일을 겪었다. 날짜 포맷이 뒤섞여 DB에 엉망진창으로 들어간 경우였는데, 알고 보니 다중 스레드 환경에서 SimpleDateFormat을 공유해서 생긴 문제였다.

그래서 오늘은 자바에서 날짜 포맷을 가장 깔끔하게 처리할 수 있는 방법에 대해서 단계별로 정리해 보려고 한다. 특히 최신 자바에서 권장되는 java.time 패키지의 DateTimeFormatter를 중심으로 실무에서 적용할 수 있는 팁까지 확실히 정리하겠다.

문제 상황: SimpleDateFormat의 한계

SimpleDateFormat이란?

자바에서 날짜 포맷을 처리할 때 전통적으로 많이 사용해왔던 클래스가 바로 SimpleDateFormat이다. 사용법이 쉽고 직관적이라 처음 자바를 배울 때부터 자연스럽게 쓰던 방식이었다.

아래와 같은 형태로 자주 사용한다:


// SimpleDateFormat 예제
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
String formattedDate = sdf.format(date);
System.out.println(formattedDate); // 2025-07-08

SimpleDateFormat의 문제점

겉보기엔 아주 간단한데, 문제는 이 클래스가 스레드 세이프하지 않다는 점이다. 즉, 여러 스레드가 같은 SimpleDateFormat 객체를 동시에 사용할 경우 예기치 못한 결과가 발생할 수 있다.

예를 들어, 아래와 같은 코드를 보면 문제 상황을 쉽게 재현할 수 있다.


// 문제 상황 코드
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
    executor.submit(() -> {
        for (int j = 0; j < 100; j++) {
            try {
                System.out.println(sdf.parse("2025-07-08"));
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    });
}
executor.shutdown();

이 코드가 돌면 정상적인 상황이라면 전부 동일한 날짜로 출력돼야 하지만, 실제로는 무작위로 예외가 발생하거나 심지어 엉뚱한 날짜가 나오는 경우가 있다.

해결 방법: DateTimeFormatter로 전환하기

java.time 패키지의 등장

자바 8부터는 날짜와 시간 처리에 큰 개선이 있었다. 바로 java.time 패키지가 도입되었기 때문이다. 여기서 제공하는 DateTimeFormatter 클래스는 불변(immutable) 객체로 설계되어 스레드 세이프하며, 훨씬 더 직관적이고 효율적이다.

DateTimeFormatter 간단한 예제

가장 간단한 사용법은 다음과 같다:


import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

// 현재 날짜 포맷팅
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = today.format(formatter);

System.out.println(formattedDate); // 2025-07-08

이 코드는 아주 깔끔하며, 여러 스레드에서 동시에 사용해도 아무런 문제가 없다.

단계별 해결 과정: 실무에서 적용하기

1단계: 기존 코드 파악하기

우선 기존 레거시 시스템에서 날짜 처리를 어떻게 하고 있는지 코드를 파악한다. 보통 SimpleDateFormat을 쓰고 있는 부분들을 찾아야 한다.

2단계: DateTimeFormatter로 대체하기

SimpleDateFormat을 DateTimeFormatter로 바꾼다. 보통 포맷 스트링은 그대로 사용할 수 있다. 바뀌는 부분은 객체 생성 방법이다.

예를 들어 다음과 같은 기존 코드가 있다면:


// 기존 코드
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String formattedDate = sdf.format(new Date());

아래처럼 바꿀 수 있다.


// 변경 후 코드
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String formattedDate = LocalDate.now().format(formatter);

3단계: 날짜 파싱 처리하기

날짜를 문자열에서 다시 LocalDate나 LocalDateTime으로 파싱할 때는 다음과 같이 한다.


// 문자열에서 날짜 파싱
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate parsedDate = LocalDate.parse("2025-07-08", formatter);

System.out.println(parsedDate); // 2025-07-08

실무 팁: 자주 쓰는 날짜 포맷 모음

실제 업무에서 자주 쓰이는 날짜 포맷을 정리해보았다. 그대로 복사해서 쓰면 좋다.


DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
DateTimeFormatter formatter4 = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");

마무리: 왜 꼭 DateTimeFormatter여야 하는가?

SimpleDateFormat을 굳이 바꾸지 않아도 되는 상황도 있긴 하지만, 현대적인 개발 환경에서는 꼭 DateTimeFormatter로 전환하길 권장한다. 스레드 세이프 문제뿐 아니라 코드의 명확성, 유지보수성 측면에서도 확실히 이점이 있다.

실제 현업에서 이 전환 작업을 진행하고 나서 날짜 관련 오류가 거의 사라졌으며, 코드 자체도 깔끔하고 직관적이 되었다. 앞으로 새로운 프로젝트를 할 때는 처음부터 DateTimeFormatter를 활용해 날짜 포맷을 처리하는 습관을 들이면 좋을 것이다.

지금 바로 프로젝트에서 날짜 포맷 처리를 한 번 점검해보자. 아마 생각보다 개선할 부분이 꽤 보일 것이다.

반응형