5.9 KiB
time 타입 / 포맷 기준
목적
시간은 문자열이나 숫자 조합이 아니라 의미에 맞는 타입으로 표현한다.
“시점”, “날짜”, “시간”, “벽시계 기준 날짜-시간”, “시간대 포함 날짜-시간”, “기간”을 서로 다른 개념으로 취급한다.
공식 의미
java.time는 날짜/시간/instant/duration의 기본 API다.java.time타입은 immutable, thread-safe 다.- 시스템 경계를 넘는 저장/네트워크에는 ISO-8601 기반
java.time타입을 우선 사용한다. Instant는 timestamp에 해당한다.LocalDate는 날짜만 표현한다.LocalTime은 시간만 표현한다.LocalDateTime은 날짜+시간이지만 offset/time-zone이 없다.ZonedDateTime은 time-zone까지 포함한 “full” date-time이다.OffsetDateTime은 UTC offset을 포함하며 네트워크 프로토콜/DB 접근에서 주로 쓰인다.- 현재 시각은
Clock에서 얻을 수 있고, 이는 테스트를 쉽게 만든다. now()계열은 시스템 clock과 기본 time-zone을 사용한다.
기본 규칙
1. 문자열 대신 타입으로 표현
시간/날짜를 내부 로직에서 String으로 들고 다니지 않는다.
금지:
"2026-04-12T10:15:30Z"를 business field로 보관"2026-04-12"를 domain/application 로직에서 날짜 대신 사용"09:00"를 opening time 의미로 직접 비교
기본:
- 날짜 ->
LocalDate - 시각만 ->
LocalTime - 시점 ->
Instant - 날짜+시간(시간대 없음) ->
LocalDateTime - 시간대 포함 wall-clock ->
ZonedDateTime - offset 포함 경계 표현 ->
OffsetDateTime - 시간 간격 ->
Duration - 사람 기준 기간(일/월/년) ->
Period
2. 시점(point in time)은 Instant를 기본값으로
로그 시각, 생성 시각, 만료 시각, 이벤트 발생 시각처럼 timeline 위 한 점이면 Instant를 우선한다.
예:
createdAtupdatedAtexpiresAtissuedAt
3. 사람 기준 날짜/시간은 Local 타입 우선
시간대와 무관한 도메인 의미에는 Local 타입을 쓴다.
예:
- 생일 ->
LocalDate - 영업 시작 시각 ->
LocalTime - UI에서 입력한 예약 시각(아직 zone 미확정) ->
LocalDateTime
4. LocalDateTime은 시점이 아니다
LocalDateTime은 offset/time-zone이 없으므로 절대적인 시점으로 저장/비교/교환할 때 기본값으로 쓰지 않는다.
금지 예:
createdAt를LocalDateTime으로 저장- 외부 시스템과 절대 시각을
LocalDateTime으로 주고받기
5. ZonedDateTime은 정말 시간대 계산이 필요할 때만
실제 지역 시간대 규칙(DST 포함)을 고려해야 하는 계산만 ZonedDateTime을 쓴다.
예:
- 특정 도시/지역 wall-clock 기준 예약
- 사용자 time-zone 기준 만료/알림 계산
기본값은 아니다. 시간대는 복잡도를 크게 높인다.
6. 경계/프로토콜/DB에서는 Instant 또는 OffsetDateTime을 우선 검토
시스템 경계를 넘는 시간 값은 ISO-8601 기반 java.time 타입을 사용한다.
기본 선택:
- “절대 시점” 저장/전송 ->
Instant - offset이 포함된 wire/db 표현 필요 ->
OffsetDateTime
7. 현재 시각은 Clock 기반으로 다룰 수 있게 설계
테스트 가능성이 중요한 코드에서는 Instant.now() / LocalDate.now() / LocalDateTime.now()를 직접 박지 않는다.
기본 방향:
- 현재 시각을 얻는 위치를 경계/서비스로 모은다
- 필요 시
Clock을 주입한다 - 테스트에서는 고정 clock을 사용한다
8. 기본 시스템 time-zone 의존 최소화
기본 시스템 time-zone을 암묵적으로 쓰는 호출은 신중히 제한한다.
기본 원칙:
- zone이 중요하면
ZoneId를 명시한다 now()계열의 기본 zone 의존은 casual code에서만 허용- business logic, persistence, cross-system contract에서는 zone을 명시적으로 다룬다
9. 포맷/파싱은 경계에서만
날짜/시간 포맷팅과 파싱은 주로 boundary에서 수행한다.
예:
- controller/request binding
- response serialization
- external API adapter
- logging formatter
domain/application 내부에서는 typed value를 유지한다.
10. 기본 포맷은 ISO-8601 우선
새로운 커스텀 날짜 포맷을 기본값으로 만들지 않는다.
시스템 간 교환은 ISO-8601을 우선한다.
11. Duration과 Period를 구분
- 기계적 시간 간격 ->
Duration - 사람 기준 달력 기간 ->
Period
예:
- access token TTL ->
Duration - “30일 후”, “1개월 후” 같은 달력 의미 ->
Period
12. legacy 시간 API는 새 코드에서 기본 금지
새 코드에서는 다음을 기본 금지한다.
java.util.Datejava.util.Calendarjava.sql.TimestampTimeZone중심 설계System.currentTimeMillis()를 직접 흩뿌리는 패턴
기본은 java.time 사용이다.
13. 저장/조회/직렬화 기준을 명시
시간 값을 DB/JSON/API에 노출할 때는 다음을 명확히 한다.
- 어떤 타입을 저장하는가
- 어떤 zone/offset 가정을 하는가
- 어떤 포맷으로 직렬화하는가
- 정밀도(초/밀리초/나노초)를 어느 수준까지 쓸 것인가
14. 문자열 비교/부분 파싱으로 시간 로직 작성 금지
시간 판단을 문자열 조작으로 하지 않는다.
금지:
timestamp.startsWith(...)"09:00".compareTo(...)- substring으로 연/월/일 추출
프로젝트 기준 요약
- 시간은 타입으로 표현한다
- 시점은
Instant - 날짜는
LocalDate - 시각은
LocalTime LocalDateTime은 절대 시점 용도 금지- 실제 time-zone 계산만
ZonedDateTime - 경계/DB/네트워크는
Instant또는OffsetDateTime - 현재 시각은 가능하면
Clock기반 - 기본 시스템 time-zone 의존 최소화
- 포맷/파싱은 경계에서만
- 새 코드에서 legacy 시간 API 금지