absl::CivilTime 분석
#CivilTime이 푸는 문제
“3월 31일에 한 달을 더하면 4월 31일?” — 달력 산술은 자연스러워 보이지만 윤년, 30일/31일 월, DST 같은 잡일이 숨어 있다. Abseil의 CivilTime 계열은 시간대를 모르는 달력 좌표만 다루어 이 잡일을 압축한다.
#include "absl/time/civil_time.h"
absl::CivilDay d(2026, 3, 31);d += 1; // 2026-04-01
absl::CivilMonth m(2026, 3);m += 1; // 2026-04absl::CivilDay end_of_april(m.year(), m.month() + 1, 1);--end_of_april; // 2026-04-30핵심 원칙은 시간대가 없다는 점이다. CivilDay는 “어떤 달력 좌표”일 뿐 “어느 순간”이 아니다.
#여섯 가지 정밀도
| 타입 | 의미 | 산술 단위 |
|---|---|---|
CivilSecond | YYYY-MM-DD hh:mm | 1초 |
CivilMinute | YYYY-MM-DD hh | 1분 |
CivilHour | YYYY-MM-DD hh | 1시간 |
CivilDay | YYYY-MM-DD | 1일 |
CivilMonth | YYYY-MM | 1월 |
CivilYear | YYYY | 1년 |
각 타입의 operator++/+=가 해당 단위로 진행한다. 정밀도가 낮은 타입은 시작 시점 으로 truncate된다.
absl::CivilSecond cs(2026, 5, 24, 16, 23, 45);
absl::CivilDay cd(cs); // 2026-05-24 00:00:00absl::CivilMonth cm(cs); // 2026-05-01 00:00:00absl::CivilYear cy(cs); // 2026-01-01 00:00:00암묵 변환이 높은 정밀도 → 낮은 정밀도 방향으로만 동작한다. 반대는 명시 변환만 허용한다.
#산술 — 정규화가 자동
이상한 입력을 넣어도 정규화된다.
absl::CivilDay d(2026, 13, 32); // ❓ 13월 32일?// → 정규화: 2027-02-01명시적으로 잘못된 날짜를 만들 수 없으므로 if (month <= 12 && day <= 31) 같은 가드가 사라진다.
윤년도 자연스럽다.
absl::CivilDay leap(2024, 2, 29); // OKabsl::CivilDay nope(2025, 2, 29); // 정규화 → 2025-03-01월말 산술도 직관적이다.
absl::CivilMonth m(2026, 1);for (int i = 0; i < 12; ++i) { absl::CivilDay last = absl::CivilDay(m + 1, 1) - 1; std::cout << m << " ends on " << last << "\n"; ++m;}#TimeZone과의 결합 — FromCivil / ToCivil
CivilTime만으로는 절대 시간을 만들 수 없다. TimeZone과 결합해야 absl::Time이 된다.
absl::TimeZone seoul;absl::LoadTimeZone("Asia/Seoul", &seoul);
absl::CivilSecond cs(2026, 5, 24, 9, 0, 0);absl::Time t = absl::FromCivil(cs, seoul); // 2026-05-24 09:00:00 KST // = 2026-05-24 00:00:00 UTC반대도 가능하다.
absl::Time now = absl::Now();absl::CivilSecond cs_seoul = absl::ToCivilSecond(now, seoul);absl::CivilSecond cs_utc = absl::ToCivilSecond(now, absl::UTCTimeZone());DST 전환점에 있는 모호한 시각은 TimeZone::At이 자세한 분기 정보를 준다.
auto info = seoul.At(cs);// info.kind: UNIQUE / SKIPPED / REPEATED// info.pre / info.trans / info.post대부분의 코드는 FromCivil이면 충분하다(SKIPPED·REPEATED 모두 합리적 기본값을 고른다).
#요일 계산 — GetWeekday
absl::CivilDay d(2026, 5, 24);absl::Weekday wd = absl::GetWeekday(d); // absl::Weekday::sunday
// 다음 월요일absl::CivilDay next_mon = absl::NextWeekday(d, absl::Weekday::monday);absl::CivilDay prev_mon = absl::PrevWeekday(d, absl::Weekday::monday);NextWeekday(d, wd)는 오늘이 wd라도 7일 후를 반환한다. 오늘 포함이 필요하면 d - 1부터 시작한다.
#두 날짜 사이 일수
absl::CivilDay start(2026, 1, 1);absl::CivilDay end(2026, 5, 24);
int64_t days = end - start; // 143operator-는 해당 단위 개수를 반환한다. CivilMonth 끼리 빼면 월 차이, CivilSecond는 초 차이.
#흔한 함정
#1. CivilDay가 곧 자정인 줄 알기
CivilDay는 좌표일 뿐 순간이 아니다. FromCivil(CivilDay(...), tz)가 자정이 되는 것이지, CivilDay 자체는 시간대가 없다.
#2. 시간대 잊고 빼기
// 회피 — 다른 사용자의 "오늘"이 같다고 가정auto today = absl::ToCivilDay(absl::Now(), absl::UTCTimeZone());
// Good — 사용자 TZ를 함께auto today_for_user = absl::ToCivilDay(absl::Now(), user_tz);#3. 윤초 처리
CivilSecond는 60초 분을 모르는 SI 시간이다. POSIX 시간과 동일하게 윤초가 흡수된다. 천체·물리 계산이 아니라면 신경 쓸 필요 없다.
#작은 예시 — 청구서 주기
struct BillingCycle { absl::CivilDay start; absl::CivilDay end; // 마지막 날 포함};
BillingCycle MonthlyCycle(absl::CivilDay any_day_in_month) { absl::CivilMonth m(any_day_in_month); absl::CivilDay start(m, 1); absl::CivilDay end = absl::CivilDay(m + 1, 1) - 1; return {start, end};}
// 청구 기간이 30일 이상 떨어졌는지bool LongerThanMonth(BillingCycle a, BillingCycle b) { return (b.start - a.end) > 30;}#정리
CivilSecond~CivilYear여섯 타입, 각각 해당 단위로 산술.- 이상한 입력은 정규화로 흡수 — 윤년·월말·invalid date 가드 불필요.
- 시간대 없음.
absl::Time이 필요하면FromCivil(civil, tz)로 결합. GetWeekday,NextWeekday,PrevWeekday로 요일 산술.- 두 좌표의 차이는
operator-— 단위 개수 반환.
#다음 장 예고
Part 7-04: TimeZone — IANA TZ DB와 안전한 변환.
#관련 항목
Abseil Code Review · 42 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
absl::Time mocking — 테스트 친화 시간
테스트에서 절대 시간을 통제하기 — clock 주입 패턴과 absl::Now() 대체 전략.
같은 시리즈에서 이어 읽기
absl::time_zone 분석
absl::TimeZone — IANA TZ database 위에 얹은 안전한 변환 layer. UTC가 아닌 시각을 다루는 모든 코드가 거쳐야 할 관문.
같은 시리즈에서 이어 읽기
absl::Time Format·Parse
FormatTime, ParseTime — Abseil이 strftime/RFC3339를 한 함수로 흡수하는 방법.
같은 시리즈에서 이어 읽기