
개요
프로젝트를 시작할 때 엔티티의 날짜 타입으로 뭘 선언할지 고민한 적이 있을 것입니다.
주변 코드나 레퍼런스를 보면 LocalDateTime, ZonedDateTime을 쓰는 경우가 대부분입니다. 그런데 Instant를 알고 난 뒤 한 가지 의문이 생겼습니다.
"LocalDateTime이 정말 맞는 선택인가?" 본 글에서는 두 타입의 차이와 어떤 상황에서 무엇을 선택해야 하는지 정리합니다.
LocalDateTime이란❓
LocalDateTime now = LocalDateTime.now();
LocalDateTime은 날짜와 시간 정보를 가지고 있지만 타임존(TimeZone) 정보는 없는 타입입니다. 이름에 Local이 붙은 이유가 여기 있습니다.
"내가 있는 곳의 시간" 을 표현하는 타입이지만, 그 "내가 있는 곳" 이 어디인지는 타입 자체가 알지 못합니다.
LocalDateTime = 벽시계 (타임존 정보 없음, 서버 환경에 종속)
LocalDateTime.now()
// 서버 타임존이 Asia/Seoul 이면
LocalDateTime.now(); // → 2026-03-14T15:00:00
// 서버 타임존이 UTC 이면
LocalDateTime.now(); // → 2026-03-14T06:00:00
LocalDateTime.now()는 내부적으로 JVM이 실행되는 서버의 타임존을 기준으로 시각을 만듭니다. 즉, 서버 환경에 따라 같은 코드가 다른 값을 만들어냅니다.
LocalDate & LocalTime
LocalDate date = LocalDate.now(); // 날짜만: 2026-03-14
LocalTime time = LocalTime.now(); // 시간만: 15:00:00
LocalDateTime dateTime = LocalDateTime.of(date, time); // 날짜 + 시간 조합
Instant란❓
Instant now = Instant.now();
// 출력: 2026-03-14T06:00:00Z
// Z는 UTC를 의미합니다.
Instant는 UTC(협정 세계시) 기준의 절대 시각을 나타내는 타입입니다.
내부적으로 1970년 1월 1일 00:00:00 UTC(Unix Epoch)로부터 경과한 시간을 나노초 단위로 저장합니다.
// 서버 타임존이 Asia/Seoul 이든 UTC 이든
Instant.now(); // → 2024-03-14T06:00:00Z (항상 UTC 기준
LocalDateTime과 달리 서버 타임존에 영향받지 않습니다. 서울 서버에서 찍든, UTC 서버에서 찍든 같은 시점이면 같은 값입니다.
Instant = 절대시각 (UTC 기준, 서버 환경에 무관)
ZonedDateTime이란❓
ZonedDateTime은 날짜 + 시간 + 타임존(ZoneId) 을 모두 가지고 있는 타입입니다. 쉽게 말하면 LocalDateTime에 타임존 정보가 추가된 형태입니다.
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
위 코드의 경우 다음과 같은 출력이 나오게 됩니다.
- 2026-03-14T15:00:00+09:00[Asia/Seoul]
- 시간 + 오프셋(+09:00) + 타임존(Asia/Seoul) 모두 포함
즉 Instant와 ZonedDateTime 은 같은 시점을 다르게 표현하는 것입니다.
그럼 ZonedDateTime을 저장용으로 쓰면 안될까❓
쓸 수는 있지만 Instant보다 복잡합니다.
Instant → UTC 기준 단순 저장, 조회 시 원하는 타임존으로 변환
ZonedDateTime → 타임존 정보까지 저장, DB 매핑 시 추가 고려사항 발생
저장용으로는 Instant가 더 단순하고 명확합니다. ZonedDateTime은 특정 타임존 기준으로 사용자에게 보여줄 때 사용하는 것이 적합합니다.
entity.setCreatedAt(Instant.now());
// 한국 사용자에게 표시
ZonedDateTime seoul = entity.getCreatedAt()
.atZone(ZoneId.of("Asia/Seoul"));
// 미국 사용자에게 표시
ZonedDateTime la = entity.getCreatedAt()
.atZone(ZoneId.of("America/Los_Angeles"));
잘못 선택하면 생기는 일
1. LocalDateTime
@Column
private LocalDateTime createdAt;
만약 서버 타임존이 바뀐다면 어떻게 될까요?
예를 들어 기존에 국내 온프레미스 서버(Asia/Seoul, UTC+9)에서 운영하다가 Docker 또는 AWS EC2로 이전하면서 서버 타임존이 UTC로 변경됐다면 다음과 같은 문제가 발생합니다.
LocalDateTime.now(); // → 2026-03-14T15:00:00
// 이후: 서버 타임존 UTC로 변경
LocalDateTime.now(); // → 2026-03-14T06:00:0
DB에 저장된 기존 데이터는 그대로 15:00:00입니다.
그런데 타임존 변경 이후 저장된 데이터는 06:00:00으로 들어갑니다. 같은 시점인데 9시간 차이가 생겨버립니다. 더 문제인 건 이게 배포 직후 바로 못 잡는다는 점입니다.
애플리케이션은 정상 동작하고, 에러 로그도 없습니다. 데이터가 어느 정도 쌓이고 나서야 "어? 시간이 이상한데?" 하고 뒤늦게 발견하게 됩니다.
2. Instant
// 서버 타임존이 Asia/Seoul 이든 UTC 이든
Instant.now(); // → 2026-03-14T06:00:00Z (항상 UTC 기준)
반면 Instant는 UTC 기준 절대시각으로 저장하기 때문에 서버 타임존이 바뀌어도 저장되는 값이 달라지지 않습니다.
언제 뭘 써야 하는가❓
Instant
- created_at, updated_at 같은 서버 기록용 시각
- 서버 환경이 바뀔 가능성이 있을 때 (온프레미스 → 클라우드 이전 등)
- 글로벌 서비스, 멀티 타임존 환경
- 두 시각 사이의 간격을 계산할 때
LocalDateTime
- 사용자에게 보여주는 시각 (타임존 변환 후 표시용)
- 비즈니스적으로 날짜 자체가 의미 있을 때
- 예) 예약일, 생년월일, 공시 기준일
- 타임존이 절대 바뀌지 않는 환경 (국내 전용, 고정 온프레미스)
Instant로 저장하고 표시할 때 변환하는 패턴
AWS EC2, ECS, Docker 컨테이너의 기본 타임존은 UTC입니다. 국내 서비스라도 클라우드 인프라를 사용하는 순간 아래 상황이 발생할 수 있습니다
서버 타임존: UTC
사용자 타임존: Asia/Seoul (UTC+9)
이 경우 LocalDateTime으로 저장하면 이전에 설명한 9시간 문제가 그대로 발생합니다. Instant로 저장하고, 사용자에게 보여줄 때 타임존을 적용해서 변환하는 패턴이 필요합니다.
// 저장 (UTC 기준 절대시각으로 저장)
entity.setCreatedAt(Instant.now());
// 한국 사용자에게 표시
LocalDateTime display = entity.getCreatedAt()
.atZone(ZoneId.of("Asia/Seoul"))
.toLocalDateTime();
// → 2026-03-14T15:00:00
'Language > Java' 카테고리의 다른 글
| [Java] 비트 연산(bit)과 비트마스크(bit mask) 알아보기 (0) | 2024.11.20 |
|---|---|
| [JAVA] Exception vs RuntimeException: 커스텀 예외에서 RuntimeException을 선택하는 이유 (0) | 2024.09.23 |
| [JAVA] Lombok이란 무엇일까❓(lombok 설치 방법, lombok사용법) (0) | 2024.07.20 |
| [JAVA] 람다 표현식을 활용한 정렬 기법: 오름차순과 내림차순 (1차원, 2차원) (0) | 2024.07.10 |
| [JAVA] 좌표와 2차원 배열 매핑의 이해 (좌표 ➡️ 배열) (0) | 2024.07.01 |