spring boot 3 으로 올린 프로젝트에서 아래와 같은 에러가 나기 시작했다.
org.opentest4j.AssertionFailedError: Unexpected exception thrown: java.time.DateTimeException: Invalid value for NanoOfSecond (valid values 0 - 999999999): -971000000
@Override
public <X> LocalTime wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if (value instanceof LocalTime) {
return (LocalTime) value;
}
if (value instanceof Time) {
final Time time = (Time) value;
final LocalTime localTime = time.toLocalTime();
final long millis = time.getTime() % 1000;
if ( millis == 0 ) {
return localTime;
}
return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L );
}
LocalTimeJavaType 은 Hibernate에서 LocalTime 타입을 처리하는 클래스이다.
time.getTime()은 에포크 시간(1970년 1월 1일 00:00:00 UTC) 이후의 밀리초를 반환한다. 이 값을 1000으로 나눈 나머지를 계산하여 밀리초 값을 얻는데, time.getTime()이 음수일 때, 모듈러 연산 결과가 음수가 될 수 있다🫠
ValueRange 에서 해당 값이 유효한지 체크하게 되는데, 음수면 AssertionFailedError
에러가 발생한다.
KST (UTC+9) UTC 에포크 시간
────────────────────────────────────────────────────────────────
1970-01-01 01:20:23 1969-12-31 16:20:23 -23177
DB 서버 타임존은 UTC(+0) 이고, 서버 타임존은 KST(+9) 이다. 에포크 시간은 1970년 1월 1일 00:00:00 UTC를 기준으로 하는 시간 표현 방식인데, DB row 값인 01:20:23
는 연도가 없어 1970-01-01 01:20:23
으로 세팅되고, KST -> UTC 로 변환되면서 음수가 되어버린 것이다.
해당 이슈는 하이버네이트 6 이슈로, 6.6 에서 패치될 예정이라고 한다.
fyi; 관련 PR
LocalDate date = LocalDate.now();
LocalDate specificDate = LocalDate.of(2023, Month.AUGUST, 7);
LocalTime time = LocalTime.now();
LocalTime specificTime = LocalTime.of(13, 45, 30);
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime specificDateTime = LocalDateTime.of(2023, Month.AUGUST, 7, 13, 45, 30);
OffsetTime offsetTime = OffsetTime.now();
OffsetTime specificOffsetTime = OffsetTime.of(13, 45, 30, 0, ZoneOffset.ofHours(2));
OffsetDateTime offsetDateTime = OffsetDateTime.now();
OffsetDateTime specificOffsetDateTime = OffsetDateTime.of(2023, Month.AUGUST, 7, 13, 45, 30, 0, ZoneOffset.ofHours(2));
ZonedDateTime zonedDateTime = ZonedDateTime.now();
ZonedDateTime specificZonedDateTime = ZonedDateTime.of(2023, Month.AUGUST, 7, 13, 45, 30, 0, ZoneId.of("Europe/Paris"));
Instant now = Instant.now();
Instant specificInstant = Instant.ofEpochSecond(1627849265); // Epoch time
Year
: 연도를 나타낸다.
Year year = Year.of(2023);
YearMonth
: 연도와 월을 나타낸다.
YearMonth yearMonth = YearMonth.of(2023, Month.AUGUST);
MonthDay
: 월과 일을 나타낸다.
MonthDay monthDay = MonthDay.of(Month.AUGUST, 7);
ZoneId
: 시간대를 나타낸다.
ZoneId zoneId = ZoneId.of("Europe/Paris");
ZoneOffset
: UTC 기준으로 오프셋을 나타낸다.
ZoneOffset offset = ZoneOffset.ofHours(9);
이 객체들은 자바의 새로운 날짜 및 시간 API인 java.time
패키지에 포함되어 있으며, JSR 310을 기반으로 설계되어있다.