LocalDate, LocalTime 및 LocalDateTime: 개요, 날짜 작업

저자: IT Sectr 게시일: 2026-07-13 읽는 시간: 12 분

LocalDate, LocalTime 및 LocalDateTime은 시간대에 구애받지 않는 날짜 및 시간 처리를 제공하는 java.time 패키지의 주요 클래스입니다. Oracle 문서(Java 17, 2024)에 따르면, 이러한 유형은 불변(immutable) 및 스레드 안전(thread-safe)하게 설계되어 멀티스레드 애플리케이션에서 안전하게 사용할 수 있습니다. Android에서는 API 26부터 Desugaring을 통해, 이전 버전에서는 ThreeTenABP 라이브러리를 통해 사용할 수 있습니다.

핵심 요점

  • LocalDate — 시간과 시간대 없이 날짜(년, 월, 일)를 나타내는 불변 클래스.
  • LocalTime — 날짜와 시간대 없이 시간(시, 분, 초, 나노초)을 나타내는 불변 클래스.
  • LocalDateTime — LocalDate와 LocalTime을 결합하여 시간대 바인딩 없이 날짜와 시간을 모두 저장.
  • 세 클래스 모두 산술 연산 지원 — plus 및 minus 메서드를 통해 일, 월, 시간 추가 및 빼기.
  • Android에서는 Desugaring(API 26+) 또는 ThreeTenABP 라이브러리(API < 26)를 통해 사용 가능.

LocalDate, LocalTime 및 LocalDateTime이란?

LocalDate — 시간 및 시간대 정보 없이 년-월-일 형식으로 날짜를 나타내는 클래스입니다. 생일, 이벤트 날짜 또는 만료 날짜와 같은 데이터를 저장하는 데 사용됩니다.

LocalDate는 -999999999에서 +999999999까지의 범위에서 연도, 1에서 12까지의 월, 윤년을 고려한 일을 저장합니다. 클래스는 완전히 불변입니다 — 모든 연산은 새 객체를 반환합니다.

LocalTime은 시간(시, 분, 초, 나노초)을 나타냅니다. 최대 정밀도는 나노초입니다. LocalTime에는 날짜나 시간대 정보가 포함되어 있지 않아 상점 영업 시간이나 프로세스 기간을 저장하는 데 편리합니다.

LocalDateTime은 LocalDate와 LocalTime을 하나의 객체로 결합합니다. 날짜와 시간을 모두 저장해야 하지만 시간대 바인딩이 필요하지 않은 경우 가장 많이 사용되는 유형입니다. 예를 들어 로컬 형식의 콘서트 날짜 및 시간입니다.

Oracle Java 문서(2024)에 따르면, 세 클래스는 모두 Joda-Time 라이브러리의 아이디어를 기반으로 설계되었지만 아키텍처가 개선되고 표준 라이브러리에 완전히 통합되었습니다.

java.time 패키지의 작동 방식

java.time 패키지는 Java 8에서 기존의 Date, Calendar 및 SimpleDateFormat 클래스를 대체하기 위해 도입되었습니다. 아키텍처는 불변 객체 및 fluent 인터페이스 원칙을 기반으로 구축되었습니다.

주요 특징 — 모든 주요 클래스는 값 기반(value-based)입니다. 즉, 인스턴스가 참조가 아닌 값으로 비교되며 상속할 수 없습니다. 두 객체를 비교하려면 == 연산자가 아닌 equals 메서드를 사용합니다.

패키지는 여러 범주로 나뉩니다. 시간대 없는 유형 — LocalDate, LocalTime, LocalDateTime — 로컬 날짜 및 시간에 사용됩니다. 시간대 있는 유형 — ZonedDateTime, OffsetDateTime, OffsetTime — 오프셋 또는 영역 정보를 추가합니다. 순간 유형 — Instant — UTC의 시간선 상의 지점을 나타냅니다.

