본문으로 건너뛰기

10.1 날짜와 시간 소개 (Date & Time Introduction)

안내: 이 문서는 Java 21 버전을 기준으로 작성되었습니다.

자바로 "오늘 날짜가 뭐야?", "이 두 날짜 사이에 며칠이야?", "결제한 지 30일이 지났어?" 같은 질문에 답하려면 날짜와 시간을 다루는 전용 클래스를 알아야 합니다.


1. 자바 날짜/시간 API의 역사

자바 역사에서 날짜와 시간을 처리하는 방법은 크게 세 세대로 나뉩니다.

1세대: java.util.Date (Java 1.0, 1996)

import java.util.Date;

// 현재 날짜/시간 생성
Date now = new Date();
System.out.println(now); // Mon Mar 13 17:30:08 KST 2026

// 특정 날짜를 지정하려면? 1970년 1월 1일 기준 밀리초 단위로...
// 완전히 비직관적이고 사용하기 불편

주요 문제점:

  • 날짜만 다루는 클래스인데 시간 정보도 같이 포함됨
  • setYear(126) → 1900년 기준이라 126은 2026년 의미 (매우 혼란)
  • Mutable(변경 가능)해서 멀티스레드 환경에서 위험

2세대: java.util.Calendar (Java 1.1, 1997)

import java.util.Calendar;

Calendar cal = Calendar.getInstance();
System.out.println("현재 연도: " + cal.get(Calendar.YEAR));
System.out.println("현재 월: " + (cal.get(Calendar.MONTH) + 1)); // +1 필수!
System.out.println("현재 일: " + cal.get(Calendar.DAY_OF_MONTH));

여전히 남아있는 문제점:

  • 월(Month)이 0부터 시작(1월 = 0, 12월 = 11 → 버그의 온상!)
  • 여전히 Mutable 해서 공유 시 버그 발생 가능
  • 스레드(Thread) 환경에서 안전하지 않음
  • SimpleDateFormat도 스레드 안전하지 않음
레거시 코드에서 Calendar를 마주쳤다면

기존 시스템의 유지보수가 아닌 신규 개발에서는 Calendar를 절대 사용하지 마세요. java.time 패키지를 항상 사용해야 합니다.

3세대: java.time 패키지 (Java 8+, 2014)

Joda-Time 라이브러리에서 영감을 받아 설계된 새로운 표준 API입니다. 모든 문제를 해결했습니다.

  • 불변(Immutable): 한 번 생성 후 변경 불가 → 스레드 안전
  • 직관적인 API: 월이 1(1월)부터 시작
  • 풍부한 기능: 타임존, 기간 계산, 포맷팅까지 모두 지원

2. java.time 패키지 핵심 클래스

클래스용도포함 정보
LocalDate날짜만연도, 월, 일
LocalTime시간만시, 분, 초, 나노초
LocalDateTime날짜 + 시간연도, 월, 일, 시, 분, 초
ZonedDateTime날짜 + 시간 + 타임존LocalDateTime + ZoneId
Instant타임스탬프1970-01-01T00:00:00Z 기준 경과 시간
Period날짜 간격년, 월, 일
Duration시간 간격시, 분, 초, 나노초
DateTimeFormatter포맷팅날짜/시간 ↔ 문자열 변환

3. 불변(Immutable) 객체로 설계된 이유

java.time 클래스들은 모두 불변 객체입니다. 날짜를 변경하는 메서드를 호출하면 원본은 그대로 두고 새 객체를 반환 합니다.

import java.time.LocalDate;

LocalDate today = LocalDate.of(2026, 3, 23);

// 잘못된 기대: today가 바뀌길 기대
today.plusDays(10); // 이 결과를 받지 않으면 의미 없음!
System.out.println(today); // 2026-03-23 (변하지 않음)

// 올바른 사용: 반환된 새 객체를 사용
LocalDate future = today.plusDays(10);
System.out.println(today); // 2026-03-23 (원본 유지)
System.out.println(future); // 2026-04-02 (새 객체)
불변 객체의 장점
  • 스레드 안전: 여러 스레드가 동시에 읽어도 안전
  • 예측 가능: 값이 몰래 바뀌는 일이 없음
  • 버그 감소: 공유 객체의 의도치 않은 수정 불가

4. 객체 생성: now()와 of()

now(): 현재 날짜/시간

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

// 현재 날짜 (시스템 기본 타임존 기준)
LocalDate today = LocalDate.now();
System.out.println("오늘: " + today); // 2026-03-23

