rokdocs
v0.1

3.3. time:: - 시간

rok::std::time 모듈은 세 종류의 시간 타입과 Duration 을 함께 제공합니다. 단조 시계, 벽시계, 그리고 부팅 후 경과 시간을 명확히 분리해 두어, 측정 목적에 맞는 시계를 골라 쓸 수 있도록 했습니다.

3.3.1. 4 타입 모델

전체 그림은 아래 표와 같습니다.

타입의미사용처
Instant단조 시계 (monotonic)duration 측정 / timeout
SystemTime벽시계 (UTC, NTP 가능)파일 mtime / 사용자 표시
BootTime부팅 후 시간 (suspend 포함)모바일 / laptop 앱
Duration시간 차이 (i64 nanosecond)둘 사이 차 / sleep 인자

Duration 의 내부 표현은 i64 ns 입니다. Rust 가 채택한 u128 은 다소 과잉이라고 판단했으며, i64 로도 약 ±292 년을 표현할 수 있어 실용 범위에서는 부족함이 없습니다.

3.3.2. Instant - 단조 시계

struct Instant { /* opaque - CLOCK_MONOTONIC / QueryPerformanceCounter */ }

impl Instant {
    fn now() -> Instant;

    fn elapsed(self) -> Duration;
    fn duration_since(self, earlier: Instant) -> Duration;       // self - earlier
    fn checked_duration_since(self, earlier: Instant) -> Duration | none;
    fn saturating_duration_since(self, earlier: Instant) -> Duration;

    // 산술 - 실패 시 none 반환 (Rust 의 saturating-on-overflow silent 회피)
    fn checked_add(self, dur: Duration) -> Instant | none;
    fn checked_sub(self, dur: Duration) -> Instant | none;
}

// 일반 산술 - overflow 시 overflow 검사가 켜져 있으면 panic, 꺼져 있으면 saturate (3.3.5)
impl Add<Duration> for Instant { type Output = Instant; ... }
impl Sub<Instant> for Instant { type Output = Duration; ... }
impl Sub<Duration> for Instant { type Output = Instant; ... }

Instant 의 핵심 특성은 다음과 같이 정리됩니다. 우선 절대로 뒤로 흐르지 않으므로 NTP 보정의 영향을 받지 않습니다. 또한 자체적으로 절대 시간 의미를 가지지 않기 때문에 (process 시작 시점이 기준이므로) serialize 가 불가능 합니다. 같은 이유로 process 사이의 비교도 무의미하며, 동일 process 내부에서만 의미가 성립합니다.

사용 예

가장 흔한 패턴은 측정 시작점을 잡아 두고 elapsed 로 경과를 얻는 것입니다.

let start = Instant::now();
heavy_computation();
let elapsed = start.elapsed();          // Duration
log(f"computation took {elapsed:?}");

3.3.3. SystemTime - 벽시계

struct SystemTime { /* opaque - CLOCK_REALTIME / GetSystemTimeAsFileTime */ }

impl SystemTime {
    fn now() -> SystemTime;

    fn duration_since(self, earlier: SystemTime) throws (SystemTimeError) -> Duration;
                                            // earlier > self 면 Err

    fn elapsed(self) throws (SystemTimeError) -> Duration;

    fn checked_add(self, dur: Duration) -> SystemTime | none;
    fn checked_sub(self, dur: Duration) -> SystemTime | none;
}

const UNIX_EPOCH: SystemTime;     // 1970-01-01 00:00:00 UTC

// SystemTime ↔ unix timestamp
impl SystemTime {
    fn from_unix(secs: i64, nanos: u32) -> SystemTime;
    fn unix_seconds(self) throws (SystemTimeError) -> i64;
    fn unix_nanos(self) throws (SystemTimeError) -> i64;   // rok 에 i128 이 없음 - i64 ns 는 약 ±292 년
}

impl Add<Duration> for SystemTime { type Output = SystemTime; ... }
impl Sub<Duration> for SystemTime { type Output = SystemTime; ... }

struct SystemTimeError {
    duration: Duration;        // earlier 가 앞서 있던 만큼
}

SystemTime 은 UNIX_EPOCH 기준 i64 나노초이므로 표현 범위는 모든 타깃에서 약 ±292 년입니다. 32-bit 타깃도 64-bit 시간 인터페이스 (time64) 로 시계를 읽으므로 2038 한계가 없습니다.

반면 SystemTime 의 성격은 사뭇 다릅니다. NTP 보정이나 사용자 변경에 의해 시간이 뒤로 갈 수도 있고, UNIX_EPOCH 를 기준으로 한 절대 시간이므로 serialize 가 가능 합니다. 그 덕분에 process 사이에서도 의미 있게 비교할 수 있습니다.

사용 예

unix timestamp 변환과 파일 mtime 조회는 다음과 같이 자연스럽게 표현됩니다.

let now = SystemTime::now();
let unix = try now.unix_seconds();
log(f"unix timestamp: {unix}");

// 파일 mtime
let meta = try fs::metadata("data.txt");
let mtime: SystemTime = try meta.modified();
let age = try SystemTime::now().duration_since(mtime);
log(f"file age: {age:?}");

3.3.4. BootTime - 부팅 후 시간 (suspend 포함)

struct BootTime { /* CLOCK_BOOTTIME (Linux) / CLOCK_MONOTONIC = mach_continuous_time (macOS) */ }

impl BootTime {
    fn now() -> BootTime;
    fn elapsed(self) -> Duration;
    fn duration_since(self, earlier: BootTime) -> Duration;
}