이러한 구분은 기존 API에 내재된 문제를 해결합니다. 개발자는 Date 객체에 시간대 정보가 포함되어 있는지 알 수 없었습니다. java.time에서는 각 유형이 의미를 명시적으로 선언합니다.

LocalDate: 날짜 작업

LocalDate 클래스는 날짜 생성, 읽기 및 수정을 위한 많은 메서드를 제공합니다. 현재 날짜는 정적 메서드 now()를 통해 얻을 수 있습니다. 특정 날짜는 of(int year, int month, int dayOfMonth) 메서드를 통해 얻을 수 있습니다.

날짜 구성 요소를 읽으려면 getter를 사용합니다: getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(). getMonth() 메서드는 Month 열거형을 반환하고 getDayOfWeek()는 DayOfWeek 열거형을 반환합니다.

LocalDate는 날짜 확인을 지원합니다. isBefore(), isAfter() 및 isEqual() 메서드는 날짜를 비교할 수 있습니다. isLeapYear() 메서드는 윤년인지 확인합니다. lengthOfMonth() 메서드는 월의 일 수를 반환하고 lengthOfYear()는 연도의 일 수를 반환합니다.

수정을 위해 withYear(), withMonth(), withDayOfMonth() 메서드를 사용하며, 변경된 구성 요소로 새 객체를 반환합니다. plusDays(), minusMonths() 및 유사한 메서드는 날짜 산술을 수행합니다.

LocalTime: 시간 작업

LocalTime은 나노초 정밀도로 시간을 나타냅니다. 표준 형식은 ISO-8601(HH:mm:ss.nnnnnnnnn)입니다. 최소값은 00:00, 최대값은 23:59:59.999999999입니다.

LocalTime 객체는 now()(현재 시간), of(int hour, int minute), of(int hour, int minute, int second) 또는 of(int hour, int minute, int second, int nanoOfSecond)로 만들 수 있습니다. parse(CharSequence text) 메서드는 ISO-8601 형식의 문자열을 구문 분석합니다.

getter에는 getHour(), getMinute(), getSecond(), getNano()가 포함됩니다. toSecondOfDay() 메서드는 하루 시작부터의 초 수를 반환하고 toNanoOfDay()는 나노초를 반환합니다. 이는 하루 내의 기간 계산에 편리합니다.

LocalTime은 LocalDate와 동일한 비교 및 수정 연산을 지원합니다: plusHours(), minusMinutes(), withHour(), withMinute(). isBefore()와 isAfter() 메서드는 시간이 하루 내에서 순환함을 고려하여 작동합니다.

LocalDateTime: 날짜와 시간의 결합

LocalDateTime은 LocalDate와 LocalTime의 기능을 하나의 클래스에 결합합니다. 날짜와 시간을 모두 저장하지만 시간대는 없습니다. 가장 유연한 로컬 유형이지만 분산 시스템에서 사용할 때는 주의가 필요합니다.

LocalDateTime은 정적 메서드 now(), of(LocalDate date, LocalTime time), of(int year, Month month, int dayOfMonth, int hour, int minute) 및 오버로드를 통해 만들 수 있습니다. atTime() 메서드를 통해 LocalDate와 LocalTime을 결합할 수도 있습니다.

LocalDateTime은 해당 getter를 통해 모든 날짜 및 시간 필드에 대한 액세스를 제공합니다: toLocalDate()와 toLocalTime()은 개별 구성 요소를 반환합니다. truncatedTo(TemporalUnit unit) 메서드는 시간을 지정된 정밀도(예: 분)로 반올림할 수 있습니다.

시간대로 변환하려면 atZone(ZoneId zone) 메서드를 사용하여 ZonedDateTime을 반환합니다. 이것이 LocalDateTime에 시간대를 추가하는 유일한 방법입니다.

날짜 및 시간 객체 생성 방법

세 클래스 모두 정적 팩토리 메서드를 통해 통합된 생성 패턴을 사용합니다. 클래스 생성자는 private으로 선언됩니다 — new를 사용하여 직접 객체를 만들 수 없습니다.

