Period·Duration과 시간 단위
날짜 사이를 Duration으로 계산하는 타입 오류를 재현하고 달력 기간·경과 시간·ChronoUnit·ChronoField의 질문 차이를 구분합니다.
“두 시점 사이”라는 말에는 달력상의 연·월·일 차이와 초 단위 경과 시간이 함께 섞여 있습니다. Period는 LocalDate 같은 날짜 차이를 연·월·일로, Duration은 시간 기반 객체 사이를 초·나노초로 표현합니다. TemporalUnit은 얼마나 더하거나 재는지, TemporalField는 한 시점에서 어떤 값을 꺼내는지 나타냅니다.
Duration과 날짜 타입 불일치
LocalDate에는 하루 안의 시간 정보가 없습니다. Duration은 초 단위 차이를 요구하므로 두 날짜를 직접 넘기면 지원되지 않는 단위 예외가 발생합니다.
import java.time.Duration;
import java.time.LocalDate;
public final class DurationBetweenDatesFailure {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2025, 1, 1);
LocalDate end = LocalDate.of(2025, 1, 3);
System.out.println(Duration.between(start, end));
}
}Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException날짜 차이는 Period 또는 ChronoUnit.DAYS를 사용합니다. 정말 정확한 48시간 경과가 필요한 경우 각 날짜에 시간과 ZoneId를 결합해 순간을 만든 뒤 Duration을 계산해야 합니다. DST가 있는 지역에서는 “다음 날 같은 현지 시각”이 항상 24시간 뒤가 아닙니다.
날짜 간격을 나타내는 Period
import java.time.LocalDate;
import java.time.Period;
public final class StudyPeriod {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2025, 1, 15);
LocalDate end = LocalDate.of(2026, 3, 20);
Period period = Period.between(start, end);
System.out.println("period=" + period);
System.out.println("years=" + period.getYears());
System.out.println("months=" + period.getMonths());
System.out.println("days=" + period.getDays());
System.out.println("restored=" + start.plus(period));
}
}period=P1Y2M5D
years=1
months=2
days=5
restored=2026-03-20getMonths는 전체 개월 수가 아니라 연도를 제외한 월 구성 요소입니다. 전체 개월 수가 필요하면 ChronoUnit.MONTHS.between을 사용하되 남은 일수를 어떻게 처리하는지 확인합니다. Period는 달력 문맥을 유지합니다.
경과 시간을 나타내는 Duration
import java.time.Duration;
import java.time.LocalTime;
public final class StudyDuration {
public static void main(String[] args) {
LocalTime start = LocalTime.of(9, 10);
LocalTime end = LocalTime.of(10, 40, 30);
Duration duration = Duration.between(start, end);
System.out.println("duration=" + duration);
System.out.println("seconds=" + duration.toSeconds());
System.out.println("minutes=" + duration.toMinutes());
System.out.println("parts=" + duration.toHoursPart() + "h "
+ duration.toMinutesPart() + "m " + duration.toSecondsPart() + "s");
}
}duration=PT1H30M30S
seconds=5430
minutes=90
parts=1h 30m 30stoMinutes는 초 나머지를 버린 전체 분입니다.
화면에서 시간·분·초 구성 요소가 필요하면 toHoursPart 같은 부분 추출 메서드를 사용합니다.
자정을 넘는 LocalTime끼리만 계산하면 날짜 정보가 없어 음수나 잘못된 차이가 될 수 있으므로 LocalDateTime을 사용합니다.
ChronoUnit 계산 단위
ChronoUnit은 NANOS부터 DAYS, WEEKS, MONTHS, YEARS까지 표준 단위를 enum으로 제공합니다. 모든 시간 타입이 모든 단위를 지원하지는 않습니다.
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public final class ChronoUnitStudyPlan {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2025, 1, 1);
LocalDate end = start.plus(6, ChronoUnit.WEEKS);
long days = ChronoUnit.DAYS.between(start, end);
LocalDateTime first = LocalDateTime.of(2025, 1, 1, 9, 0);
LocalDateTime second = first.plus(95, ChronoUnit.MINUTES);
System.out.println("end=" + end);
System.out.println("days=" + days);
System.out.println("second=" + second);
}
}end=2025-02-12
days=42
second=2025-01-01T10:35date.plusWeeks(6) 같은 편의 메서드가 더 읽기 쉬우면 우선 사용합니다.
단위를 설정이나 인수로 전달해야 할 때 TemporalUnit 추상화가 유용합니다.
ChronoField 시점 필드
YEAR, MONTH_OF_YEAR, DAY_OF_MONTH, HOUR_OF_DAY 같은 필드는 TemporalField를 구현합니다.
LocalDate는 시간 필드를 지원하지 않으므로 isSupported로 확인하거나 타입에 맞는 편의 getter를 사용합니다.
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
public final class ChronoFieldInspection {
public static void main(String[] args) {
LocalDateTime value = LocalDateTime.of(2025, 3, 10, 14, 25, 30);
System.out.println("year=" + value.get(ChronoField.YEAR));
System.out.println("month=" + value.getMonthValue());
System.out.println("minute=" + value.get(ChronoField.MINUTE_OF_HOUR));
System.out.println("nano-supported=" + value.isSupported(ChronoField.NANO_OF_SECOND));
}
}year=2025
month=3
minute=25
nano-supported=true일반 업무 코드는 getYear, getMonthValue처럼 의미가 드러나는 편의 메서드가 좋습니다. 필드를 동적으로 선택하는 범용 도구와 포매터에서 ChronoField를 사용합니다.
간격·단위·필드 선택
- 생일까지 남은 연·월·일: Period
- 세션 시작부터 종료까지 초: Duration
- 날짜 사이 총 일수: ChronoUnit.DAYS
- 설정에 따라 주·월 단위 더하기: TemporalUnit
- 한 날짜시간의 연도 필드 동적 조회: ChronoField
- 고정된 연도 조회: getYear 편의 메서드
달력 기간과 경과 시간을 하나의 long으로 통일하면 월 길이와 DST 의미를 잃습니다. 타입이 질문을 보존하게 합니다.
연습 문제
09:00부터 11:15까지 전체 Duration을 구하고, 25분 학습 블록 사이에 5분 휴식이 두 번 있었다면 실제 학습 분을 계산하세요.
해설 보기
import java.time.Duration;
import java.time.LocalTime;
public final class SessionDurationExercise {
public static void main(String[] args) {
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(11, 15);
Duration elapsed = Duration.between(start, end);
long studyMinutes = elapsed.toMinutes() - 2 * 5;
System.out.println("elapsed=" + elapsed.toMinutes());
System.out.println("study=" + studyMinutes);
}
}elapsed=135
study=125휴식 횟수가 세션 길이에서 자동으로 나오지 않으므로 실제 휴식 기록이나 명시 정책이 필요합니다. 계산에 필요한 사실을 타입과 입력으로 분리합니다.