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시간” 같은 시간 기반 간격 (시분초)