주요 생성 메서드:

  • now() — 시스템 시계에서 현재 날짜/시간
  • of(...) — 구성 요소(년, 월, 일 등)에서 생성
  • parse(String) — ISO-8601 형식의 문자열에서 생성
  • from(TemporalAccessor) — 다른 시간 객체에서 생성

of 메서드에는 많은 오버로드가 있습니다. LocalDate에는 년, 월, 일이 필요합니다. LocalTime에는 시와 분(선택적으로 초와 나노초)이 필요합니다. LocalDateTime에는 년, 월, 일, 시, 분이 필요합니다. 월은 int(1-12) 또는 Month 열거형으로 전달할 수 있습니다.

kotlin
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")

val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")

val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)

유형 간 변환

java.time 클래스는 서로 간의 편리한 변환을 위해 설계되었습니다. LocalDate는 atTime(LocalTime) 또는 atStartOfDay() 메서드를 통해 LocalDateTime으로 변환할 수 있습니다. LocalTime은 atDate(LocalDate)를 통해 변환할 수 있습니다.

LocalDateTime은 toLocalDate()를 통해 LocalDate로, toLocalTime()을 통해 LocalTime으로 다시 변환할 수 있습니다. ZonedDateTime으로 변환하려면 atZone(ZoneId) 메서드를 사용합니다.

java.util.Date로의 변환(레거시 코드 호환용)은 Instant와 시간대를 통한 중간 단계가 필요합니다. Baeldung(2024)에 따르면 이 작업은 Date.from(instant)를 통해 수행됩니다.

kotlin
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))

val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)

val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()

val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))

형식 지정 및 구문 분석

형식 지정 및 구문 분석에는 DateTimeFormatter 클래스가 사용됩니다. 상수(ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME)를 통해 미리 정의된 형식과 패턴 문자열을 사용한 사용자 지정 형식 생성을 제공합니다.

형식 패턴은 기호를 사용합니다: yyyy — 년, MM — 월(두 자리), dd — 일, HH — 시(0-23), mm — 분, ss — 초. format() 메서드는 날짜-시간 객체에서 또는 DateTimeFormatter를 통해 호출됩니다.

DateTimeFormatter는 정적 메서드 ofLocalizedDate(FormatStyle), ofLocalizedTime(FormatStyle) 및 ofLocalizedDateTime(FormatStyle)을 통한 지역화도 지원합니다. 사용 가능한 스타일은 SHORT, MEDIUM, LONG 및 FULL입니다.

kotlin
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)

val parsed = LocalDate.parse(
    "21.07.2026",
    DateTimeFormatter.ofPattern("dd.MM.yyyy")
)

날짜-시간 객체 비교

세 클래스 모두 Comparable 인터페이스를 구현하여 자연스러운 비교가 가능합니다. compareTo() 메서드는 순서에 따라 음수, 0 또는 양수를 반환합니다. isBefore(), isAfter() 및 isEqual() 메서드는 부울 값을 반환합니다.

LocalDate의 경우 비교는 시간순으로 이루어집니다 — 이른 날짜가 더 작은 것으로 간주됩니다. LocalTime의 경우 — 시간 순서입니다. LocalDateTime의 경우 — 먼저 날짜, 그 다음 시간입니다. 모든 비교는 윤년과 월의 일 수를 올바르게 고려합니다.

기존 API와의 중요한 차이점: LocalDate, LocalTime 및 LocalDateTime의 equals()는 참조가 아닌 값을 비교합니다. 즉, 동일한 필드를 가진 두 객체는 다른 인스턴스라도 동일합니다.

kotlin
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)

if (d1.isBefore(d2)) {
    Log.d("날짜", "d1이 d2보다 이전")
}

val sortedDates = listOf(d2, d1).sorted()

날짜 및 시간 산술