BootTime 과 Instant 의 차이는 suspend 동작에서 갈립니다. Instant 는 Linux CLOCK_MONOTONIC 처럼 suspend 동안에는 멈춰 있는 반면, BootTime 은 CLOCK_BOOTTIME 을 따라 suspend 중에도 계속 진행합니다.

이러한 차이는 모바일이나 laptop 앱에서 "실제 경과 시간 (suspend 포함)" 을 측정해야 할 때 결정적입니다. Rust 는 2026 시점에도 stable API 가 없는 영역인데, rok 은 이를 일찍 표면에 노출시켜 둡니다.

3.3.5. Duration

struct Duration { ns: i64; }       // i64 nanosecond - 약 ±292년

impl Duration {
    const ZERO: Duration = Duration { ns: 0 };
    const MAX: Duration = Duration { ns: I64_MAX };

    // 생성자
    fn ns(n: i64) -> Duration;
    fn us(n: i64) -> Duration;        // microseconds
    fn ms(n: i64) -> Duration;        // milliseconds
    fn sec(n: i64) -> Duration;
    fn min(n: i64) -> Duration;
    fn hours(n: i64) -> Duration;
    fn days(n: i64) -> Duration;

    fn from_secs_f64(s: f64) -> Duration;        // floating point seconds

    // 추출
    fn as_ns(self) -> i64;
    fn as_us(self) -> i64;
    fn as_ms(self) -> i64;
    fn as_secs(self) -> i64;
    fn as_secs_f64(self) -> f64;

    fn subsec_ns(self) -> u32;          // 하위 nanosecond 부분
    fn subsec_us(self) -> u32;
    fn subsec_ms(self) -> u32;
}

// 산술
impl Add for Duration { ... }
impl Sub for Duration { ... }
impl Mul<i64> for Duration { ... }     // Duration × i64
impl Div<i64> for Duration { ... }
impl Mul<Duration> for f64 { ... }      // f64 × Duration

impl Display for Duration { ... }       // "1.234s" / "5ms" / "1.5µs" / "100ns"
impl Debug for Duration { ... }         // Display 와 같음

Display 는 크기에 맞는 단위 하나로 씁니다. 절댓값이 1 초 이상이면 s, 1 ms 이상이면 ms, 1 µs 이상이면 µs, 그 밖에는 ns 입니다. 그 단위로 나눈 정수부 뒤에 나머지를 소수로 붙이되, 끝의 0 은 지우고 나머지가 0 이면 소수점도 쓰지 않습니다. 음수는 앞에 - 를 붙입니다.

Duration::ms(1234);      // "1.234s"
Duration::ms(5);         // "5ms"
Duration::ns(1500);      // "1.5µs"
Duration::ns(-20);       // "-20ns"

시간 타입은 모두 PartialEq / Eq / PartialOrd / Ord / Hash 를 구현합니다. Instant, SystemTime, BootTime 은 Debug 도 구현합니다.

Duration 산술 정책

Duration / SystemTime / Instant / BootTime 의 산술 (duration_since, from_unix 포함) 은 overflow 검사가 켜져 있으면 overflow 에 panic 하고, 꺼져 있으면 saturate 합니다 (-C overflow-checks, refs-lang 9.1.2). 0 으로 나누기는 언제나 panic 입니다. checked_* / saturating_* 변형으로 명시적으로 고를 수 있습니다.

impl Duration {
    fn checked_add(self, rhs: Duration) -> Duration | none;
    fn checked_sub(self, rhs: Duration) -> Duration | none;
    fn checked_mul(self, factor: i64) -> Duration | none;
    fn checked_div(self, divisor: i64) -> Duration | none;

    fn saturating_add(self, rhs: Duration) -> Duration;
    fn saturating_sub(self, rhs: Duration) -> Duration;
    fn saturating_mul(self, factor: i64) -> Duration;
}

3.3.6. 정책 - TAI / leap second 미지원

SystemTime 은 어디까지나 UTC 이며, leap second 는 처리하지 않습니다 (Linux kernel 자체가 이 부분에서 모호하다는 점도 고려했습니다). TAI (atomic time) 나 leap second 의 정확한 처리가 필요한 경우는 별도 패키지 (rok/chrono 또는 rok/tai) 의 영역으로 분리합니다.

자리권장
일반 앱 (timestamp / 측정)rok::std::time
시뮬레이션 / 정밀 천체별도 chrono-like 패키지
금융 / 정확한 leap second별도 패키지

3.3.7. impl / target

플랫폼별 구현 매핑은 아래 표를 참고합니다.

항목linuxmacoswindowswasi
InstantCLOCK_MONOTONICCLOCK_UPTIME_RAWQueryPerformanceCounterwasi clock
SystemTimeCLOCK_REALTIMECLOCK_REALTIMEGetSystemTimePreciseAsFileTimewasi
BootTimeCLOCK_BOOTTIMECLOCK_MONOTONIC (mach_continuous_time, sleep 포함)QueryPerformanceCounter (~ Instant)(no suspend)

3.3.8. 정책 요약

본 장의 핵심 결정은 세 가지로 정리됩니다. 먼저 Instant / SystemTime / Duration 을 분리하여 단조 시간과 벽시계 시간을 의미상 구분합니다. 다음으로 Rust 의 std 가 다루지 않는 BootTime 을 별도로 노출하여, suspend 를 포함한 부팅 이후 경과 시간을 직접 확인할 수 있게 합니다. 마지막으로 TAI 와 leap second 는 본 모듈 바깥의 별도 패키지로 분리합니다.

GitHub에서 이 페이지 고치기