날짜 조회·조작·서식
형식이 맞지 않는 날짜 파싱 실패를 재현하고 편의 메서드·TemporalField·with·TemporalAdjusters·DateTimeFormatter를 안전하게 사용합니다.
java.time 값은 불변이므로 조회 메서드는 필드를 읽고 조작 메서드는 새 값을 반환합니다. 문자열 경계에서는 DateTimeFormatter가 문자 위치와 의미를 정의합니다. 파싱 형식과 출력 형식을 분리하고, 지원하지 않는 필드·잘못된 날짜·반환값 누락을 경계에서 처리해야 합니다.
날짜 서식과 파싱 실패
입력은 점으로 연·월·일을 나누지만 formatter는 하이픈을 기대합니다. LocalDate는 어디까지 읽었는지 포함한 DateTimeParseException을 던집니다.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public final class DatePatternMismatchFailure {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse("2025.03.10", formatter);
System.out.println(date);
}
}Exception in thread "main" java.time.format.DateTimeParseException사용자 입력 양식과 formatter를 같은 경계에 두고 예시를 제공합니다. 여러 입력 형식을 허용해야 하면 무작정 문자열 replace를 하기보다 허용 formatter 목록을 순서대로 시도하고 모호한 날짜를 거부합니다.
편의 조회와 동적 필드 조회
고정된 업무 코드에서는 getYear, getMonth, getDayOfWeek가 의도를 잘 드러냅니다. 범용 표 작성기처럼 필드가 데이터로 전달될 때는 ChronoField를 사용합니다.
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
public final class DateTimeFieldQuery {
public static void main(String[] args) {
LocalDateTime value = LocalDateTime.of(2025, 3, 10, 14, 25, 30);
System.out.println("year=" + value.getYear());
System.out.println("day-of-week=" + value.getDayOfWeek());
System.out.println("hour=" + value.get(ChronoField.HOUR_OF_DAY));
System.out.println("day-range=" + value.range(ChronoField.DAY_OF_MONTH));
System.out.println("offset-supported=" + value.isSupported(ChronoField.OFFSET_SECONDS));
}
}year=2025
day-of-week=MONDAY
hour=14
day-range=1 - 31
offset-supported=falseLocalDateTime에는 오프셋이 없으므로 OFFSET_SECONDS를 지원하지 않습니다.
get을 바로 호출하기보다 isSupported를 확인하거나 ZonedDateTime으로 정보 모델을 바꿉니다.
타입에 없는 정보를 기본값 0으로 꾸며 내지 않습니다.
plus와 with의 차이
plus는 현재 값에서 상대적인 양을 더하고, with는 특정 필드를 절대값으로 교체합니다. 둘 다 원본을 유지합니다.
import java.time.LocalDateTime;
import java.time.Period;
public final class DateTimeManipulation {
public static void main(String[] args) {
LocalDateTime original = LocalDateTime.of(2025, 1, 31, 9, 0);
LocalDateTime plusMonth = original.plusMonths(1);
LocalDateTime nextYear = original.plus(Period.ofYears(1));
LocalDateTime changedHour = original.withHour(18);
System.out.println("original=" + original);
System.out.println("plus-month=" + plusMonth);
System.out.println("next-year=" + nextYear);
System.out.println("changed-hour=" + changedHour);
}
}original=2025-01-31T09:00
plus-month=2025-02-28T09:00
next-year=2026-01-31T09:00
changed-hour=2025-01-31T18:001월 31일에 한 달을 더하면 대상 월의 유효한 마지막 날로 조정됩니다. “30일 뒤”가 필요하면 plusDays(30)이고 “다음 달 같은 청구일”은 별도 청구 정책이 필요합니다. 메서드 이름만 비슷하다고 업무 의미가 같지 않습니다.
달력 규칙과 TemporalAdjusters
마지막 날, 다음 월요일, 이번 달 마지막 금요일 같은 규칙은 직접 반복 계산하지 않고 표준 adjuster를 사용할 수 있습니다.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public final class StudyDateAdjusters {
public static void main(String[] args) {
LocalDate base = LocalDate.of(2025, 3, 10);
LocalDate endOfMonth = base.with(TemporalAdjusters.lastDayOfMonth());
LocalDate nextMonday = base.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
LocalDate lastFriday = base.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
System.out.println("end=" + endOfMonth);
System.out.println("next-monday=" + nextMonday);
System.out.println("last-friday=" + lastFriday);
}
}end=2025-03-31
next-monday=2025-03-17
last-friday=2025-03-28next는 기준일이 이미 월요일이어도 다음 주 월요일을 반환하고, nextOrSame은 기준일을 그대로 허용합니다. 일정 요구 문구와 일치하는 adjuster를 선택합니다.
문자 경계와 DateTimeFormatter
ISO_LOCAL_DATE 같은 표준 formatter를 우선하고, 업무 형식이 필요할 때 패턴을 만듭니다.
MM은 월이고 mm은 분이며, HH는 24시간제 시간입니다.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public final class StudyDateFormatting {
public static void main(String[] args) {
DateTimeFormatter input = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH:mm", Locale.KOREAN);
DateTimeFormatter output = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime parsed = LocalDateTime.parse("2025년 03월 10일 14:25", input);
String formatted = parsed.format(output);
System.out.println("parsed=" + parsed);
System.out.println("formatted=" + formatted);
}
}parsed=2025-03-10T14:25
formatted=2025-03-10 14:25formatter는 불변이며 공유해도 안전합니다. 패턴 문자열을 매 호출마다 만들 필요 없이 상수로 재사용할 수 있습니다. 외부 프로토콜은 사람이 읽는 지역 형식보다 ISO 표준 형식을 우선합니다.
날짜 API 선택 기준
- 고정 필드 조회는 편의 getter
- 동적 필드 도구는 TemporalField와 isSupported
- 상대적 이동은 plus/minus
- 특정 필드 교체는 withX
- 달력 규칙은 TemporalAdjusters
- 문자열→시간은 parse
- 시간→문자열은 format
불변 반환값을 변수에 받지 않는 실수를 조심하고, 월말과 DST 같은 경계 입력을 반드시 예제에 포함합니다.
연습 문제
입력 날짜가 월요일이면 그대로, 아니면 다음 월요일을 선택하고 yyyy/MM/dd E 형식으로 출력하세요.
해설 보기
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;
public final class NextStudyMondayExercise {
public static void main(String[] args) {
LocalDate input = LocalDate.of(2025, 3, 12);
LocalDate monday = input.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd E", Locale.ENGLISH);
System.out.println(monday.format(formatter));
}
}2025/03/17 MonnextOrSame이 “이미 월요일이면 유지”라는 요구를 직접 표현합니다. 직접 요일 숫자를 빼는 계산보다 월 경계를 안전하게 넘습니다.