ChronoLocalDate
ChronoLocalDate (임의 달력의 날짜)
임의의 달력 시스템(chronology)에서 시간대(time-of-day)가 없는 날짜를 나타내는 인터페이스예요. 고급 국제화(globalization) 사용 사례를 위해 마련된 것이에요. 대부분의 애플리케이션에서는 이 인터페이스 대신 LocalDate를 메서드 시그니처·필드·변수로 선언해야 해요.
본문
ChronoLocalDate는 달력 시스템(chronology)이 플러그인 가능한 날짜의 추상 표현이에요. 날짜는 TemporalField로 표현되는 필드들로 정의되며, 가장 흔한 구현은 ChronoField에 정의되어 있어요. 달력 시스템이 어떻게 동작하고 표준 필드가 무엇을 뜻하는지는 chronology가 정의해요.
이 인터페이스를 쓰는 상황
API 설계는, 애플리케이션이 여러 달력 시스템을 다뤄야 하는 경우에도 이 인터페이스보다 LocalDate를 쓰도록 권장해요. 자연스럽게 달력 시스템을 추상화하면 애플리케이션을 국제화하기 쉬울 것처럼 보이지만, 실제로는 논리 오류와 찾기 어려운 버그를 불러오는 경우가 많아요. 따라서 이 인터페이스를 쓸지 LocalDate를 쓸지는 애플리케이션 전반의 아키텍처 결정으로 봐야 해요.
고려할 아키텍처 이슈는 다음과 같아요.
- 이 인터페이스를 쓰면
LocalDate만 쓸 때보다 버그 확률이 훨씬 높아져요. 개발 시점에 사용 중인 달력 시스템을 모르기 때문이에요. ISO 달력에 대한 일상 지식을 임의의 달력 시스템을 다루는 코드에 적용하는 것이 주요 버그 원인이에요. - 이 인터페이스는 구현의 불변성을 강제하지 않아요. 구현 노트에서 모두 불변이어야 한다고 하지만, 코드나 타입 시스템 차원에서 강제하는 것은 없어요.
- 이 인터페이스를 쓸 때는 era(연호)의 영향을 고려해야 해요.
LocalDate는getYear()가 proleptic 연도를 반환하게 해서 era 개념을 숨겨주지만, 이 인터페이스는 era, year-of-era, month-of-year, day-of-month 네 필드로 생각해야 해요. 예를 들어 일본 달력에서는 era가 천황의 재위를 나타내고, 재위가 바뀔 때마다 year-of-era가 1로 초기화돼요. - 국제적으로 합의된 날짜 전달 표준은 ISO-8601이며, 이는 ISO 달력 시스템을 요구해요.
- 데이터베이스 같은 장기 영속 저장소는 거의 항상 ISO-8601 달력(또는 관련 율리우스-그레고리력)만 받아들여요.
잘못된 가정이 버그를 만드는 예
- day-of-month를 조회하며 31을 넘지 않는다고 가정하면 틀려요. 어떤 달력은 어떤 달에 31일이 넘어요.
- 날짜에 12개월을 더하면 1년이 더해진다고 가정하면 틀려요. 콥트력이나 에티오피아력처럼 13개월을 쓰는 달력이 있어요.
- 날짜에 1개월을 더하면 month-of-year가 1만큼 늘거나 다음 해로 넘어간다고 가정하면 틀려요. 히브리력처럼 해마다 월 수가 달라지는 달력이 있어요.
- 날짜에 1개월을 두 번 더하면 day-of-month가 원래 값 근처를 유지한다고 가정하면 틀려요. 가장 긴 달과 가장 짧은 달의 길이 차이가 큰 달력이 있어요.
- 7일을 더하면 일주일이 더해진다고 가정하면 틀려요. 프랑스 혁명력처럼 7일이 아닌 주를 쓰는 달력이 있어요.
일반적으로 달력 시스템이 개발 시점에 알려지지 않았다면, 날짜를 조작하거나 심지어 조회하는 것도 버그에 매우 취약해요. 그래서 이 인터페이스를 쓰는 코드는 추가 코드 리뷰가 필수예요.
LocalDate로 대체하기
달력을 다루는 메서드 시그니처는 전부 LocalDate로 선언하고, 사용자의 달력 시스템(chronology)은 사용자 프로필이나 로케일에서 찾아, 출력·파싱 시에만 ISO LocalDate를 사용자의 선호 달력으로 변환하는 방식이 권장돼요. 이렇게 하면 달력 문제를 로컬라이제이션 이슈로 보고 UI 계층에 한정할 수 있어요. 달력 규칙이 플러그인 가능한 상태에서 날짜 계산을 하는 것은 기술이 필요하며 권장되지 않아요. 저수준 프레임워크나 라이브러리를 만드는 개발자도 이 인터페이스를 피하고, 읽기 전용이면 TemporalAccessor, 읽기·쓰기 모두 필요하면 Temporal을 쓰는 게 좋아요.