4.7 KiB
4.7 KiB
time 타입 / 포맷 예시
이 문서는 time 타입 / 포맷 기준을 코드 예시로 확인하기 위한 자료입니다.
핵심 기준은 시간 값을 문자열이나 숫자로 들고 다니지 않고, 시점 / 날짜 / 시각 / 기간 / 시간대 의미에 맞는 java.time 타입으로 표현하는 것입니다.
좋은 예시 1: event timestamp는 Instant
public record AuditEvent(
String action,
Long userId,
Instant occurredAt
) {
}
왜 좋은가:
- timeline 위 한 점을 명확하게 표현한다.
- 로깅, 저장, 비교에 적합하다.
좋은 예시 2: 사람 기준 날짜 의미는 LocalDate / YearMonth
public record UserProfile(
LocalDate birthDate,
YearMonth cardExpiry
) {
}
왜 좋은가:
- 시간대와 무관한 사람 기준 날짜 의미를 타입으로 드러낸다.
- 생일과 카드 만료월처럼 서로 다른 날짜 의미를 구분한다.
좋은 예시 3: 현재 시각은 Clock 기반
public class TokenIssuer {
private final Clock clock;
public TokenIssuer(Clock clock) {
this.clock = clock;
}
public Instant issueTime() {
return Instant.now(clock);
}
public Instant expiryTime(Duration ttl) {
return Instant.now(clock).plus(ttl);
}
}
왜 좋은가:
- 현재 시각을 고정해 테스트하기 쉽다.
- static
now()호출이 코드 곳곳에 흩어지지 않는다.
좋은 예시 4: 시간 간격은 Duration
private static final Duration ACCESS_TOKEN_TTL = Duration.ofMinutes(30);
왜 좋은가:
1800같은 매직 숫자보다 의미가 분명하다.- 초, 밀리초, 분 단위 혼동이 줄어든다.
좋은 예시 5: 외부 응답 포맷은 경계에서 처리
String value = DateTimeFormatter.ISO_INSTANT.format(event.occurredAt());
왜 좋은가:
- 내부 로직은
Instant를 유지한다. - 문자열 포맷은 serialization, logging, external API adapter 같은 boundary에서만 수행한다.
좋은 예시 6: 실제 zone 계산이 필요할 때만 ZonedDateTime
ZonedDateTime reservationTime = localReservationTime.atZone(ZoneId.of("Asia/Seoul"));
왜 좋은가:
- 서울 지역 wall-clock 시간이라는 의미가 필요할 때만 zone을 붙인다.
- 시간대 규칙이 필요한 계산임을 코드에 드러낸다.
나쁜 예시 1: createdAt을 LocalDateTime으로 저장
private LocalDateTime createdAt;
문제:
- 절대 시점이 아니라 zone/offset 없는 wall-clock 값이 된다.
- 시스템 간 교환, 저장, 비교에서 의미가 흔들린다.
개선:
private Instant createdAt;
나쁜 예시 2: business logic에서 기본 시스템 zone 의존
LocalDate today = LocalDate.now();
문제:
- JVM 기본 time-zone에 암묵적으로 의존한다.
- 테스트와 운영 환경에 따라 결과가 달라질 수 있다.
개선:
LocalDate today = LocalDate.now(clock);
또는:
LocalDate today = LocalDate.now(zoneId);
나쁜 예시 3: 문자열로 시간 비교
if (request.startTime().compareTo("09:00") >= 0) {
// open
}
문제:
- 타입 의미가 사라진다.
- 포맷 변화에 취약하다.
개선:
if (!request.startTime().isBefore(LocalTime.of(9, 0))) {
// open
}
나쁜 예시 4: legacy API 사용
Date now = new Date();
Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 1_800_000);
문제:
- 새 코드 기준으로
java.time보다 의미가 덜 명확하다. - 시간 단위와 시스템 clock 의존이 코드에 흩어진다.
개선:
Instant now = Instant.now(clock);
Instant expiresAt = now.plus(Duration.ofMinutes(30));
나쁜 예시 5: wall-clock 의미인데 Instant 남용
public record StoreHours(
Instant opensAt,
Instant closesAt
) {
}
문제:
- 영업 시작/종료는 보통 지역 wall-clock 의미다.
- 절대 시점 타입이 도메인 의미를 흐린다.
개선:
public record StoreHours(
LocalTime opensAt,
LocalTime closesAt
) {
}
나쁜 예시 6: Instant를 DTO에서 문자열로 직접 조립
public record TokenResponse(
String expiresAt
) {
public static TokenResponse from(Instant expiresAt) {
return new TokenResponse(expiresAt.toString());
}
}
문제:
- DTO 조립 코드가 시간 포맷 정책을 직접 가진다.
- 응답 포맷 변경이 여러 DTO 생성 코드로 퍼질 수 있다.
개선 방향:
- response serialization 설정이나 전용 formatter 경계에서 포맷한다.
- 내부 모델과 유스케이스 결과는
Instant같은 typed value를 유지한다.