날짜 / 시간 정리

Java 8부터는 java.time 패키지(=JSR-310)가 기본 표준이 되었다. 이 API는 다음과 같은 특징을 갖는다.

  • 불변(Immutable) 객체 중심 설계
  • 명확한 역할 분리(Local / Zone / Instant / Amount)
  • 기존 Date, Calendar의 단점을 개선한 구조

LocalDate / LocalTime / LocalDateTime

가장 기본이 되는 날짜/시간 클래스는 아래 3개이다

LocalDate

  • 날짜(년/월/일)만 다룬다. 2023-11-21

LocalTime

  • 시간(시/분/초/나노초)만 다룬다. 08:20:30.123

LocalDateTime

  • LocalDate + LocalTime을 합친 개념이다. 2023-11-21T08:20:30.123

이름에 Local이 붙는 이유는 타임존(ZoneId)이 없기 때문이다. 즉, “서울 시간”, “런던 시간”같은 국가/지역 기반 시간 계산을 하지 않는다.

LocalDate

import java.time.LocalTime;

public class LocalTimeMain {
    public static void main(String[] args) {
        LocalTime ofTime = LocalTime.of(10, 12, 35);

        // 계산(불변)
        LocalTime plusTime = ofTime.plusSeconds(55);

        System.out.println("원본 ofTime = " + ofTime);       // 10:12:35
        System.out.println("ofTime + 55초 = " + plusTime);    // 10:13:30
    }
}

LocalTime

import java.time.LocalDate;

public class LocalDateMain {
    public static void main(String[] args) {
        LocalDate ofDate = LocalDate.of(2013, 11, 21);

        // 계산(불변) - 원본은 바뀌지 않음
        ofDate.plusDays(10);
        System.out.println("원본 ofDate = " + ofDate); // 2013-11-21

        // 반환값을 받아야 변경된 값 사용 가능
        LocalDate ofDatePlus = ofDate.plusDays(10);
        System.out.println("ofDate + 10일 = " + ofDatePlus); // 2013-12-01
    }
}

LocalDateTime (분리/합체)

LocalDateTime은 실제로 내부에서 LocalDate, LocalTime을 가지고 있다

핵심 기능

  • toLocalDate(), toLocalTime() → 분리
  • LocalDateTime.of(date, time) → 합체
  • plusDays(), plusYears() → 계산(불변)
  • isBefore, isAfter, isEqual → 비교
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class LocalDateTimeMain {
    public static void main(String[] args) {
        LocalDateTime ofDt = LocalDateTime.of(2020, 5, 15, 10, 30, 45);

        LocalDate date = ofDt.toLocalDate();
        LocalTime time = ofDt.toLocalTime();

        LocalDateTime merged = LocalDateTime.of(date, time);

        System.out.println("date = " + date);
        System.out.println("time = " + time);
        System.out.println("merged = " + merged);

        LocalDateTime plus = ofDt.plusDays(10).plusHours(5);
        System.out.println("plus = " + plus);
    }
}

ZoneId / ZonedDateTime / OffsetDateTime

ZoneId

타임존은 ZoneId로 표현한다

import java.time.ZoneId;

public class ZoneIdMain {
    public static void main(String[] args) {
        ZoneId systemZone = ZoneId.systemDefault();
        System.out.println("systemZone = " + systemZone);

        ZoneId seoul = ZoneId.of("Asia/Seoul");
        System.out.println("seoul = " + seoul);
    }
}

ZonedDateTime

ZonedDateTime은 LocalDateTime + ZoneId(+ ZoneOffset)이다. 시간대 기반으로 시간을 다루므로 DST(일광절약시간제) 같은 규칙도 자동 반영된다

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDateTimeMain {
    public static void main(String[] args) {
        LocalDateTime ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 50);

        ZonedDateTime seoulTime = ZonedDateTime.of(ldt, ZoneId.of("Asia/Seoul"));
        System.out.println("seoulTime = " + seoulTime);

        ZonedDateTime utcTime = seoulTime.withZoneSameInstant(ZoneId.of("UTC"));
        System.out.println("utcTime = " + utcTime);
    }
}

