기본적으로 애플리케이션을 타임존 설정 없이는 시스템에 설정된 기본 타임존을 따라간다. 내용은 아래와 같다.
JVM은 시작 시 운영 체제에서 설정된 시스템 타임존을 읽어 들여 초기화한다.
- 예를 들어, Linux, macOS, Windows에서 각각 설정된 타임존 (
/etc/localtime
또는 GUI 설정 등)이 JVM의 기본 타임존으로 설정된다. - 이 값은 JVM의 시스템 속성
user.timezone
에 저장된다.
기본 시간대
코드로 확인해 보자.
val defaultTimeZone = TimeZone.getDefault()
println("기본 타임존 (TimeZone): ${defaultTimeZone.id}")
// java.time.ZoneId를 사용한 기본 타임존 확인
val defaultZoneId = ZoneId.systemDefault()
println("기본 타임존 (ZoneId): $defaultZoneId")
지금 필자에 기본 타임존은 아래와 같다.
기본 타임존 (TimeZone): Asia/Seoul
기본 타임존 (ZoneId): Asia/Seoul
보통 대부분의 개발을 하다 보면 한국 시간을 기준대로 설정되어 개발을 시작한다
⇒ 즉, 이 말은 LocalDateTime을 사용하는 것이 아마 대부분일 것이다. 그런데 만약 한국 서비스가 아니라 글로벌 서비스가 된다면 어떻게 될까?
한국 시간만 처리하는 게 아니라면?
이게 이 글에 핵심 내용이 될 것이다. 제목에서 언급하였듯이 만약 동 시간대가 아닌 곳에서 이를 컨트롤해서 보여줘야 하는 서비스라면 어떻게 되고, 그에 대한 책임은 누구에게 있는 것일까?
필자가 생각한 방식은 아래와 같다.
- OffsetDateTime을 활용하여 UTC기준으로 서버에 저장 및 서버에서 내려줄 때 핸들링
⇒ 서버에서 핸들링한다는 의미는 클라이언트 Request에 약속된 지역 값에 따라 내려주는 것이다.
- OffsetDateTime을 활용하여 UTC기준으로 서버에 저장 및 클라이언트에서 핸들링
⇒ 서버에서 핸들링하는 것이 아니라 모든 서버 데이터는 UTC로 통일된다. 즉, 클라이언트에서 toLocal과 같은 함수로 핸들링 하는 것이다.
위에서 나온 것처럼 결국 서버는 오프셋 기반으로 이를 저장해야 한다는 부분이다.
그렇담 데이터를 저장할 때 OffsetDateTime으로 저장하면 어떻게 될까? 예시로 JPA로 간단하게 저장해 보았다.
@Column(name = "created_at", nullable = false)
@CreatedDate
val createdAt = OffsetDateTime.now()
결과는 아래와 같다. 필자의 시간대는 Asia/Seoul이며, OffsetdateTime을 사용하여 저장해 본 값이다.
여기서 확인할 수 있는 건 +09이다. 즉 이는 UTC 시간대 보다 9시간 빠르다는 것이다.

개념 정리
- UTC: 전 세계에서 기준이 되는 시간대입니다. 흔히 "Z 시간"이라고도 불리며, 오프셋은
+00:00
이다. - 오프셋 (Offset): 특정 시간대가 UTC 기준으로 몇 시간 빠르거나 느린지를 나타낸다.
+
기호는 해당 시간대가 UTC보다 빠름을 의미한다.-
기호는 해당 시간대가 UTC보다 느림을 의미한다.
예시
UTC+09:00
(Asia/Seoul, Asia/Tokyo 등)- UTC 기준 시간에 9시간을 더한 시간이 된다.
- 예를 들어, UTC 기준으로 2025년 1월 3일 12:00라면, UTC+09:00 시간대에서는 2025년 1월 3일 21:00이다.
UTC-05:00
(New York, 미국 동부 표준시)- UTC 기준 시간에서 5시간을 뺀 시간이 된다.
- UTC 12:00 기준으로 UTC-05:00에서는 07:00이 된다.
하지만 만약 여러 시간대를 특정 애플리케이션에서 처리하게 된다면 어떻게 해야 할까? 이는 위에서 필자가 스스로 던진 질문과 같다. UTC(협정 세계시)를 기반으로 저장을 먼저 해야 한다. 그다음으로 액션을 취해도 문제가 없다.
혹여나 애플리케이션이 Asia/Seoul 이어도 이는 로직 단에서 특정 시간대로 변경하여 내릴 수 있다.
그럼 애플리케이션이 실행될 때 UTC로 바꾸고 이를 확인해보자
fun main(args: Array<String>) {
runApplication<AllApplication>(*args)
started()
}
@PostConstruct
fun started() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
}
...
기본 타임존 (TimeZone): UTC
기본 타임존 (ZoneId): UTC
현재 날짜와 시간 (오프셋 포함): 2025-01-03T00:47:37.951543Z
오프셋: Z
저장된 데이터는 어떨까?