// 현재 시간
LocalTime now = LocalTime.now();
System.out.println("지금 시간: " + now); // 예: 14:30:08.123456789

// 현재 날짜 + 시간
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("날짜 + 시간: " + dateTime); // 2026-03-23T14:30:08.123

// 특정 타임존의 현재 시간
ZonedDateTime seoulTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
ZonedDateTime nyTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("서울: " + seoulTime);
System.out.println("뉴욕: " + nyTime);

of(): 특정 날짜/시간 지정

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;

// 날짜 지정 - 월이 1부터 시작! (Calendar와 달리 직관적)
LocalDate birthday = LocalDate.of(1995, 6, 15);
LocalDate christmas = LocalDate.of(2026, 12, 25);
LocalDate newYear = LocalDate.of(2026, Month.JANUARY, 1); // Month 열거형 사용 가능

System.out.println("생일: " + birthday); // 1995-06-15
System.out.println("크리스마스: " + christmas); // 2026-12-25
System.out.println("새해: " + newYear); // 2026-01-01

// 시간 지정 (시, 분, 초, 나노초)
LocalTime lunchTime = LocalTime.of(12, 30); // 12:30:00
LocalTime precise = LocalTime.of(14, 30, 45, 123000000); // 14:30:45.123

// 날짜 + 시간 지정
LocalDateTime meeting = LocalDateTime.of(2026, 3, 23, 10, 0, 0);
System.out.println("회의: " + meeting); // 2026-03-23T10:00

parse(): 문자열에서 생성

import java.time.LocalDate;
import java.time.LocalDateTime;

// ISO 8601 형식 (기본 형식)
LocalDate d = LocalDate.parse("2026-03-23");
LocalDateTime dt = LocalDateTime.parse("2026-03-23T14:30:00");

System.out.println(d); // 2026-03-23
System.out.println(dt); // 2026-03-23T14:30

5. 날짜 조작: plus, minus, with

import java.time.LocalDate;
import java.time.LocalDateTime;

LocalDate today = LocalDate.of(2026, 3, 23);

// plus: 더하기
System.out.println(today.plusDays(30)); // 2026-04-22
System.out.println(today.plusMonths(3)); // 2026-06-23
System.out.println(today.plusYears(1)); // 2027-03-23
System.out.println(today.plusWeeks(2)); // 2026-04-06

// minus: 빼기
System.out.println(today.minusDays(7)); // 2026-03-16
System.out.println(today.minusMonths(1)); // 2026-02-23
System.out.println(today.minusYears(2)); // 2024-03-23

// with: 특정 필드만 교체
System.out.println(today.withYear(2030)); // 2030-03-23 (연도만 변경)
System.out.println(today.withMonth(12)); // 2026-12-23 (월만 변경)
System.out.println(today.withDayOfMonth(1)); // 2026-03-01 (일만 변경)

// 체인 연결
LocalDate complexDate = today
.plusMonths(6)
.minusDays(5)
.withDayOfMonth(1);
System.out.println("복합 계산: " + complexDate);

6. 날짜 비교: isBefore, isAfter, isEqual

import java.time.LocalDate;

LocalDate date1 = LocalDate.of(2026, 1, 1);
LocalDate date2 = LocalDate.of(2026, 6, 15);
LocalDate date3 = LocalDate.of(2026, 1, 1);

// isBefore: 이전인가?
System.out.println(date1.isBefore(date2)); // true (date1이 date2보다 이전)
System.out.println(date2.isBefore(date1)); // false

// isAfter: 이후인가?
System.out.println(date2.isAfter(date1)); // true (date2가 date1보다 이후)
System.out.println(date1.isAfter(date2)); // false

// isEqual: 같은 날짜인가?
System.out.println(date1.isEqual(date3)); // true
System.out.println(date1.equals(date3)); // true (equals도 날짜값 비교)
System.out.println(date1.compareTo(date2)); // 음수 (date1이 더 이전)

// 실용 예제: 기간 유효성 검사
LocalDate startDate = LocalDate.of(2026, 1, 1);
LocalDate endDate = LocalDate.of(2026, 12, 31);
LocalDate checkDate = LocalDate.now();

if (!checkDate.isBefore(startDate) && !checkDate.isAfter(endDate)) {
System.out.println(checkDate + "은 유효 기간 내입니다.");
} else {
System.out.println(checkDate + "은 유효 기간이 아닙니다.");
}

7. 날짜 필드 조회

import java.time.LocalDate;
import java.time.DayOfWeek;

LocalDate date = LocalDate.of(2026, 3, 23);