세 클래스 모두 plusminus 메서드를 통한 산술 연산을 지원합니다. LocalDate에는 plusDays(), plusWeeks(), plusMonths(), plusYears() 및 해당 minus 메서드가 있습니다. LocalTime은 plusHours(), plusMinutes(), plusSeconds(), plusNanos()를 지원합니다.

LocalDateTime은 두 유형에서 모든 산술 연산을 상속합니다. LocalDate의 주목할 만한 기능: 월을 추가할 때 결과는 다른 월 길이를 올바르게 처리합니다. 예를 들어 1월 31일 + 1개월 = 2월 28일(윤년에는 29일)입니다.

더 복잡한 연산을 위해 Period 클래스(날짜용)와 Duration 클래스(시간용)가 있습니다. plus(TemporalAmount) 및 minus(TemporalAmount) 메서드는 이러한 객체를 허용합니다.

kotlin
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)

val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)

Kotlin 코드 예제

실용적인 예를 살펴보겠습니다: 작업 교대 추적 애플리케이션입니다. 교대 시간을 계산하고 야간 시간에 해당하는지 확인해야 합니다. 시작 및 종료 시간에는 LocalTime, 날짜에는 LocalDate, 자정을 넘는 교대 계산에는 LocalDateTime을 사용합니다.

kotlin
data class Shift(
    val startTime: LocalTime,
    val endTime: LocalTime,
    val date: LocalDate
) {
    fun isOvernight(): Boolean = endTime.isBefore(startTime)

    fun durationInMinutes(): Long {
        val start = LocalDateTime.of(date, startTime)
        val end = LocalDateTime.of(
            if (isOvernight()) date.plusDays(1) else date,
            endTime
        )
        return Duration.between(start, end).toMinutes()
    }
}

두 번째 예 — 사용자 나이 계산. 생년월일에 LocalDate를 사용하고 생일의 일과 월을 고려하여 현재 날짜와 비교합니다.

kotlin
fun calculateAge(birthDate: LocalDate): Int {
    val today = LocalDate.now()
    val period = Period.between(birthDate, today)
    return period.years
}

세 번째 예 — 알림 작업. LocalDateTime은 미리 알림을 예약하는 데 사용됩니다. 예약된 시간이 도래했는지 확인합니다.

kotlin
data class Reminder(
    val id: Long,
    val scheduledAt: LocalDateTime
) {
    fun isDue(): Boolean =
        LocalDateTime.now().isAfter(scheduledAt)
}

Android 호환성: API 레벨 및 Desugaring

java.time에 대한 내장 지원은 Android API 26(Android 8.0 Oreo)부터 나타났습니다. 이전 Android 버전의 기기에서는 Desugaring — 이전 버전에 새로운 Java API 지원을 추가하는 메커니즘 — 을 사용해야 합니다.

Android Gradle Plugin의 Desugaring은 build.gradle의 compileOptions를 통해 구성됩니다. isCoreLibraryDesugaringEnabled = true를 설정하고 desugar_jdk_libs 라이브러리를 추가하기만 하면 됩니다. 그러면 java.time을 14부터 모든 API 레벨에서 사용할 수 있습니다.

Desugaring을 사용할 수 없는 프로젝트(예: AGP 4.0 미만의 레거시 프로젝트)의 경우 ThreeTenABP 라이브러리가 있습니다 — java.time의 백포트입니다. 동일한 클래스(LocalDate, LocalTime, LocalDateTime)를 제공하지만 패키지는 org.threeten.bp입니다.

groovy
@Suppress("UnstableApiUsage")
android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    "coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}

일반적인 실수와 방지 방법

첫 번째 일반적인 실수 — 시간대를 고려하지 않고 분산 시스템에서 LocalDateTime을 사용하는 것입니다. 서버가 Europe/Moscow에 있고 클라이언트가 Asia/Tokyo에 있으면 LocalDateTime이 다르게 해석됩니다. 해결책: 전역 데이터에는 Instant 또는 ZonedDateTime을 사용하세요.

두 번째 실수 — 잘못된 문자열 구문 분석. 기본적으로 LocalDate.parse()는 ISO-8601 형식(yyyy-MM-dd)을 기대합니다. 문자열이 다른 형식인 경우 명시적으로 DateTimeFormatter를 전달해야 합니다. 또한 잘못된 입력 시 애플리케이션이 충돌하지 않도록 DateTimeParseException을 처리해야 합니다.

세 번째 실수 — null 안전성 무시. LocalDate, LocalTime 및 LocalDateTime은 null이 될 수 있는 객체입니다. Kotlin에서는 명시적 검사 또는 Elvis 연산자와 함께 nullable 유형을 사용하는 것이 좋습니다. Java에서는 메서드를 호출하기 전에 null을 확인하세요.

네 번째 실수 — LocalDateTime과 ZonedDateTime 혼동. LocalDateTime에는 시간대 정보가 없습니다. 절대적인 시간을 전달해야 하는 경우 시간대 유형을 사용하세요. 로컬 시간으로 충분하면 로컬 유형을 사용하세요.

자주 묻는 질문

Java에서 LocalDate와 Date의 차이점은 무엇인가요?

Date는 1970-01-01 UTC 이후의 밀리초 수를 저장하는 반면 LocalDate는 시간대 바인딩 없이 년, 월, 일을 저장합니다. Date는 가변적이고 스레드 안전하지 않지만 LocalDate는 불변이고 스레드 안전합니다. Date는 Java 8부터 사용되지 않습니다.

데이터베이스에서 LocalDateTime을 사용할 수 있나요?

네, LocalDateTime은 SQL 유형 TIMESTAMP WITHOUT TIME ZONE에 잘 매핑됩니다. JPA와 Room은 TypeConverter를 통해 지원합니다. TIMESTAMP WITH TIME ZONE의 경우 ZonedDateTime 또는 OffsetDateTime을 사용하세요.

두 날짜 사이의 일 수를 얻는 방법은?

ChronoUnit.DAYS.between(startDate, endDate)를 사용하세요. 이 메서드는 long 값을 반환합니다 — 일 수 차이입니다. 더 자세한 계산을 위해 Period.between()을 사용하면 년, 월, 일이 포함된 Period를 반환합니다.

밀리초까지 시간 정밀도를 유지해야 하는 경우 어떻게 하나요?

LocalTime은 나노초 정밀도(소수점 9자리)를 지원합니다. 밀리초 정밀도로 충분하면 저장 전에 truncateTo(ChronoUnit.MILLIS)를 사용하세요. 직렬화 중 반올림 문제를 방지할 수 있습니다.

LocalDate.now()가 다른 기기에서 다른 날짜를 반환하는 이유는?

now() 메서드는 기기의 시스템 시계와 기본 시간대를 사용합니다. 기기가 다른 시간대에 있으면 날짜가 다를 수 있습니다. 통일된 타임스탬프를 위해 Instant.now()를 사용하세요. 항상 UTC 시간을 반환합니다.

요약

  • LocalDate — 시간과 시간대 없이 날짜를 위한 불변 클래스. 생일, 마감일, 이벤트 날짜 저장에 사용.
  • LocalTime — 나노초 정밀도의 시간을 위한 불변 클래스. 영업 시간, 프로세스 기간 저장에 적합.
  • LocalDateTime — 시간대 바인딩 없이 날짜와 시간의 결합. 가장 유연한 로컬 유형이지만 분산 시스템에는 부적합.
  • 세 클래스 모두 DateTimeFormatter 기반의 통합 API를 통해 산술, 비교, 형식 지정 및 구문 분석 지원.
  • Android에서 java.time은 API 26부터 내장 지원 또는 이전 버전용 Desugaring을 통해 사용 가능.
  • 전역 타임스탬프 및 시간대 관련 데이터에는 로컬 유형 대신 ZonedDateTime 또는 Instant 사용.
  • 문자열 구문 분석 시 비표준 형식에는 항상 DateTimeFormatter를 전달하고 DateTimeParseException 처리.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기