Java시작과 문법

날짜와 시간 — java.time API

`java.time`의 `LocalDate`, `LocalDateTime`, `ZonedDateTime`을 어떤 기준으로 고르는지, 불변 설계가 날짜 연산을 어떻게 안전하게 만드는지 정리합니다.

마지막 수정 2026년 3월 27일

기본 패턴

java
// 오늘 날짜
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, 3, 15);

// 날짜 연산 — 새 객체 반환 (불변)
LocalDate nextWeek = today.plusDays(7);
LocalDate lastMonth = today.minusMonths(1);

// 포매팅
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formatted = today.format(fmt); // "2026-03-27"

설명

LocalDate / LocalDateTime / ZonedDateTime — 선택 기준

java.time 패키지는 사용 목적에 따라 세 가지 주요 타입을 제공합니다.

LocalDate는 시간대 없이 날짜만 표현합니다. 생일, 예약 날짜처럼 "어느 시간대에서도 같은 날짜"를 나타낼 때 씁니다. LocalDateTime은 날짜 + 시간이지만 여전히 시간대 정보가 없습니다. 로컬 이벤트 시각처럼 시간대가 중요하지 않을 때 씁니다. ZonedDateTime은 시간대까지 포함합니다. 서버 로그, 국제 일정, API 통신처럼 같은 순간이 시간대에 따라 다르게 표시되는 상황에 씁니다.

java
LocalDate date = LocalDate.of(2026, 3, 27);           // 날짜만
LocalDateTime dateTime = LocalDateTime.of(2026, 3, 27, 9, 30); // 날짜 + 시각
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); // 시간대 포함

// Instant — 절대 시각 (유닉스 타임스탬프 기반)
Instant now = Instant.now(); // 시간대 무관한 순간

불변 설계 — 날짜 연산이 안전한 이유

java.time의 모든 타입은 불변입니다. 날짜를 수정하는 메서드는 기존 객체를 바꾸지 않고 항상 새 객체를 반환합니다. 이 덕분에 날짜를 여러 곳에서 공유해도 누군가가 수정해서 다른 코드에 영향을 주는 일이 없습니다. (구버전 java.util.DateCalendar는 가변이어서 이런 버그가 잦았습니다.)

java
LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end = start.plusMonths(3); // 새 객체 반환
// start는 여전히 2026-01-01

// 날짜 비교
System.out.println(start.isBefore(end));  // true
System.out.println(end.isAfter(start));   // true

// 두 날짜 간 차이 — 날짜 단위
long days = ChronoUnit.DAYS.between(start, end); // 90
Period period = Period.between(start, end);       // P3M (3개월)

// 두 시각 간 차이 — 시간 단위 (Duration)
LocalDateTime from = LocalDateTime.of(2026, 3, 27, 9, 0);
LocalDateTime to   = LocalDateTime.of(2026, 3, 27, 11, 30);
Duration duration = Duration.between(from, to);
long minutes = duration.toMinutes(); // 150
long seconds = duration.toSeconds(); // 9000
// Period: 날짜 기반 (년/월/일), Duration: 시간 기반 (시/분/초/나노초)

DateTimeFormatter — 파싱과 포매팅

DateTimeFormatter는 날짜를 문자열로 변환하거나 문자열을 날짜로 파싱할 때 씁니다. 스레드 안전하므로 상수로 만들어 재사용할 수 있습니다. (구버전 SimpleDateFormat은 스레드 안전하지 않아 공유 사용이 위험했습니다.)

java
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");

// 날짜 → 문자열
String s = LocalDate.of(2026, 3, 27).format(fmt); // "2026년 03월 27일"

// 문자열 → 날짜
LocalDate d = LocalDate.parse("2026년 03월 27일", fmt);

// ISO 8601 포맷은 상수로 제공됨
String iso = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE); // "2026-03-27"

// ZonedDateTime → String (API 응답 등)
String timestamp = ZonedDateTime.now(ZoneId.of("UTC"))
    .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);

빠른 정리

상황선택
날짜만 (생일, 예약일)LocalDate
날짜 + 시각 (시간대 불필요)LocalDateTime
시간대가 중요한 일정, 로그ZonedDateTime
절대 시각 (타임스탬프)Instant
두 날짜 간 차이 (일 단위)ChronoUnit.DAYS.between()
두 날짜 간 차이 (년/월/일)Period.between()
두 시각 간 차이 (시간/분/초)Duration.between()

주의할 점

LocalDate.parse()는 기본적으로 ISO 8601 형식(yyyy-MM-dd)만 파싱합니다. 다른 형식의 문자열을 파싱하려면 반드시 DateTimeFormatter를 명시해야 하며, 형식이 맞지 않으면 DateTimeParseException이 발생합니다.

java
// ❌ 기본 파싱 — ISO 8601 형식 아니면 예외
LocalDate.parse("27/03/2026"); // DateTimeParseException

// ✅ 포매터 명시
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date = LocalDate.parse("27/03/2026", fmt); // 정상

// ⚠ 구버전 Date와 혼용 주의 — API 호환이 필요하면 변환
Date legacy = Date.from(LocalDate.now()
    .atStartOfDay(ZoneId.systemDefault()).toInstant());

참고 링크

2 sources