날짜와 시간 API
시간 코드는 달력 날짜, 시계 시각, 타임존이 포함된 순간을 같은 타입으로 다루면 오류가 납니다.
LocalDate와 LocalTime은 지역 값이고 Instant는 UTC 타임라인의 한 지점입니다.
ZonedDateTime은 지역 규칙과 타임존을 함께 적용하며 일광 절약 시간 전환을 포함합니다.
DateTimeFormatter는 불변이고 스레드 안전해 형식을 명시적으로 공유할 수 있습니다.
이 절에서는 저장, 계산, 표시의 타입을 나눕니다.
타임존은 단순한 UTC 오프셋이 아니라 지역별 과거·미래 규칙의 집합입니다. 같은 LocalDateTime이 일광 절약 시간 전환에서 존재하지 않거나 두 번 나타날 수도 있습니다. 사용자가 입력한 지역 시각을 Instant로 바꿀 때 ZoneId와 겹침·공백 정책이 필요합니다.
LocalDate와 LocalDateTime에는 타임존이 없다
생일이나 지역 업무 날짜에는 적합하지만 전 세계에서 하나의 순간을 식별하지 못합니다.
Instant는 타임라인의 절대 시점을 표현한다
로그와 이벤트 저장에는 Instant가 단순하며 표시할 때 사용자의 ZoneId로 변환합니다.
고정된 Clock 하나에서 Instant와 지역 날짜를 만들고, Duration과 Period가 세는 단위를 나란히 출력합니다.
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
public class TimeModels {
public static void main(String[] args) {
Instant fixed = Instant.parse("2025-01-01T00:00:00Z");
Clock clock = Clock.fixed(fixed, ZoneId.of("Asia/Seoul"));
System.out.println(Instant.now(clock));
System.out.println(LocalDate.now(clock));
Duration hours = Duration.between(fixed, fixed.plusSeconds(7_200));
Period months = Period.between(LocalDate.of(2025, 1, 1), LocalDate.of(2025, 3, 1));
System.out.println(hours.toHours());
System.out.println(months.getMonths());
}
}고정 Clock이 제공한 절대 시점은 언제 실행해도 첫 줄과 같고, 같은 Clock의 Seoul zone으로 지역 날짜를 계산해 둘째 줄을 만듭니다. 마지막 두 숫자는 우연히 같지만 하나는 7200초의 시간량, 다른 하나는 달력상 두 달이라 서로 바꿔 쓸 수 없습니다.
2025-01-01T00:00:00Z
2025-01-01
2
2고정 Clock 덕분에 현재 시각 의존 출력이 항상 같고 Instant와 LocalDate를 같은 기준에서 비교할 수 있습니다.
생일이나 지역 업무 날짜에는 적합하지만 전 세계에서 하나의 순간을 식별하지 못합니다.
로그와 이벤트 저장에는 Instant가 단순하며 표시할 때 사용자의 ZoneId로 변환합니다.
Duration은 초·나노 기반 시간량, Period는 년·월·일 달력량입니다.
Duration과 Period의 단위를 구분한다
Duration은 초·나노 기반 시간량, Period는 년·월·일 달력량입니다.
하루가 항상 24시간이 아닌 타임존 계산에서 차이가 드러납니다.
저장할 절대 시점과 사용자에게 받은 지역 시각 사이에는 반드시 타임존을 적용하는 변환 단계가 있습니다.
var formatter = java.time.format.DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm", java.util.Locale.KOREA);
var local = java.time.LocalDateTime.parse("2025-07-13 09:30", formatter);
var zoned = local.atZone(java.time.ZoneId.of("Asia/Seoul"));
Instant stored = zoned.toInstant();// LocalDateTime만 저장하면 어느 타임존의 순간인지 결정할 수 없음
// Duration.ofDays(1)과 Period.ofDays(1)은 DST 경계에서 의미가 다를 수 있음Formatter에 형식과 Locale을 고정한다
입력 문자열 패턴과 사용자 언어를 명시하고 파싱 실패를 처리합니다.
기본 로케일에 의존한 저장 포맷을 만들지 않습니다.
Clock을 주입하면 현재 시각 의존성을 통제한다
now()를 코드 곳곳에서 직접 부르기보다 Clock을 받으면 같은 시각을 재현할 수 있습니다.
운영에서는 system clock, 검증에서는 fixed clock을 사용합니다.
Clock의 zone을 America/New_York로 바꾸고 같은 Instant에서 LocalDate.now(clock) 결과를 비교해 보세요. 절대 시점은 그대로지만 지역 날짜는 이전 날이 될 수 있습니다. 저장 값과 표시 값 가운데 무엇이 바뀐 것인지 구분해서 설명하세요.
포맷 문자열은 화면 표현이지 시간의 의미가 아닙니다. 외부 API에는 ISO 형식과 zone/offset 포함 여부를 명시하고, 사용자 입력에는 Locale과 잘못된 날짜 처리 정책을 둡니다. LocalDateTime 문자열만 보내고 서버 기본 타임존으로 해석하게 두면 배포 지역에 따라 다른 Instant가 만들어집니다.
시간 모델은 절대 순간, 지역 날짜, 타임존, 표시 형식을 서로 다른 타입과 변환 단계로 유지해야 합니다.