System.out.println("연도: " + date.getYear()); // 2026
System.out.println("월(숫자): " + date.getMonthValue());// 3
System.out.println("월(열거형): " + date.getMonth()); // MARCH
System.out.println("일: " + date.getDayOfMonth()); // 23
System.out.println("요일: " + date.getDayOfWeek()); // MONDAY
System.out.println("연중 몇째 날: " + date.getDayOfYear()); // 82

// 요일 한국어 처리
DayOfWeek dow = date.getDayOfWeek();
String[] koreanDays = {"월요일", "화요일", "수요일", "목요일",
"금요일", "토요일", "일요일"};
System.out.println("요일(한국어): " + koreanDays[dow.getValue() - 1]);

// 윤년 확인
System.out.println("2026년 윤년 여부: " + date.isLeapYear()); // false
System.out.println("이달의 날짜 수: " + date.lengthOfMonth()); // 31

8. 실전 예제: 생년월일로 나이 계산 및 다양한 날짜 포맷

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.DayOfWeek;

public class DatePractice {

// 만 나이 계산
public static int calculateAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
Period period = Period.between(birthDate, today);
return period.getYears();
}

// D-Day 계산
public static long calculateDDay(LocalDate targetDate) {
LocalDate today = LocalDate.now();
return java.time.temporal.ChronoUnit.DAYS.between(today, targetDate);
}

// 오늘 날짜 다양한 포맷으로 출력
public static void printTodayFormats() {
LocalDate today = LocalDate.now();
LocalDateTime now = LocalDateTime.now();

System.out.println("기본 형식: " + today);
System.out.println("한국식: " + today.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")));
System.out.println("미국식: " + today.format(DateTimeFormatter.ofPattern("MM/dd/yyyy")));
System.out.println("일본식: " + today.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")));
System.out.println("ISO: " + today.format(DateTimeFormatter.ISO_DATE));

// 요일 포함
DayOfWeek dow = today.getDayOfWeek();
String[] days = {"월", "화", "수", "목", "금", "토", "일"};
System.out.println("요일 포함: " + today.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"))
+ " (" + days[dow.getValue() - 1] + "요일)");

// 날짜 + 시간
System.out.println("날짜+시간: " + now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}

public static void main(String[] args) {
// 나이 계산
LocalDate myBirthday = LocalDate.of(1995, 8, 20);
System.out.println("생년월일: " + myBirthday);
System.out.println("만 나이: " + calculateAge(myBirthday) + "세");
System.out.println();

// D-Day 계산
LocalDate christmas = LocalDate.of(2026, 12, 25);
long dDay = calculateDDay(christmas);
if (dDay > 0) {
System.out.println("크리스마스까지 D-" + dDay);
} else if (dDay == 0) {
System.out.println("오늘이 크리스마스!");
} else {
System.out.println("크리스마스가 " + Math.abs(dDay) + "일 지났습니다.");
}
System.out.println();

// 다양한 날짜 형식
System.out.println("=== 오늘 날짜 다양한 형식 ===");
printTodayFormats();

// 특별한 날짜 체크
LocalDate today = LocalDate.now();
System.out.println("\n오늘은 주말인가? " +
(today.getDayOfWeek() == DayOfWeek.SATURDAY ||
today.getDayOfWeek() == DayOfWeek.SUNDAY ? "예" : "아니오"));
}
}

[실행 결과 예시]

생년월일: 1995-08-20
만 나이: 30세

크리스마스까지 D-276

=== 오늘 날짜 다양한 형식 ===
기본 형식: 2026-03-23
한국식: 2026년 03월 23일
미국식: 03/23/2026
일본식: 2026.03.23
ISO: 2026-03-23
요일 포함: 2026년 03월 23일 (월요일)
날짜+시간: 2026-03-23 14:30:08

오늘은 주말인가? 아니오

마치며

java.time 패키지, 특히 LocalDateLocalDateTime은 자바 실무 전반에 걸쳐서 매우 자주 접하게 될 클래스들입니다.

  • LocalDate: 날짜만 필요할 때 (생일, 공휴일, 만료일 등)
  • LocalTime: 시간만 필요할 때 (영업시간, 알람 등)
  • LocalDateTime: 날짜와 시간 모두 필요할 때 (예약, 이벤트 등)
  • ZonedDateTime: 국제 서비스나 타임존 변환이 필요할 때

다음 챕터에서 날짜 간격 계산(Period, Duration)과 포맷팅(DateTimeFormatter)을 더 깊이 배워봅니다.