Files
project-auth-server/docs/standards/language/time.md
T

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를 우선한다.

예:

  • createdAt
  • updatedAt
  • expiresAt
  • issuedAt

3. 사람 기준 날짜/시간은 Local 타입 우선

시간대와 무관한 도메인 의미에는 Local 타입을 쓴다.

예:

  • 생일 -> LocalDate
  • 영업 시작 시각 -> LocalTime
  • UI에서 입력한 예약 시각(아직 zone 미확정) -> LocalDateTime

4. LocalDateTime은 시점이 아니다

LocalDateTime은 offset/time-zone이 없으므로 절대적인 시점으로 저장/비교/교환할 때 기본값으로 쓰지 않는다.

금지 예:

  • createdAtLocalDateTime으로 저장
  • 외부 시스템과 절대 시각을 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. DurationPeriod를 구분

  • 기계적 시간 간격 -> Duration
  • 사람 기준 달력 기간 -> Period

예:

  • access token TTL -> Duration
  • “30일 후”, “1개월 후” 같은 달력 의미 -> Period

12. legacy 시간 API는 새 코드에서 기본 금지

새 코드에서는 다음을 기본 금지한다.

  • java.util.Date
  • java.util.Calendar
  • java.sql.Timestamp
  • TimeZone 중심 설계
  • 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 금지