저장된 데이터 또한, UTC(협정 세계시)를 기반으로 저장된다. 즉, +00 부분을 확인하고 현제 오프셋이 Z임을 알 수 있는 것이다. 그럼 바로 아래 섹션에서 이를 어떻게 하면 좋을지 생각해 보자.
그렇담 누가 이걸 처리할까?
먼저 클라이언트에서는 특정 국가 혹은 지역 정보를 알 수 있다. 그렇담 서버에서는 이에 따라 변환해서 내려야 할까? 아니면 클라이언트에서 변환하도록 해야 할까?
⇒ 사실 정해진 답변은 없다. 팀의 정책 따라서 결정하기 나름이다.
필자가 위처럼 생각한 이유는 크게 두 가지로 다음과 같다.
- 서버에서 DB에 저장되는 값이 UTC면 모든 데이터는 UTC 정보로 통일되어야 하지 않을까?
- 하지만 서버에서 UTC로 저장했다고 해도 변환하는 간단한 Utils 함수 모음들을 활용해서 변환하여 내려줄 수 있지 않을까?
사실 서버에서 UTC로 저장하고 특정 지역 시간대에 따라 클라이언트와 협업된 Request Header 혹은 값으로 이를 판단하여 내릴 수 있다. 이 또한 그렇게 어려운 과정도 아니다. 아래와 같은 함수들도 간단하게 표현해 줄 수 있다.
object DateTimeUtil {
fun utcNow(): OffsetDateTime {
return OffsetDateTime.now(ZoneId.of("UTC"))
}
fun koreaNow(): OffsetDateTime {
return OffsetDateTime.now(ZoneId.of("Asia/Seoul"))
}
fun OffsetDateTime.koreaToUtc(): OffsetDateTime {
return this.withOffsetSameInstant(ZoneOffset.UTC)
}
fun OffsetDateTime.utcToKorea(): OffsetDateTime {
return this.withOffsetSameInstant(ZoneOffset.ofHours(9))
}
}
하지만 이를 여러 지역 시간대가 있다고 가정해 보자. 그렇담 무수히 많은 코드들이 존재할 것이고, 이를 일관적으로 관리하는 서버 애플리케이션에서는 복잡함을 느낄 수 있다. 단순히 Response를 내려주는 게 아니라 요청에 따라 이를 처리해줘야 하기 때문이다.
그렇담 오로지 클라이언트에게 이 책임을 위임할 것인가.라고 묻는 다면 쉽게 답변하지는 못할 것 같다. 여러 지역 시간대가 필요하지 않을 수 있을뿐더러 한국 시간만 처리한다면 큰 문제가 되지 않을 것이기 때문이다.
따라서 서비스하는 지역에 따라 이에 대한 의견은 많이 갈리게 될 것이다. 팀마다 다를 것이고, 이미 특정 Local에 집중적인 데이터가 많다면 마이그레이션 혹은 코드단에서 이걸 처리하는 건 어지간히 번거로울 것이다.
결론적으로 필자의 생각은 각 팀 혹은 서비스에 따라 다를 것이라고 생각한다. 하지만 그래도 필자의 의견은 첫 번째 의견에 조금 더 힘이 실리는 것 같다. 서버는 멱등한 시간대를 내려야 한다고 생각한다. 그래야 더 유연하게 대처할 수 있다고 생각한다. 세계 협정시인 UTC를 기준으로 저장하고 있다면 여러 가지 오프셋을 활용할 수 있다고 느낀다.
이는 결국 확장성과 서비스 지역에 얼마나 대응할 것인가도 중요하고 어떻게 대응할 것이 가도 중요하다. 그래서 답은 없다고 생각하고 더 나은 서비스를 어떻게 하면 더 좋은 방식으로 해결할지 방법을 찾으면 된다. 방법은 많으니까!
'서버 개발(생각과 구현)' 카테고리의 다른 글
단일 프로젝트 구조와 멀티 모듈 구조 (0) | 2025.01.01 |
---|---|
Custom Swagger를 통해 협업, 업무 효율성 동시에 잡기 (0) | 2024.12.27 |
JPA N+1의 의도와 문제 해결 (0) | 2024.12.26 |
외부 API와 트랜잭션의 관계를 조심하자 (0) | 2024.12.23 |
락의 필요성과 실제 사용을 위한 분산 락, 낙관적 락, 비관적 락 탐구 (0) | 2024.12.22 |