[Spring] LocalDateTime 사용법

하원·2024년 8월 14일
post-thumbnail

안녕하세요, 하원입니다.
이번에는 제가 과거부터 현재까지 LocalDateTime을 어떻게 사용해 왔는지 소개해 보겠습니다.

프로젝트를 진행하면서 LocalDateTime의 숨은 용도를 많이 배우게 되면서 꼭 포스팅으로 남겨보고 싶었습니다!


LocalDateTime이란?

  • Java8에서 도입된 Date/Time API(LocalDate, LocalTime, LocalDateTime) 중 LocalDateTime 클래스를 의미합니다.
  • 날짜와 시간을 표현할 수 있는 불변 객체입니다. 불변 객체라는 것은 누군가 임의로 변경이 불가능하다는 의미입니다.

어디에 사용하나요?

public class Poster {
    private Long posterId;
    private String title;
    private String content;
    private LocalDateTime startDate;  // 이벤트 시작 날짜
    private LocalDateTime endDate;  // 이벤트 종료 날짜
}

보통 프로젝트를 진행할 때 위처럼 startDate, endDate과 같이 시간이 필요한 필드의 타입으로 많이 사용합니다.

하지만 저는 과거에 startDate, endDate 필드에 String을 사용했었습니다..!
물론 시간 값이 들어갔지만, '타입을 굳이 LocalDateTime으로 해야 하나?'라는 저의 개인적인 생각으로 String을 사용하였습니다. (하지만 잘못된 생각이었습니다..)


어떻게 사용하나요?

LocalDateTime은 다양하고 유연한 기능을 제공합니다.

1. 현재 날짜 받아오기

        LocalDateTime now = LocalDateTime.now();
        System.out.println("now = " + now);
		2024-08-15T00:29:47.548997300

위와 같이 now() 메서드를 통해 현재 날짜를 받아올 수 있습니다.
그 외에도, getYear(), getMonth(), getDayOfMonth(), getHour(), getMinute(), getSecond() 등 다양한 시간 정보를 확인할 수 있습니다.


그런데 약간 형식이 익숙하지 않습니다.
T는 무엇이고, second 뒤에 긴 숫자는 또 무엇일까요?

LocalDateTime의 기본 날짜 포맷

  • LocalDateTime은 ISO 형식인 yyyy-MM-ddTHH:mm:ss.nnnnnnnnn를 따릅니다.
  • 기본적인 연도, 월, 일, 시간 등이 표시됩니다.
  • 추가적으로 T는 날짜와 시간 사이의 구분자 역할을 하고, nnnnnnnnn은 나노세컨드를 의미합니다. 밀리세컨드와 나노세컨드는 다릅니다.

2. 포맷 변경하기

        String now = LocalDateTime.now()
        				.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("now = " + now);
		2024-08-15 00:49:14

제가 앞에서 말했던 String으로 타입을 선언한 이유입니다.
위 코드를 사용해서 자주 사용되는 yyyy-MM-dd HH:mm:ss 포맷으로 쉽게 변경할 수 있었지만,
String으로 반환되었기 때문에 startDate, endDate 필드들을 String으로 선언할 수밖에 없었습니다.

저 때는 LocalDateTime으로 정렬할 수 있다는 사실을 몰랐기 때문에 모든 시간 관련 필드를 String으로 선언하여 사용했었습니다. 이것도 뭐.. 경험인 거죠..?!


3. 조회 관련 API의 정렬

        // 페이징 조건 추가
        Pageable pageable = PageRequest.of(
                page,
                size,
                Sort.by(
                        Sort.Order.asc("poster.startDate"),  // 첫 번째 정렬 기준: 이벤트 시작 날짜
                        Sort.Order.asc("title")  // 두 번째 정렬 기준: 이벤트 이름
                )
        );

위 코드는 제가 조회 관련 API 구현 과정에서 페이징 조건을 추가할 때 사용한 Pageable 설정 코드 부분입니다.

Sort 부분 코드를 보시면 Sort.Order.asc("poster.startDate") 코드를 추가함으로써, 이벤트 시작 날짜를 기준으로 오름차순 정렬을 진행할 수 있었습니다.

하지만, 이 정렬 처리를 하는 과정에서 제가 이전에 String 타입으로 설정했던 것이 약간의 걸림돌이 되었습니다. String 타입의 필드로도 정렬이 가능하지만, String은 사전 순으로 정렬이 이루어지기 때문에 날짜 형식에 따라 잘못된 정렬 결과가 도출될 수 있었습니다.


String으로 정렬하면 발생할 수 있는 경우

  • 날짜 형식이 불규칙한 경우
{"2024-08-11", "2023-07-01", "2024-06-30", "24-01-13", "2022-12-31"}
정렬 결과 -> "2022-12-31", "2023-07-01", "2024-06-30", "2024-08-11", "24-01-13"

  • 날짜 형식이 yyyy-MM-dd HH:mm:ss가 아닌 경우
{"09-11-2024", "07-05-2021", "06-21-2024"}
정렬 결과 -> "06-21-2024", "07-05-2021", "09-11-2024"

LocalDateTime과 같이 날짜 형식이 지정되지 않은 String 타입을 사용하면 위와 같은 오류를 겪을 수도 있습니다. 물론, String 타입을 사용하더라도 yyyy-MM-dd HH:mm:ss 형식을 선택하여 사용하면 문제는 없습니다!

하지만 날짜 및 시간 전용으로 나온 LocalDateTime과 같은 클래스를 사용하면 이런 고민은 하지 않아도 돼서 다들 지향하는 것 같습니다.

그런데 여기서 또 다른 문제가 발생했습니다.
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")를 사용하지 않으면 포맷은 어떤 방식으로 설정해야 할까요?


현재 제가 사용하는 방식

@Getter
@AllArgsConstructor
public class PosterDto {
    private Long posterId;
    private String title;
    private String content;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime startDate;  // 이벤트 시작일
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime endDate;  // 이벤트 종료일
}

저는 주로 프로젝트에서 API를 구현하기 때문에 JSON 형식을 자주 사용합니다.
저번 글에서 말했듯이 Response에는 엔티티를 전달하면 안 되기 때문에 위 코드처럼 DTO를 생성해서 전달하는데요!

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")startDate, endDate 필드 위에 지정해 주면, 자동으로 yyyy-MM-dd HH:mm:ss 형식에 맞추어 Response를 전달하게 됩니다.

반대로, Request의 경우에도 Request DTO를 생성하여 위 코드처럼 @JsonFormat을 지정해 주면 yyyy-MM-dd HH:mm:ss 형식에 맞는 데이터만 들어오게 됩니다.


유연한 LocalDateTime

LocalDateTime은 날짜와 시간을 모두 표현할 수 있는 클래스입니다.
또한, 다른 기능들과 결합하면서 다양하고 유연한 기능들을 제공합니다.

저는 그저 날짜와 시간만 표현할 수 있는 클래스라고 생각하여, 다양한 기능들을 찾으려고 하지 않았던 것 같습니다. 반성해야겠습니다..!


마무리

예전에 같이 프로젝트를 진행하던 팀원이 왜 LocalDateTime을 사용하지 않고 String을 사용하냐고 물었던 적이 있었다. 나는 그냥 Format을 바꾸려고 어쩔 수 없이 String을 사용했다고 답했다.
그때 찾아봤어야 했는데... 역시 팀원들의 조언에는 조용한 가르침이 숨어져 있는 것 같다.


참고

profile
호기심 저장소

0개의 댓글