본문으로 건너뛰기
Abseil Code Review · 39/79

absl::Time·Duration 분석 — 단단한 type

· Hawk · 3분 읽기

#왜 또 하나의 시간 라이브러리인가

C++11에 <chrono>가 들어왔는데도 Google이 absl::Time을 따로 만든 이유는 단순하다. std::chrono는 강력하지만 너무 일반화 되어 있고, 일반 엔지니어가 매일 쓰기에는 boilerplate가 많다. 시간대(time zone), 달력(civil time), mocking 같은 일상 작업에서 표준은 침묵한다(C++20에서 일부 보강되었으나 14/17 코드베이스에는 무용지물이다).

Abseil은 두 가지 핵심 타입만 둔다.

  • absl::Time — 어떤 순간. UTC epoch 기준의 단일 timestamp.
  • absl::Duration — 두 순간 사이의 간격. 부호 있는 길이.

그 외 달력은 absl::CivilDay, 시간대는 absl::TimeZone으로 분리한다. 각 타입이 한 가지 의미만 가지므로 함수 시그니처에 의도가 그대로 드러난다.

std::chrono와의 차이를 항목별로 비교하면 다음과 같다.

absl::Time/Duration vs std::chrono

#단위 안전 — Duration이 부동소수처럼 보이는 이유

absl::Duration은 내부적으로 nanosecond resolution이지만 사용자는 단위를 의식하지 않는다.

#include "absl/time/time.h"
absl::Duration timeout = absl::Seconds(30);
absl::Duration jitter = absl::Milliseconds(250);
absl::Duration total = timeout + jitter; // 자동 합산

단위 변환이 생성자가 아니라 팩토리 함수로만 일어난다. 그래서 다음과 같은 코드는 컴파일되지 않는다.

// 회피 — 단위 명시 없는 정수 대입
absl::Duration d = 30; // ❌ 컴파일 에러
void Sleep(absl::Duration d);
Sleep(1000); // ❌ 1초인지 1ms인지 모름
// Good — 단위가 함수 호출에 박혀 있음
Sleep(absl::Seconds(1)); // ✅ 명백
Sleep(absl::Milliseconds(1)); // ✅ 명백

std::chrono::seconds와 비교하면 차이가 분명하다. chrono는 std::chrono::seconds{1}처럼 타입 자체가 단위를 인코딩한다. Abseil은 단일 타입(Duration)에 팩토리 단위를 분리한다. 이쪽이 API 시그니처가 한결 깔끔하다.

// std::chrono — 단위마다 다른 타입
void Wait(std::chrono::seconds s);
void Wait(std::chrono::milliseconds ms); // 오버로드 폭발
// abseil — 단일 타입
void Wait(absl::Duration d);

#Time — UTC epoch 기준 단일 timestamp

absl::Time순간만 표현한다. 시간대도, 달력도 모른다.

absl::Time start = absl::Now();
DoExpensiveWork();
absl::Duration elapsed = absl::Now() - start; // Time - Time = Duration
if (elapsed > absl::Seconds(5)) {
LOG(WARNING) << "Slow: " << elapsed;
}

산술 규칙이 직관적이다.

연산결과
Time + DurationTime
Time - TimeDuration
Duration + DurationDuration
Duration * doubleDuration
Duration / Durationdouble (비율)

Time + Time컴파일 에러다. epoch 합산은 의미가 없으므로 타입이 막는다. 이런 정적 안전성은 std::chrono::time_point + time_point도 동일하게 막지만, Abseil은 단일 Time 타입으로 묶어 사용 측 boilerplate를 줄였다.

#Duration의 음수 — 부호 있는 간격

absl::Duration은 부호 있는 값이다.

absl::Duration d = absl::Seconds(-3); // OK
absl::Duration negated = -timeout; // 부호 반전
if (deadline - absl::Now() < absl::ZeroDuration()) {
// 이미 지난 deadline
}

absl::InfiniteDuration()-absl::InfiniteDuration()도 valid한 값이다. 산술에 자연스럽게 흡수된다.

absl::Duration timeout = WantsBlocking() ? absl::InfiniteDuration()
: absl::Seconds(5);
// timeout이 무한이면 Wait()는 영원히 대기
mutex.LockWhenWithTimeout(condition, timeout);

#단위 추출 — 명시적 변환

Duration → 정수/실수 변환은 손실 가능성을 명시 하는 함수 이름을 쓴다.

absl::Duration d = absl::Microseconds(1500);
int64_t us = absl::ToInt64Microseconds(d); // 1500
int64_t ms = absl::ToInt64Milliseconds(d); // 1 (정수 절단)
double s = absl::ToDoubleSeconds(d); // 0.0015
// 회피 — 단위 모호한 추출 시도
int64_t raw = d.count(); // ❌ 그런 메서드 없음

absl::ToInt64*0 방향 절단을 한다. 반올림이 필요하면 Trunc/Floor/Ceil/Round를 명시적으로 부른다.

absl::Duration ms = absl::Trunc(d, absl::Milliseconds(1)); // ms 단위로 절단
absl::Duration ms2 = absl::Round(d, absl::Milliseconds(1)); // ms 단위로 반올림

#한 줄 비교 — chrono vs absl

작업std::chronoabseil
지금 시각std::chrono::system_clock::now()absl::Now()
5초 후now + std::chrono::seconds{5}absl::Now() + absl::Seconds(5)
정수 ms 추출duration_cast<ms>(d).count()absl::ToInt64Milliseconds(d)
단위 명시타입(seconds, milliseconds…)팩토리(Seconds, Milliseconds…)
시간대 변환C++20부터 zoned_timeabsl::TimeZone + ConvertDateTime
Mocking표준에 없음TestTime 또는 시계 주입

