
우테코 오픈미션을 하던 중 예약 기능을 만들면서 "보류 기한은 3일"이라는 정책이 생겼다. 처음엔 단순히 int day = 3; 이런 식으로 처리할까 했지만 이건 그냥 숫자일 뿐 '시간'이라는 의미를 표현하지 못한다는 점이 마음에 걸렸다.
그래서 시간을 표현하는 방법을 고민하던 중java.time 패키지를 떠올렸다.
이번 여름방학 때 『자바의 정석』을 읽으며 LocalDateTime이나 Duration같은 클래스 이름은 한 번쯤 본 기억이 있었다. 하지만 그땐 단순히 "아 이런 게 있구나"정도로만 읽었지 내가 이걸 직접 프로젝트에서 쓰게 될 줄은 정말 몰랐다.
이번엔 단순히 읽고 지나가는 게 아니라 책에서 본 개념을 직접 코드로 써보며 배우는 진짜 도전이었다.
이 글에서는 내가 공부한 java.time의 일부 핵심 개념들과 그것을 내 코드에 적용하면서 느꼈던 생각과 배움을 함께 풀어보려 한다.
java.time 패키지는 자바 8부터 추가된 현대적 시간 API다.
날짜, 시간, 시점, 시간대 같은 개념을 분리해 더 명확하고 테스트 가능한 방식으로 다룰 수 있게 해준다.
여러가지 클래스가 있지만 내가 프로젝트에 직접적으로 사용한 클래스들을 중점으로 내용을 정리했다.
"2025-11-09 22:30" 같은 형태로 사람이 인식할 수 있는 시간을 표현한다.
생성
- now() / now(Clock)
- of(year, month, day, hour, minute[, second[, nano]])
- ofInstant(Instant, ZoneId)
연산
- plusDays/Hours/Minutes/Seconds(...)
- plus(Duration) / minus(Duration)
- withYear/Month/DayOfMonth/Hour/Minute/Second(...)
비교
- isBefore/ isAfter(ChronoLocalDateTime)
- isEqual(ChronoLocalDateTime)
변환/포맷
- atZone(ZoneId)
- toLocalDate() / toLocalTime()
- format(DateTimeFormatter)
주의
타임존이 없다. 타임존 필요하면 atZone(ZoneId) 또는 ZonedDateTime 사용.
다음 코드는 사용 예시이다.
LocalDateTime now = LocalDateTime.now(); // 현재 시각
LocalDateTime threeDaysLater = now.plusDays(3); // 3일 뒤
LocalDateTime custom = LocalDateTime.of(2025, 11, 9, 22, 30); // 직접 생성
"얼마 동안 지속되는가?"를 표현하는 클래스
생성
- ofDays/Hours/Minutes/Seconds/Millis/Nanos(...)
- between(Temporal startInclusive, Temporal endExclusive)
연산
- plus(...) / minus(...)
- multipliedBy(long) / dividedBy(long)
- isZero() / isNegative() / isPositive()
사용패턴
- LocalDateTime.plus(Duration) / minus(Duration)
- Instant.plus(Duration) / minus(Duration)
주의
달/년은 가변 길이라 Duration에 없다. (그럴 땐 Period)
다음 코드는 사용 예시이다.
Duration threeDays = Duration.ofDays(3); // 3일
Duration twoHours = Duration.ofHours(2); // 2시간
LocalDateTime holdUntil = LocalDateTime.now().plus(threeDays); // 지금 + 3일
"지금 몇 시인지"를 직접 코드에 의존하지 않게 해준다.
생성
- systemDefaultZone() / system(ZoneId)
- fixed(Instant, ZoneId) ← 테스트용 고정 시계
- offset(Clock, Duration) ← 기준 시계에서 오프셋
사용
- LocalDateTime.now(clock)
- Instant.now(clock)
주의
운영은 systemDefaultZone() 같은 실시간 시계
테스트는 fixed(...)로 시간을 멈춘다.
다음 코드는 사용 예시이다.
Clock systemClock = Clock.systemDefaultZone(); // 실제 시스템 시계
Clock fixedClock = Clock.fixed(
LocalDateTime.of(2025, 11, 12, 10, 0)
.toInstant(ZoneOffset.UTC), ZoneId.of("UTC")); // 테스트용 고정 시계
LocalDateTime now = LocalDateTime.now(systemClock);
"세계 공통의 지금 이 순간"을 나타냄.
생성
- now() / now(Clock)
- ofEpochSecond(long) / ofEpochMilli(long)
연산
- plus(Duration) / minus(Duration)
- isBefore/ isAfter(Instant)
변환
- LocalDateTime.ofInstant(Instant, ZoneId)
- ZonedDateTime.ofInstant(Instant, ZoneId)
주의
사람 친화적 포맷 아님 → 화면/도메인에는 보통 LocalDateTime로 변환
다음 코드는 사용 예시이다.
Instant now = Instant.now(clock); // 절대 시각
Instant after = now.plus(Duration.ofDays(3)); // 3일 뒤 절대 시각
"어느 지역 기준으로 시간을 계산할 것인가?"를 결정한다.
얻기
- of("Asia/Seoul"), of("UTC")
- systemDefault() ← 서버의 기본 시간대
사용
- LocalDateTime.ofInstant(instant, zoneId)
- LocalDateTime.atZone(zoneId)
주의
서버/배포 환경의 기본 타임존이 바뀌면 systemDefault() 결과가 달라질 수 있다.
정책적으로 고정하려면 ZoneId.of("Asia/Seoul")을 명시.
다음 코드는 사용 예시이다.
ZoneId seoulZone = ZoneId.of("Asia/Seoul");
LocalDateTime seoulTime = LocalDateTime.ofInstant(Instant.now(), seoulZone);
공부한 내용을 실제 코드에 녹였다.
예약 관련된 모든 곳에 많이 사용되었지만 일부만 보여주겠다.
그중에서 예약 선두를 찾아 보류(HOLD_READY) 상태로 전환하고 3일 뒤 만료 시각을 계산하는 메서드를 보여주겠다.
public void promoteHeadToHoldReady(Long storedBookId, Duration holdDuration, Clock clock) {
// 1) 선두(QUEUED) 예약 찾기
Reservation head = reservations.stream()
.filter(Reservation::isQueued)
.findFirst()
.orElseThrow(() -> new IllegalStateException("대기 중인 예약이 없습니다."));
// 2) 현재(절대 시각) + 보류 기간(정책) → 만료 시각 계산
Instant now = Instant.now(clock);
LocalDateTime holdUntil = LocalDateTime.ofInstant(
now.plus(holdDuration),
ZoneId.systemDefault()
);
// 3) 예약 상태 전이: HOLD_READY + 소장본/기한 지정
head.prepareHold(storedBookId, holdUntil);
}
코드의 이해를 돕기 위해 추가로 정보를 주겠다.
Duration = 보류 정책(3일)Clock = 현재 시각 주입(테스트에서 고정 가능)이번 오픈 미션을 통해 또 한 번 새로운 도전을 하게 되었다.
여름방학 때 책으로만 봤던 java.time이 단순한 개념이 아니라
실제 코드 속에서 살아 움직이는 도구로 쓰이게 된 것이다.
그때는 “이런 게 있구나” 하고 가볍게 넘겼던 개념이 지금은 내 도메인의 언어가 되어 있었다.
이 과정에서 java.time을 더 깊이 이해하게 된 것도 좋았지만 그보다 더 크게 느낀 건 “배움에 헛된 건 하나도 없다”는 사실이었다.
자바의 정석을 읽을 때 나는 java.time을 대충 훑고 지나쳤다.
그때 조금만 더 집중해서 공부했더라면 이번 미션이 더 수월했을지도 모르겠다는 생각이 든다. 하지만 동시에 이렇게 새로운 도전 속에서 다시 배우게 된 경험이 나중에 java.time을 사용할 때 훨씬 큰 자산이 될 거라 믿는다.
앞으로 무엇을 공부하든, 어떤 경험을 하든 그 어느 것도 헛된 시간은 없다는 마음으로 나아가고 싶다.
마음 한켠에 이 말을 꼭 가지고 다녀야겠다.
“모든 배움은 결국 어딘가에서 연결된다.”
16. Java 자바 [API] - java.time 패키지
[Java] 날짜 및 시간 API (Date and Time API) 사용 방법