9

Is there an API to obtain the NSDate or NSTimeInterval representing the time the system booted? Some APIs such as [NSProcessInfo systemUptime] and Core Motion return time since boot. I need to precisely correlate these uptime values with NSDates, to about a millisecond.

Time since boot ostensibly provides more precision, but it's easy to see that NSDate already provides precision on the order of 100 nanoseconds, and anything under a microsecond is just measuring interrupt latency and PCB clock jitter.

The obvious thing is to subtract the uptime from the current time [NSDate date]. But that assumes that time does not change between the two system calls, which is, well, hard to accomplish. Moreover if the thread is preempted between the calls, everything is thrown off. The workaround is to repeat the process several times and use the smallest result, but yuck.

NSDate must have a master offset it uses to generate objects with the current time from the system uptime, is there really no way to obtain it?

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • You've already accepted an answer, but as @hotpaw2 noted, `NSDate` is a wall-clock timestamp, and changes such as daylight savings and NTP updates can cause two successive moments in real time to show up in the wrong order as `NSDate`s (or `time_t`s or any other wall-clock timestamp.) – Jonathan Grynspan Oct 10 '12 at 23:00
  • @JonathanGrynspan Indeed the problem isn't resolved to my satisfaction. It looks like Apple doesn't at all generate robust timestamps, despite having changed the format between CoreLocation and CoreMotion in an apparent attempt at improvement. Daylight savings at least shouldn't make a difference as NSDate measures GMT. For what it's worth my app will request airplane mode "for optimal performance" as receiving calls is bad for real time operation. This would also stop NTP. – Potatoswatter Oct 11 '12 at 00:53
  • What are you trying to achieve? What specific need do you have for a precise timestamp? Apple does timestamps pretty much the same way every other system does, just with different type names. – Jonathan Grynspan Oct 11 '12 at 01:00
  • @JonathanGrynspan I need to precisely synchronize data acquisition from CoreMotion. Most systems stick to a particular timestamp format whereas all the timestamp sources on iOS seem to be routed through a different set of adjustments… and the decision to use floating point is unusual too. – Potatoswatter Oct 11 '12 at 02:48
  • Floating-point provides sub-microsecond precision across a wide range of dates while being easily convertible to integer types. That's why it's used. If you want a timestamp for non-wall-clock purposes, try `CACurrentMediaTime()`--it's a `double` via `CFTimeInterval`, but it's guaranteed to increase regularly over time and is measured in seconds. – Jonathan Grynspan Oct 11 '12 at 13:40
  • @JonathanGrynspan CACurrentMediaTime returns the *current* time, which is useless. And would be yet another different timing source, I want less not more. – Potatoswatter Oct 12 '12 at 01:54
  • It's really not clear what you want. – Jonathan Grynspan Oct 12 '12 at 08:06
  • @JonathanGrynspan I want to know which CoreMotion update associates with each CoreLocation update. But as hotpaw mentions, CoreLocation's time basis isn't even guaranteed to increase. Long story short, I want a real data acquisition platform. – Potatoswatter Oct 12 '12 at 08:37

5 Answers5

8

In OSX you could use sysctl(). This is how the OSX Unix utility uptime does it. Source code is available - search for boottime.

Fair warning though, in iOS i have no idea if this would work.

UPDATE: found some code :)

#include <sys/types.h>
#include <sys/sysctl.h>  

#define MIB_SIZE 2  

int mib[MIB_SIZE];
size_t size;
struct timeval  boottime;

mib[0] = CTL_KERN;
mib[1] = KERN_BOOTTIME;
size = sizeof(boottime);
if (sysctl(mib, MIB_SIZE, &boottime, &size, NULL, 0) != -1)
{
    // successful call
    NSDate* bootDate = [NSDate dateWithTimeIntervalSince1970:
                               boottime.tv_sec + boottime.tv_usec / 1.e6];
}