#코드 리뷰에서 자주 보는 지적

// 회피 — 단위 없는 raw int
void RetryAfter(int seconds);
RetryAfter(60); // 분? 초?
// Good — Duration으로 단위 박제
void RetryAfter(absl::Duration delay);
RetryAfter(absl::Minutes(1));
// 회피 — 시간 비교에 raw 정수 ms
if (elapsed_ms > 5000) { /* ... */ }
// Good — Duration끼리 비교
if (elapsed > absl::Seconds(5)) { /* ... */ }
// 회피 — std::time_t 그대로 노출
std::time_t deadline = ...;
// Good — absl::Time으로 캡슐화
absl::Time deadline = absl::FromTimeT(legacy);

#정리

  • absl::Time순간, absl::Duration간격. 두 타입의 산술 규칙이 의미를 강제한다.
  • 단위는 팩토리 함수에 박혀 있어 정수 대입이 컴파일되지 않는다.
  • 추출은 ToInt64*/ToDouble*로 명시. 절단/반올림이 코드에 드러난다.
  • 무한·음수가 valid value. 분기 없이 산술에 그대로 흡수된다.
  • chrono보다 표면적이 좁아 일상 코드의 시그니처가 한결 단순하다.

#다음 장 예고

Part 7-02에서는 FormatTime/ParseTime으로 RFC3339·strftime 호환 포맷을 다룬다.

#관련 항목

Abseil Code Review · 40 of 79

  1. 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
  2. 2 Abseil 개요 — Google이 std를 보완한 이유
  3. 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
  4. 4 Abseil 빌드와 의존성 — Bazel vs CMake
  5. 5 Abseil LTS vs HEAD 릴리스 모델 분석
  6. 6 Abseil Versioning과 ABI 호환성 정책
  7. 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
  8. 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
  9. 9 absl::LogSeverity — 로그 레벨 타입
  10. 10 Abseil type_traits — negation·conjunction·void_t
  11. 11 Abseil Conformance·Policy 분석
  12. 12 Abseil Memory utilities 분석
  13. 13 Abseil raw_logging — heap-free 로깅
  14. 14 Abseil thread_annotations — clang TSA 통합
  15. 15 absl::Status — exception-free error handling
  16. 16 absl::StatusOr<T> — 값 또는 에러
  17. 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
  18. 18 absl::Status payload — 구조화된 에러 컨텍스트
  19. 19 absl::Status ↔ exception 변환 패턴
  20. 20 absl::string_view — non-owning 문자열 참조
  21. 21 absl::string_view 함정 — dangling·c_str·임시 객체
  22. 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
  23. 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
  24. 24 absl::StrJoin — 컨테이너 결합과 Formatter
  25. 25 absl::StrFormat — type-safe printf·FormatSpec
  26. 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
  27. 27 Abseil Escape — CEscape·HexEscape·Base64
  28. 28 absl::flat_hash_map — Swiss Table 기반 hash map
  29. 29 absl::flat_hash_set — set 버전 Swiss Table
  30. 30 absl::node_hash_map — stable pointer가 필요할 때
  31. 31 absl::btree_map — sorted·cache-friendly B-tree
  32. 32 absl::FixedArray — 런타임 크기 stack 배열
  33. 33 absl::InlinedVector — small buffer optimization
  34. 34 Abseil Swiss Table internals — control byte·SIMD probing
  35. 35 absl::Mutex — reader-writer·fairness·deadlock 검출
  36. 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
  37. 37 absl::Notification — once-only signal
  38. 38 absl::BlockingCounter·Barrier — 다중 thread 조율
  39. 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
  40. 40 absl::Time·Duration 분석 — 단단한 type
  41. 41 absl::Time Format·Parse
  42. 42 absl::CivilTime 분석
  43. 43 absl::time_zone 분석
  44. 44 absl::Time mocking — 테스트 친화 시간
  45. 45 absl::BitGen — 모던 난수 생성기
  46. 46 Abseil Random Distributions — Uniform·Exponential
  47. 47 Abseil Mocking Random — 테스트 결정성
  48. 48 Abseil Random Seeding·Entropy
  49. 49 absl::int128·uint128 분석
  50. 50 absl::bits — popcount·countl_zero
  51. 51 absl::optional vs std::optional
  52. 52 absl::variant 분석
  53. 53 absl::span 분석
  54. 54 absl::any 분석
  55. 55 absl::compare — three-way 비교
  56. 56 Abseil utility — apply·in_place
  57. 57 Abseil AbslHashValue 분석
  58. 58 Abseil HashState chaining
  59. 59 Abseil Custom hashable 구현
  60. 60 Abseil LOG·VLOG·CHECK 분석
  61. 61 Abseil LogSink 분석
  62. 62 Abseil LogEntry·structured logging
  63. 63 Abseil Stack trace·failure_signal_handler
  64. 64 ABSL_FLAG 정의 분석
  65. 65 Abseil ParseCommandLine 동작
  66. 66 Abseil Flag introspection·validation
  67. 67 Google 스타일의 Abseil 사용 패턴
  68. 68 Abseil 자주 보는 anti-pattern
  69. 69 std → absl 마이그레이션 전략
  70. 70 absl::Cleanup — 함수 종료 시 실행 보장
  71. 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
  72. 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
  73. 73 absl::bind_front와 Overload — 함수 객체 보조 도구
  74. 74 absl::Cord — 분산 시스템용 대용량 문자열
  75. 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
  76. 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
  77. 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
  78. 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
  79. 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피