빌드노트
두 날짜는 40일 차이인데 Period.getDays()는 왜 10일일까
Period.getDays()는 연·월·일 중 일 성분이다. 고정 날짜로 40일 구간과 평년·윤년의 한 달을 비교하고, 전체 일수에 ChronoUnit.DAYS.between을 쓰는 기준과 종료일 제외·음수 처리까지 확인한다.
Java에서 두 LocalDate 사이의 일수를 구하려고 Period.between(start, end).getDays()를 쓰면, 2024년 4월 1일부터 5월 11일까지의 차이가 10으로 나온다. 두 날짜의 차이는 40일이다. getDays()는 연·월·일로 나눈 기간 중 일 성분만 반환한다. 전체 일수가 필요하면 long totalDays = ChronoUnit.DAYS.between(start, end);로 계산한다.
먼저 그 값을 쓰는 곳을 확인한다. “1개월 10일”처럼 달력 단위로 기간을 표시하려는지, “40일 남음”처럼 날짜 차이를 하나의 일수로 쓰려는지에 따라 필요한 결과가 다르다. Period를 일수로 읽는 코드만 바꿀 문제인지, 연·월·일 표현을 유지할 문제인지 여기서 결정할 수 있다.
10일 앞에 한 달이 있다
공식 문서에서 Period.between은 완전한 월 수와 남은 일수를 구하고, 월 수를 연과 월로 나눈다. getDays()는 그중 일 성분을 꺼낸다. 4월 1일→5월 11일은 한 달과 10일이며, 여기서 한 달에 해당하는 4월 1일→5월 1일이 30일이다. Period의 기간 계산과 성분 정의 (새 탭에서 열림)
아래 코드는 같은 입력에서 연·월·일 성분과 전체 일수를 비교한다. JDK 17 이상을 준비하고 터미널에서 java -version을 확인한 뒤 jshell --execution local --feedback concise를 실행해 붙여 넣는다. 별도 프로젝트나 라이브러리는 필요 없다.
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;
var start = LocalDate.of(2024, 4, 1);
var end = LocalDate.of(2024, 5, 11);
var p = Period.between(start, end);
System.out.printf("years=%d months=%d days=%d%n", p.getYears(), p.getMonths(), p.getDays());
long totalDays = ChronoUnit.DAYS.between(start, end);
System.out.println("totalDays=" + totalDays);
Windows의 OpenJDK 17.0.2에서 두 출력문이 남긴 값이다. 실제 기한 계산 장애의 로그가 아니라, 고정 날짜를 넣은 별도 실험이다.
years=0 months=1 days=10
totalDays=40
종료할 때는 /exit를 입력한다. 이 기간을 문자열로 출력한 값은 P1M10D였다. M 앞의 1은 월 성분, D 앞의 10은 일 성분이다. 40일 중 30일이 계산에서 사라진 것이 아니라 월 성분에 담겨 있다.
ChronoUnit.DAYS.between은 지정한 단위인 일로 날짜 차이를 반환하며, 반환형은 long이다. 기존 변수도 int에서 long으로 맞추면 된다. 같은 입력인데 위 출력과 다르면 계산 직전의 두 날짜와 실제로 호출한 메서드를 먼저 확인한다. 입력 날짜부터 다르면 날짜를 만드는 앞 단계도 조사해야 한다. ChronoUnit.between의 단위와 반환값 (새 탭에서 열림)
같은 한 달이 28일·29일·30일이었다
같은 달 안의 짧은 구간만 확인하면 이 오류를 놓치기 쉽다. 다음은 날짜를 바꾸어 실행한 결과다. 각 항목의 getDays는 Period.between(...).getDays(), 전체 일수는 ChronoUnit.DAYS.between(...)의 값이다.
- 2024-04-01→2024-04-11:
P10D,getDays=10, 전체 10일. - 2024-04-01→2024-05-01:
P1M,getDays=0, 전체 30일. - 2023-02-01→2023-03-01:
P1M,getDays=0, 전체 28일. - 2024-02-01→2024-03-01:
P1M,getDays=0, 전체 29일. - 2023-02-28→2023-03-01은
P1D, 전체 1일이었다. 윤년인 2024-02-28→2024-03-01은P2D, 전체 2일이었다. - 2023-03-01→2024-03-11:
P1Y10D,getDays=10, 전체 376일. 여기서는 월 성분 대신 연 성분이 따로 있다.
같은 P1M인데도 전체 일수는 세 가지였다. 따라서 getMonths() * 30 + getDays()로 보정하면 2월부터 틀린다. Period 값만 가지고는 어느 날짜에서 시작한 한 달인지 알 수 없다. 날짜 차이가 목적이면 원래의 시작일과 종료일로 계산해야 한다.
normalized()를 붙여도 해결되지 않는다. 문서상 이 메서드는 연·월을 정규화하고 일 성분은 유지한다. 실제로 위 40일 구간의 p.normalized().getDays()도 10이었다. Period.normalized의 처리 범위 (새 탭에서 열림)
종료일 포함 여부와 음수는 별도로 정한다
두 between 계산은 시작일을 포함하고 종료일을 제외한다. 직접 실행했을 때 4월 1일→4월 2일의 전체 일수는 1, 4월 1일→같은 4월 1일은 0이었다. 4월 1일부터 4월 3일 직전까지 날짜를 나열하면 1일과 2일, 두 개다. 이는 날짜 양끝을 모두 세는 방식과 다르다. Period.between의 날짜 경계 (새 탭에서 열림)
양끝을 모두 포함한 날짜 개수가 요구사항이라면, 먼저 start.isAfter(end)인 입력을 거절하고 나머지에 ChronoUnit.DAYS.between(start, end) + 1을 적용할 수 있다. 이 규칙도 별도로 실행했다. 4월 1일~3일은 3개, 같은 날짜 하나는 1개였고 역순 입력은 거절됐다. 모든 날짜 차이에 무조건 1을 더하는 규칙으로 사용하지 않는다.
역순도 예외가 생기는 계산은 아니다. 2024-05-11→2024-04-01의 결과는 P-1M-10D, getDays=-10, 전체 일수는 -40이었다. 종료일이 앞서면 음수를 반환하는 API 동작이다. 기한이 지난 날짜도 표현할 목적이면 이 부호를 유지하고, 입력 순서가 잘못된 상황이면 계산 전에 거절한다. ChronoUnit.between의 음수 반환 조건 (새 탭에서 열림)
이 비교의 대상은 시각과 시간대를 담지 않는 LocalDate다. 40이라는 결과는 달력 날짜의 차이이며, 두 시점 사이에 정확히 960시간이 지났다는 측정이 아니다. 공식 문서에서 Duration의 하루는 정확히 24시간이고, 시간대가 있는 날짜의 하루는 일광 절약 시간 전환에 따라 그와 다를 수 있다. LocalDate의 표현 범위 (새 탭에서 열림), Period와 Duration의 하루 차이 (새 탭에서 열림)
실행 검증은 OpenJDK 17.0.2의 고정 날짜 11쌍과 종료일 포함 규칙에 한정했다. Java SE 17·21의 관련 API 문서는 대조했지만 JDK 21에서의 재실행, 시간대 변환, 실제 시점 사이의 경과 시간은 검증하지 않았다.