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 패키지, 특히 LocalDate와 LocalDateTime은 자바 실무 전반에 걸쳐서 매우 자주 접하게 될 클래스들입니다.
LocalDate: 날짜만 필요할 때 (생일, 공휴일, 만료일 등)LocalTime: 시간만 필요할 때 (영업시간, 알람 등)LocalDateTime: 날짜와 시간 모두 필요할 때 (예약, 이벤트 등)ZonedDateTime: 국제 서비스나 타임존 변환이 필요할 때
다음 챕터에서 날짜 간격 계산(Period, Duration)과 포맷팅(DateTimeFormatter)을 더 깊이 배워봅니다.