withZoneSameInstant()는 “같은 순간(Instant)은 유지하고, 표기되는 지역 시간만 바꾸는 것”이다

OffsetDateTime

OffsetDateTime은 LocalDateTime + ZoneOffset이다. ZoneId(지역 개념)없이 UTC와의 차이(오프셋)만 포함한다. 따라서 DST 규칙 적용이 되지 않는다

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public class OffsetDateTimeMain {
    public static void main(String[] args) {
        LocalDateTime ldt = LocalDateTime.of(2040, 1, 1, 13, 40, 40);

        OffsetDateTime odt = OffsetDateTime.of(ldt, ZoneOffset.of("+01:00"));
        System.out.println("odt = " + odt);
    }
}

Instant (기계 시간 / UTC / Epoch 기반)

Instant는 “사람이 보는 날짜”가 아니라, UTC 기준 ‘한 점(순간)’을 표현하는 클래스이다.

  • 기준: 1970-01-01T00:00:00Z
  • 내부는 초/나노초 단위의 경과 값 기반
import java.time.Instant;

public class InstantMain {
    public static void main(String[] args) {
        Instant now = Instant.now();
        System.out.println("now = " + now);

        Instant epoch = Instant.ofEpochSecond(0);
        System.out.println("epoch = " + epoch);

        Instant later = epoch.plusSeconds(3600);
        System.out.println("later = " + later);

        System.out.println("laterEpochSecond = " + later.getEpochSecond());
    }
}

Instant는 로그/트랜잭션/서버 시간 동기화에 매우 자주 쓰인다

Period vs Duration (기간/간격)

시간은 크게 두 가지 개념이 있다

  • 시점(point in time): LocalDateTime / ZonedDateTime / Instant
  • 간격(amount of time): Period / Duration

Period (연/월/일 기반 기간)

import java.time.LocalDate;
import java.time.Period;

public class PeriodMain {
    public static void main(String[] args) {
        Period period = Period.ofDays(10);

        LocalDate current = LocalDate.of(2040, 1, 1);
        LocalDate plus = current.plus(period);

        System.out.println("current = " + current);
        System.out.println("plus = " + plus);

        LocalDate start = LocalDate.of(2026, 1, 1);
        LocalDate end = LocalDate.of(2026, 4, 5);

        Period between = Period.between(start, end);
        System.out.println("between = " + between);
        System.out.println("기간: " + between.getMonths() + "개월 " + between.getDays() + "일");
    }
}

Duration (시/분/초 기반 간격)

import java.time.Duration;
import java.time.LocalTime;

public class DurationMain {
    public static void main(String[] args) {
        Duration duration = Duration.ofMinutes(40);

        LocalTime base = LocalTime.of(1, 0);
        LocalTime plus = base.plus(duration);

        System.out.println("base = " + base);      // 01:00
        System.out.println("plus = " + plus);      // 01:40

        LocalTime start = LocalTime.of(9, 0);
        LocalTime end = LocalTime.of(10, 10);

        Duration between = Duration.between(start, end);

        System.out.println("seconds = " + between.getSeconds()); // 4200
        System.out.println("근무 시간: " + between.toHours() + "시간 " + between.toMinutesPart() + "분");
        // 근무 시간: 1시간 10분
    }
}

실무 기준 날짜/시간 선택 가이드

LocalDate
  • 생일, 마감일, 예약일처럼 “날짜만” 필요한 경우
LocalDateTime
  • “시스템 내부 로컬 기준” 이벤트 시간 (단, 글로벌 서비스에서는 위험할 수 있음)
ZonedDateTime
  • “사람 기준 시간” + “지역(ZoneId)포함”
  • 예약/캘린더/국가별 시간 변환이 필요한 경우
OffsetDateTime
  • 오프셋만 중요하고 “지역 규칙(DST)”은 필요 없을 때
Instant
  • 로그, 트랜잭션, 서버 간 시간 일관성
  • DB 저장 시 가장 안전한 기준값(UTC)
Period
  • “3개월”, “10일”같은 달력 기반 기간(연월일)
Duration
  • “40분”, “2시간” 같은 시간 기반 간격 (시분초)

출처 – 김영한 님의 강의 중 김영한의 실전 자바 – 중급 1편