fekke
fekke

Reputation: 351

iOS real monotonic clock other than mach_absolute_time()

My iOS app needs a custom clock that is synchronized with my server's clock all the time. All the synchronization logic is done.

My clock is based on mach_absolute_time() from which you can calculate the elapsed time since the device was booted. The problem is that when the device goes into sleep mode (by pressing the screen lock key and having no app running in background) the mach time ticking is paused. When the phone is awakened the mach time ticking resumes but it will not account for the time that the phone has been asleep.

This is how I calculate elapsed time since boot (but it won't account for the time the device is asleep)

- (long long) elapsedTimeMillis {
    uint64_t elapsedTimeNano = 0;

    mach_timebase_info_data_t timeBaseInfo;
    mach_timebase_info(&timeBaseInfo);

    elapsedTimeNano = mach_absolute_time() * timeBaseInfo.numer / timeBaseInfo.denom;
    return (long long)(elapsedTimeNano / 1000000);
}

And this has exactly the same results:

- (long long) elapsedTimeMillis {
    clock_serv_t cclock;
    mach_timespec_t mts;

    host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
    clock_get_time(cclock, &mts);
    mach_port_deallocate(mach_task_self(), cclock);

    return mts.tv_nsec / 1000000 + mts.tv_sec * 1000;
}

NSDate, CFAbsoluteTimeGetCurrent() or alike are not an option because they are affected by device time being sync'ed or manually changed by the user.

Is there ANY way to get time in iOS from a non-stop ticking clock that won't jump back or forth?

Upvotes: 10

Views: 7369

Answers (3)

snake_case
snake_case

Reputation: 126

Other answers related mostly to Obj-C but I've found an official Swift-based solution included in the standard library since iOS 16.0.

There are two structures to choose from:

After obtaining two Instances from ContinuousClock.now, the method duration may be used to obtain the difference.

let start = ContinuousClock.now;

// Do something

let end = ContinuousClock.now;

let delta = start.duration(end)

Upvotes: 2

robinst
robinst

Reputation: 31427

There is now a mach_continuous_time function that includes time while the system is asleep:

Returns current value of a clock that increments monotonically in tick units (starting at an arbitrary point), including while the system is asleep.

Note that it returns the time in ticks, which is inconvenient. You can instead use clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) to get nanoseconds.

Here's a complete example in Swift:

import Foundation

let elapsed_time_nanos = clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)

(I don't think it was available back when this question was asked. I'm not sure with which iOS version it was introduced exactly, but it's there on at least iOS 15.)

Upvotes: 1

fekke
fekke

Reputation: 351

This does the trick:

See: Getting iOS system uptime, that doesn't pause when asleep

- (time_t)uptime
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);
    time_t now;
    time_t uptime = -1;
    (void)time(&now);
    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = now - (boottime.tv_sec);
    }

    return uptime;
}

That returns SECONDS since last boot. Device sleeps won't affect it, neither will phone time syncs or phone time changes by the user. Unfortunately it doesn't return the milliseconds or nanoseconds.

Upvotes: 4

Related Questions