macOS Mach Clocks: Choose Whether Deadlines Include System Sleep
Compare mach_absolute_time and mach_continuous_time, convert ticks safely, and keep elapsed-time measurements separate from wall-clock timestamps.
Two timestamps can both be monotonic and still answer different questions. On macOS, mach_absolute_time() returns a tick count from an arbitrary starting point and does not advance while the system sleeps. mach_continuous_time() also returns monotonic ticks from an arbitrary point, but it continues advancing during sleep. The choice changes the meaning of a deadline after a laptop lid closes: should a timeout measure awake elapsed time, or should it have expired while the machine was asleep? Apple documents both clock contracts directly. Apple: mach_absolute_time Apple: mach_continuous_time
Neither counter is a calendar date. The arbitrary origin means that persisting a raw value as an event timestamp, comparing it with a Unix epoch value, or sending it to another machine is a category error. Use a clock with the required semantics, subtract readings from that same clock domain for an interval, and convert units only when the consumer needs them.
Decide what “elapsed” means for the feature
An animation or short in-process benchmark usually wants a monotonic interval that is not distorted by a wall-clock adjustment. If the system sleeps between two samples, mach_absolute_time() excludes that suspended period. A retry or expiry policy may instead require that time passing during sleep counts; mach_continuous_time() expresses that requirement. Do not describe the first as a thread CPU-time clock: Apple documents sleep behavior, not a measure of CPU cycles consumed by a particular thread.
Write the feature requirement as a sentence before selecting a clock. “Retry after ten minutes of awake time” and “expire ten minutes after creation, even if the Mac sleeps” are different policies. For a true calendar expiry that must survive reboot or be interpreted on another system, use a wall-clock timestamp and define its time-zone and clock-correction policy; neither Mach counter supplies that durable identity.
Apple lists mach_continuous_time() as available from macOS 10.12. Its current reference recommends corresponding clock_gettime_nsec_np() clocks when nanoseconds are more convenient: CLOCK_UPTIME_RAW for the awake-only behavior of mach_absolute_time(), and CLOCK_MONOTONIC_RAW for the sleep-inclusive behavior of mach_continuous_time(). That can remove manual conversion; check the deployment target before substituting APIs. Apple: Mach time APIs
Convert ticks with the returned timebase
Mach counters are in ticks, not necessarily nanoseconds. mach_timebase_info() returns the numer and denom fraction used to convert ticks to nanoseconds: multiply the tick count by numer, then divide by denom. Do not assume the fraction is 1:1 because it happened to be so on one test Mac. The product can overflow 64-bit arithmetic even when the final nanosecond count would fit, so widen the multiplication before doing it.
#include <mach/mach_time.h>
#include <stddef.h>
#include <stdint.h>
static int ticks_to_nanoseconds(uint64_t ticks, uint64_t *out) {
mach_timebase_info_data_t tb;
if (out == NULL || mach_timebase_info(&tb) != KERN_SUCCESS || tb.denom == 0)
return -1;
__uint128_t scaled = (__uint128_t) ticks * tb.numer;
scaled /= tb.denom;
if (scaled > UINT64_MAX)
return -1;
*out = (uint64_t) scaled;
return 0;
}
uint64_t started = mach_continuous_time();
/* perform work */
uint64_t finished = mach_continuous_time();
uint64_t elapsed_ns;
if (ticks_to_nanoseconds(finished - started, &elapsed_ns) == 0) {
/* report this interval; do not label it a wall-clock timestamp */
}
The example samples one clock twice and converts the difference, not its arbitrary origin. __uint128_t is a compiler extension supported by Apple’s Clang on 64-bit targets; for other toolchains use checked quotient/remainder arithmetic or Apple’s recommended nanosecond API. Retain the overflow check even for short intervals.
Do not silently change clock domains
Subtract samples from the same clock. Mixing absolute-time and continuous-time readings does not define “sleep duration”; they have distinct sleep semantics. To measure an interval that includes sleep, sample the continuous clock before and after and document that the host may suspend between them. Comparing wall and monotonic clocks may help diagnosis but does not replace a stated clock contract.
Test policy rather than a fixed tick constant. Where controlled sleep tests are safe, capture both clocks before and after and compare deltas; do not assert exact durations because scheduling and sleep-entry delay vary. Test conversion and overflow on supported architectures. For deterministic tests, inject a clock function and feed it samples from one domain.
Include Apple’s privacy requirement in release review
Apple marks mach_absolute_time() as a required-reason API because it could be misused for fingerprinting. Apps and third-party SDKs using it must describe an approved reason in PrivacyInfo.xcprivacy. Check Apple’s current categories and reason codes; do not guess or choose a vaguely similar reason. This warning is specifically documented for the absolute-time API. Apple: required-reason API guidance Apple: system boot-time API category
Related:
- DispatchSourceTimer on macOS: Leeway, Coalescing, and Safe Cancellation
- macOS Run Loops: Sources, Modes, Timers, and Thread Ownership
Sources: