공부가 필요하다고 느낀 문법을 정리한 글입니다.
현재 A&I 동아리 레포트 관리 프로젝트를 진행하면서, 레포트 마감 시간이 지나면 레포트가 보이지 않도록 처리하는 API를 구현하고 있었습니다.
그런데 데이터베이스의 시간이 UTC 기준으로 저장되어 있어, 이를 서울 시간에 맞게 올바르게 변환하는 작업이 필요했습니다.
이 과정에서 날짜와 시간을 어떤 클래스로 다뤄야 하는지 헷갈리는 부분이 있어,
이번 글에서는 Kotlin에서 자주 사용하는 날짜·시간 관련 클래스들을 정리해보려고 합니다.
Kotlin은 JVM 위에서 동작하기 때문에, Kotlin/JVM 환경에서는 Java의 날짜·시간 API인 java.time 패키지를 그대로 사용할 수 있습니다.
이 패키지는 날짜, 시간, 타임존, 포맷팅 같은 기능을 체계적으로 제공하며, 현재 Java 진영에서 날짜·시간을 다룰 때 표준처럼 사용됩니다. (Kotlin)
LocalDateTime은 날짜와 시간을 함께 표현하는 클래스입니다.
다만 시간대(Time Zone)나 UTC 오프셋 정보는 포함하지 않습니다.
즉, “2025년 3월 30일 15시 30분”처럼 로컬 기준의 날짜와 시간만 표현할 때 적합합니다. (Oracle Docs)
import java.time.LocalDateTime
val localDateTime = LocalDateTime.now()
println(localDateTime)
// 예: 2025-03-30T15:30:45
LocalDateTime은 날짜와 시간을 모두 담고 있지만,
“이 시간이 서울 시간인지, UTC인지” 같은 정보는 포함하지 않는다는 점을 기억해야 합니다. (Oracle Docs)
ZonedDateTime은 LocalDateTime에 시간대 정보까지 포함한 클래스입니다.
예를 들어 Asia/Seoul 같은 타임존을 함께 가지므로,
세계 여러 지역의 시간을 정확하게 다룰 때 유용합니다. (Oracle Docs)
import java.time.ZonedDateTime
import java.time.ZoneId
val zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"))
println(zonedDateTime)
// 예: 2025-03-30T15:30:45+09:00[Asia/Seoul]
사용자에게 보여줄 시간처럼 “어느 지역 기준 시간인지”가 중요한 경우에는
ZonedDateTime이 더 적합합니다. (Oracle Docs)
Instant는 UTC 기준의 한 순간을 나타내는 클래스입니다.
타임존이 붙은 지역 시간이라기보다, 전 세계에서 공통으로 해석할 수 있는 절대 시점을 표현할 때 사용합니다. (Oracle Docs)
import java.time.Instant
val instant = Instant.now()
println(instant)
// 예: 2025-03-30T06:30:45.123Z
데이터베이스 저장 시간이나 서버 내부 기준 시간처럼
“지역 시간”보다 “정확한 시점”이 중요한 경우에는Instant가 잘 어울립니다.
그리고 필요할 때만ZonedDateTime으로 변환해 보여주면 됩니다. 이는Instant가 절대 시점을 표현하고,atZone(ZoneId)로 타임존을 붙여 지역 시간으로 바꿀 수 있다는 점에서 자연스럽게 이어지는 사용 방식입니다. (Oracle Docs)
Clock은 현재 시각을 얻기 위한 기준 객체입니다.
Oracle 문서에서도 Clock은 현재 instant와 날짜·시간에 접근하기 위한 클래스라고 설명하며, systemUTC()나 systemDefaultZone() 같은 팩토리 메서드를 제공합니다. 테스트에서 시간을 고정하거나 주입할 때도 자주 사용됩니다. (Oracle Docs)
import java.time.Clock
import java.time.Instant
val clock = Clock.systemUTC()
val instant = Instant.now(clock)
println(instant)
// 예: 2025-03-30T06:30:45Z
시간 관련 로직을 테스트해야 한다면
Clock을 직접 주입하는 방식이 유용합니다.
그러면now()를 코드 곳곳에서 직접 호출하는 것보다 테스트 가능한 구조를 만들기 쉬워집니다. (Oracle Docs)
LocalDate는 시간 정보 없이 날짜만 표현하는 클래스입니다.
예를 들어 생일, 마감일, 출석일처럼 날짜만 중요할 때 사용하기 좋습니다.
이 클래스는 시간이나 타임존을 저장하지 않습니다. (Oracle Docs)
import java.time.LocalDate
val today = LocalDate.now()
println(today)
// 예: 2025-03-30
val tomorrow = today.plusDays(1)
val yesterday = today.minusDays(1)
val nextMonth = today.plusMonths(1)
LocalTime은 반대로 날짜 없이 시간만 표현하는 클래스입니다.
예를 들어 “오후 3시 회의”, “매일 23시 마감”처럼 시간만 중요할 때 사용합니다.
이 클래스도 날짜나 타임존은 포함하지 않습니다. (Oracle Docs)
import java.time.LocalTime
val now = LocalTime.now()
println(now)
// 예: 15:30:45.123
val later = now.plusHours(2)
val earlier = now.minusMinutes(30)
DateTimeFormatter는 날짜·시간을 문자열로 출력하거나, 문자열을 날짜·시간으로 파싱할 때 사용하는 클래스입니다.
공식 문서에서도 이 클래스는 날짜·시간을 출력하고 파싱하는 주요 진입점이라고 설명하며, ofPattern()으로 원하는 패턴의 포매터를 만들 수 있다고 안내합니다. 또한 포매터는 immutable하고 thread-safe합니다. (Oracle Docs)
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
val formatted = now.format(formatter)
println(formatted)
// 예: 2025/03/30 15:30:45
| 패턴 | 의미 |
|---|---|
yyyy | 4자리 연도 |
MM | 2자리 월 |
dd | 2자리 일 |
HH | 24시간제 시간 |
mm | 분 |
ss | 초 |
SSS | 밀리초 |
a | AM / PM |
EEE | 짧은 요일 |
EEEE | 긴 요일 |
z | 시간대 이름 |
Z | +0900 같은 오프셋 |
X | ISO-8601 오프셋 (+09 등) |
포맷 패턴은
ofPattern()에 넣어 사용하며,
출력 형식뿐 아니라 문자열 파싱에도 같은 개념이 적용됩니다. (Oracle Docs)
날짜와 시간은 isBefore()와 isAfter() 같은 메서드로 비교할 수 있습니다.
즉, 어떤 날짜가 다른 날짜보다 이전인지, 이후인지를 코드로 명확하게 표현할 수 있습니다. (Oracle Docs)
import java.time.LocalDate
val date1 = LocalDate.of(2025, 3, 30)
val date2 = LocalDate.of(2099, 3, 30)
println(date1.isBefore(date2)) // true
println(date1.isAfter(date2)) // false
이번 글의 시작처럼, 데이터베이스에 시간이 UTC 기준으로 저장되어 있다면
보통은 그 값을 Instant로 다루고, 사용자에게 보여주기 직전에 Asia/Seoul 타임존을 적용하는 방식이 가장 이해하기 쉽습니다. Instant는 절대 시점을 나타내고, atZone(ZoneId)를 통해 해당 시점을 원하는 지역 시간으로 해석할 수 있습니다. (Oracle Docs)
import java.time.Instant
import java.time.ZoneId
val utcTime = Instant.parse("2025-03-30T06:30:45Z")
val seoulTime = utcTime.atZone(ZoneId.of("Asia/Seoul"))
println(seoulTime)
// 예: 2025-03-30T15:30:45+09:00[Asia/Seoul]
즉, 저장은 UTC 기준으로 하고,
응답이나 화면 출력 시점에만 서울 시간으로 변환하는 방식이 실무에서 자주 쓰이는 흐름입니다. 이는 절대 시점과 지역 시간을 분리해 다루는java.timeAPI 구조와도 잘 맞습니다. (Oracle Docs)
이번 글에서는 Kotlin/JVM에서 사용하는 날짜·시간 클래스들을 정리해보았습니다.
핵심은 비슷해 보이는 클래스라도 역할이 다르다는 점입니다.
LocalDateTime : 날짜와 시간은 있지만 타임존은 없음ZonedDateTime : 날짜, 시간, 타임존까지 포함Instant : UTC 기준의 절대 시점LocalDate : 날짜만LocalTime : 시간만DateTimeFormatter : 날짜·시간 포맷팅과 파싱이번처럼 데이터베이스 시간이 UTC로 저장되어 있다면,
무조건 문자열만 바꾸기보다 어떤 클래스가 “절대 시점”을 나타내는지,
어떤 클래스가 “지역 시간”을 나타내는지 구분해서 사용하는 것이 중요합니다.
요구사항에 맞는 클래스를 선택하면 날짜와 시간 처리도 훨씬 덜 헷갈리게 작성할 수 있습니다.
java.time 패키지와 LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Clock, DateTimeFormatter 설명을 참고했습니다. (Oracle Docs)