see if this works...

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
Srikar Appalaraju
  • 71,928
  • 54
  • 216
  • 264
  • Ooh, it even returns the saner fixed-point format. It's worth a try… – Potatoswatter Jul 01 '12 at 15:08
  • i even remember seeing some code somewhere to get the boottime, let me find it for you. – Srikar Appalaraju Jul 01 '12 at 15:09
  • just curious what are you going to use this info for? what will you do with iOS system boottime? – Srikar Appalaraju Jul 01 '12 at 15:22
  • Combine CoreMotion with CoreLocation… by the way it's [documented on Apple's iOS site](http://developer.apple.com/library/ios/#documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctl.3.html) so it should be supported. Still didn't try running it… – Potatoswatter Jul 01 '12 at 15:27
  • 1
    cool,yeah i know its documented. But I still didnt get why you need this info. Can you please tell me how this related to CoreMotion with CoreLocation? And what you are doing in detail? – Srikar Appalaraju Jul 01 '12 at 15:31
  • I'm new to iOS, does the "Mac OS X manpage" being on the iOS website not make a difference? … I'd prefer not to go into more detail, but precision is a good thing. – Potatoswatter Jul 01 '12 at 15:51
  • This works on iOS, but it seems to be calculated dynamically based on the uptime. When I set the device's clock forward an hour, the consequent system boot time leaps forward by an hour. – piojo Sep 19 '16 at 09:01
2

The accepted answer, using systcl, works, but the values returned by sysctl for KERN_BOOTTIME, at least in my testing (Darwin Kernel Version 11.4.2), are always in whole seconds (the microseconds field, tv_usec, is 0). This means the resulting time may be up to 1 second off, which is not very accurate.

Also, having compared that value, to one derived experimentally from the difference between the REALTIME_CLOCK and CALENDAR_CLOCK, they sometimes differ by a couple seconds, so its not clear whether the KERN_BOOTTIME value corresponds exactly to the time-basis for the uptime clocks.

Hank
  • 21
  • 2
1

The routines inside mach/mach_time.h are guaranteed to be monotonically increasing, unlike NSDate.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • NSDate increases at a constant rate over time, of course, but the frequency of change varies. Currently it's ~100 ns, in about 5 years it will shift to ~200 ns, 20 years after that ~400 ns. That's not going to break a lot of apps… although I'm not saying floating-point is a good way to represent time. – Potatoswatter Jul 01 '12 at 15:58
  • 3
    @Potatoeswatter: Not true. Two successive NSDate calls can go backwards in time (if an NTP correction falls in that direction in between). See the Apple Developer forums for verification of this. – hotpaw2 Jul 01 '12 at 19:26
  • OK, interesting. But this still isn't an answer to the question. I'm given an NSDate by CoreLocation and an NSTimeInterval by CoreMotion; I don't get to choose the representation. Is there a way to temporarily disable NTP while my app executes on iOS? – Potatoswatter Jul 02 '12 at 00:03
  • You could request the user to put the device in Airplane mode to stop NTP from working. – hotpaw2 Mar 02 '13 at 15:05
1

There is another way. It could give result slightly different (less or more) than accepted answer

I have compared them. I get difference -7 second for OSX 10.9.3 and +2 second for iOS 7.1.1

As i understand this way gives same result if wall clock changed, but accepted answer gives different results if wall clock changed...

Here code:

static CFAbsoluteTime getKernelTaskStartTime(void) {
    enum { MICROSECONDS_IN_SEC = 1000 * 1000 };
    struct kinfo_proc   info;
    bzero(&info, sizeof(info));

    // Initialize mib, which tells sysctl the info we want, in this case
    // we're looking for information about a specific process ID = 0.
    int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};

    // Call sysctl.
    size_t size = sizeof(info);
    const int sysctlResult = sysctl(mib, COUNT_ARRAY_ELEMS(mib), &info, &size, NULL, 0);
    assert(0 != sysctlResult);

    const struct timeval * timeVal = &(info.kp_proc.p_starttime);
    NSTimeInterval result = -kCFAbsoluteTimeIntervalSince1970;
    result += timeVal->tv_sec;
    result += timeVal->tv_usec / (double)MICROSECONDS_IN_SEC;
    return result;
}
Community
  • 1
  • 1
Maxim Kholyavkin
  • 4,463
  • 2
  • 37
  • 82
  • where is COUNT_ARRAY_ELEMS macro defined? – Peter Lapisu Aug 03 '15 at 13:43
  • 1
    i suppose #define COUNT_ARRAY_ELEMS(arr) sizeof(arr)/sizeof(arr[0]) – Peter Lapisu Aug 03 '15 at 13:45
  • First of all, I don't really understand the code you wrote. Then I decided to have a test of it, the result stays the same but seems meaningless if I print the result as a readable time: "Wednesday, December 31, 1969 at 4:00:00 PM Pacific Standard Time." (Note the year: 1969) Did I get lose something? – Code Farmer Sep 29 '17 at 02:17
1

Refer to this category

NSDate+BootTime.h

#import <Foundation/Foundation.h>

@interface NSDate (BootTime)

+ (NSDate *)bootTime;

+ (NSTimeInterval)bootTimeTimeIntervalSinceReferenceDate;

@end

NSDate+BootTime.m

#import "NSDate+BootTime.h"

#include <sys/types.h>
#include <sys/sysctl.h>

@implementation NSDate (BootTime)

+ (NSDate *)bootTime {
    return [NSDate dateWithTimeIntervalSinceReferenceDate:[NSDate bootTimeTimeIntervalSinceReferenceDate]];
}

+ (NSTimeInterval)bootTimeTimeIntervalSinceReferenceDate {
    return getKernelTaskStartTime();
}

////////////////////////////////////////////////////////////////////////
#pragma mark - Private
////////////////////////////////////////////////////////////////////////

#define COUNT_ARRAY_ELEMS(arr) sizeof(arr)/sizeof(arr[0])

static CFAbsoluteTime getKernelTaskStartTime(void) {
    enum { MICROSECONDS_IN_SEC = 1000 * 1000 };
    struct kinfo_proc   info;
    bzero(&info, sizeof(info));

    // Initialize mib, which tells sysctl the info we want, in this case
    // we're looking for information about a specific process ID = 0.
    int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};

    // Call sysctl.
    size_t size = sizeof(info);
    const int sysctlResult = sysctl(mib, COUNT_ARRAY_ELEMS(mib), &info, &size, NULL, 0);
    if (sysctlResult != -1) {

        const struct timeval * timeVal = &(info.kp_proc.p_starttime);
        NSTimeInterval result = -kCFAbsoluteTimeIntervalSince1970;
        result += timeVal->tv_sec;
        result += timeVal->tv_usec / (double)MICROSECONDS_IN_SEC;
        return result;

    }

    return 0;
}

@end
l'L'l
  • 44,951
  • 10
  • 95
  • 146
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179