105

Is there an easy way to get a time very precisely?

I need to calculate some delays between method calls. More specifically, I want to calculate the speed of scrolling in an UIScrollView.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thanks
  • 40,109
  • 71
  • 208
  • 322
  • [here](http://stackoverflow.com/questions/12760388/how-to-set-a-variable-that-represents-a-time-in-the-future-in-absolute-terms-obj) is a very much related question that may also help understand the answers here.. please take a look! – abbood Oct 06 '12 at 13:54
  • 1
    Possible duplicate of [How to log a method's execution time exactly in milliseconds?](https://stackoverflow.com/questions/2129794/how-to-log-a-methods-execution-time-exactly-in-milliseconds) – Cœur Jan 28 '19 at 12:50
  • Related: [Get current date in milliseconds](https://stackoverflow.com/questions/6150422/get-current-date-in-milliseconds) – jrh Jun 26 '19 at 01:52

11 Answers11

128

NSDate and the timeIntervalSince* methods will return a NSTimeInterval which is a double with sub-millisecond accuracy. NSTimeInterval is in seconds, but it uses the double to give you greater precision.

In order to calculate millisecond time accuracy, you can do:

// Get a current time for where you want to start measuring from
NSDate *date = [NSDate date];

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
double timePassed_ms = [date timeIntervalSinceNow] * -1000.0;

Documentation on timeIntervalSinceNow.

There are many other ways to calculate this interval using NSDate, and I would recommend looking at the class documentation for NSDate which is found in NSDate Class Reference.

garafajon
  • 1,278
  • 13
  • 15
Jeff Thompson
  • 2,662
  • 2
  • 18
  • 17
  • 3
    Actually this is precise enough for the general use case. – logancautrell Jul 02 '12 at 12:30
  • 4
    Is it safe to use NSDates to calculate elapsed time? Seems to me that the system time can go forward or backward when syncing to external time sources, so you wouldn't be able to trust the result. You really want a monotonically increasing clock, don't you? – Kristopher Johnson Sep 19 '12 at 11:12
  • 1
    I just compared `NSDate` and `mach_absolute_time()` at around 30ms level. 27 vs. 29, 36 vs. 39, 43 vs. 45. `NSDate` was easier to use for me and the results were similar enough to not care. – nevan king Jul 31 '13 at 20:56
  • 8
    It is not safe to use NSDate to compare elapsed time, since the system clock can change at any time (due to NTP, DST transitions, leap seconds, and many other reasons). Use mach_absolute_time instead. – nevyn May 21 '15 at 02:21
  • @nevyn you just saved my internet speed test, ty! Damn I'm glad I read the comments before copy pasting code heh – Albert Renshaw Oct 31 '17 at 00:01
42

mach_absolute_time() can be used to get precise measurements.

See http://developer.apple.com/qa/qa2004/qa1398.html

Also available is CACurrentMediaTime(), which is essentially the same thing but with an easier-to-use interface.

(Note: This answer was written in 2009. See Pavel Alexeev's answer for the simpler POSIX clock_gettime() interfaces available in newer versions of macOS and iOS.)

Kristopher Johnson
  • 81,409
  • 55
  • 245
  • 302
  • 1
    In that code is a weird conversion - last line of the first example is "return * (uint64_t *) &elapsedNano;" why not just "return (uint64_t)elapsedNano" ? – Tyler Dec 29 '10 at 23:00
  • 8
    Core Animation (QuartzCore.framework) also provides a convenience method, `CACurrentMediaTime()`, that converts `mach_absolute_time()` directly into a `double`. – otto Feb 27 '13 at 04:28
  • 1
    @Tyler, `elapsedNano` is of type `Nanoseconds`, which is not a plain integer type. It's an alias of `UnsignedWide`, which is a struct with two 32-bit integer fields. You can use `UnsignedWideToUInt64()` instead of the cast if you prefer. – Ken Thomases Aug 24 '13 at 00:14
  • CoreServices is only a Mac library. Is there an equivalent in iOS? – mm24 Jul 14 '16 at 13:24
  • 1
    @mm24 On iOS, use CACurrentMediaTime(), which is in Core Animation. – Kristopher Johnson Jul 14 '16 at 14:18
30

Please do not use NSDate, CFAbsoluteTimeGetCurrent, or gettimeofday to measure elapsed time. These all depend on the system clock, which can change at any time due to many different reasons, such as network time sync (NTP) updating the clock (happens often to adjust for drift), DST adjustments, leap seconds, and so on.

This means that if you're measuring your download or upload speed, you will get sudden spikes or drops in your numbers that don't correlate with what actually happened; your performance tests will have weird incorrect outliers; and your manual timers will trigger after incorrect durations. Time might even go backwards, and you end up with negative deltas, and you can end up with infinite recursion or dead code (yeah, I've done both of these).

Use mach_absolute_time. It measures real seconds since the kernel was booted. It is monotonically increasing (will never go backwards), and is unaffected by date and time settings. Since it's a pain to work with, here's a simple wrapper that gives you NSTimeIntervals:

// LBClock.h
@interface LBClock : NSObject
+ (instancetype)sharedClock;
// since device boot or something. Monotonically increasing, unaffected by date and time settings
- (NSTimeInterval)absoluteTime;

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute;
@end

// LBClock.m
#include <mach/mach.h>
#include <mach/mach_time.h>

@implementation LBClock
{
    mach_timebase_info_data_t _clock_timebase;
}

+ (instancetype)sharedClock
{
    static LBClock *g;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g = [LBClock new];
    });
    return g;
}

- (id)init
{
    if(!(self = [super init]))
        return nil;
    mach_timebase_info(&_clock_timebase);
    return self;
}

- (NSTimeInterval)machAbsoluteToTimeInterval:(uint64_t)machAbsolute
{
    uint64_t nanos = (machAbsolute * _clock_timebase.numer) / _clock_timebase.denom;

    return nanos/1.0e9;
}

- (NSTimeInterval)absoluteTime
{
    uint64_t machtime = mach_absolute_time();
    return [self machAbsoluteToTimeInterval:machtime];
}
@end
nevyn
  • 7,052
  • 3
  • 32
  • 43
  • 2
    Thanks. What about `CACurrentMediaTime()` from QuartzCore? – Cœur Nov 26 '15 at 05:56
  • 1
    Note: one can also use `@import Darwin;` instead of `#include ` – Cœur Nov 26 '15 at 06:03
  • @Cœur CACurrentMediaTime should actually be exactly equivalent to my code. Good find! (Header says "This is the result of calling mach_absolute_time () and converting the units to seconds") – nevyn Nov 28 '15 at 08:41
  • "can change at any time due to many different reasons, such as network time sync (NTP) updating the clock (happens often to adjust for drift), DST adjustments, leap seconds, and so on." - What other reasons could that be? I'm observing backward jumps of about 50 ms while having no internet connection. So, apparently, none of these reasons should apply... – Falko Aug 04 '17 at 03:45
  • @Falko not sure, but if I'd have to guess, I'd wager the OS does drift compensation on its own if it notices different hardware clocks being out of sync? – nevyn Aug 31 '17 at 23:32
  • @Falko Even without an internet connection, the cellular provider can provide an updated time. As the phone's clock drifts, it will continually need to readjust. – Edward Brey Oct 05 '20 at 00:49
16

CFAbsoluteTimeGetCurrent() returns the absolute time as a double value, but I don't know what its precision is -- it might only update every dozen milliseconds, or it might update every microsecond, I don't know.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • Returns the time in seconds, so not ok for TS – Fortega May 20 '09 at 18:12
  • @Fortega: It returns a double value, not an integer, so it has sub-second accuracy – Adam Rosenfield May 20 '09 at 18:20
  • 2
    It's a double-precision floating-point value though, and does provide sub-millisecond accuracy. A value of 72.89674947369 seconds is not uncommon... – Jim Dovey May 20 '09 at 18:21
  • 26
    @JimDovey: I would have to say a value of `72.89674947369` seconds would be pretty uncommon, considering all the other values it could be. ;) – FreeAsInBeer Feb 24 '12 at 20:21
  • 3
    @Jim: Do you have a citation that it provides sub-millisecond accuracy (this is an honest question)? I do hope everyone here understands the differences between [accuracy and precision](http://en.wikipedia.org/wiki/Accuracy_and_precision). – Adam Rosenfield Feb 26 '12 at 16:28
  • 5
    CFAbsoluteTimeGetCurrent() calls gettimeofday() on OS X and GetSystemTimeAsFileTime() on Windows. Here's [the source code](http://opensource.apple.com/source/CF/CF-635.19/CFDate.c). – Jim Dovey Feb 27 '12 at 20:59
  • 3
    Oh, and gettimeofday() is implemented using the Mach nanosecond timer via [mach_absolute_time()](https://developer.apple.com/library/mac/#qa/qa1398/_index.html); here's the source for the common-page implementation of gettimeofday() on Darwin/ARM: http://opensource.apple.com/source/Libc/Libc-763.12/arm/sys/arm_commpage_gettimeofday.c – Jim Dovey Feb 27 '12 at 21:13
  • `CFAbsoluteTimeGetCurrent()` depends on system clock which can be altered at any time, potentially resulting in wrong or even negative time intervals. Use `mach_absolute_time` instead to get correct elapsed time. – Cœur Nov 25 '15 at 12:22
  • 1
    [`CFAbsoluteTimeGetCurrent` docs](https://developer.apple.com/documentation/corefoundation/1543542-cfabsolutetimegetcurrent) say, "Repeated calls to this function do not guarantee monotonically increasing results. The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock." Use [`CACurrentMediaTime`](https://developer.apple.com/documentation/quartzcore/1395996-cacurrentmediatime) if you don't want to suffer this behavior. (Or see [Technical Q&A 1398](https://developer.apple.com/library/content/qa/qa1398/_index.html).) – Rob Aug 07 '17 at 17:20
10

I would NOT use mach_absolute_time() because it queries a combination of the kernel and the processor for an absolute time using ticks (probably an uptime).

What I would use:

CFAbsoluteTimeGetCurrent();

This function is optimized to correct the difference in the iOS and OSX software and hardware.

Something Geekier

The quotient of a difference in mach_absolute_time() and AFAbsoluteTimeGetCurrent() is always around 24000011.154871

Here is a log of my app:

Please note that final result time is a difference in CFAbsoluteTimeGetCurrent()'s

 2012-03-19 21:46:35.609 Rest Counter[3776:707] First Time: 353900795.609040
 2012-03-19 21:46:36.360 Rest Counter[3776:707] Second Time: 353900796.360177
 2012-03-19 21:46:36.361 Rest Counter[3776:707] Final Result Time (difference): 0.751137
 2012-03-19 21:46:36.363 Rest Counter[3776:707] Mach absolute time: 18027372
 2012-03-19 21:46:36.365 Rest Counter[3776:707] Mach absolute time/final time: 24000113.153295
 2012-03-19 21:46:36.367 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:43.074 Rest Counter[3776:707] First Time: 353900803.074637
 2012-03-19 21:46:43.170 Rest Counter[3776:707] Second Time: 353900803.170256
 2012-03-19 21:46:43.172 Rest Counter[3776:707] Final Result Time (difference): 0.095619
 2012-03-19 21:46:43.173 Rest Counter[3776:707] Mach absolute time: 2294833
 2012-03-19 21:46:43.175 Rest Counter[3776:707] Mach absolute time/final time: 23999753.727777
 2012-03-19 21:46:43.177 Rest Counter[3776:707] -----------------------------------------------------
 2012-03-19 21:46:46.499 Rest Counter[3776:707] First Time: 353900806.499199
 2012-03-19 21:46:55.017 Rest Counter[3776:707] Second Time: 353900815.016985
 2012-03-19 21:46:55.018 Rest Counter[3776:707] Final Result Time (difference): 8.517786
 2012-03-19 21:46:55.020 Rest Counter[3776:707] Mach absolute time: 204426836
 2012-03-19 21:46:55.022 Rest Counter[3776:707] Mach absolute time/final time: 23999996.639500
 2012-03-19 21:46:55.024 Rest Counter[3776:707] -----------------------------------------------------
Kristian Glass
  • 37,325
  • 7
  • 45
  • 73
Nate Symer
  • 2,185
  • 1
  • 20
  • 27
  • I ended up using `mach_absolute_time()` with a `mach_timebase_info_data` and then did `(long double)((mach_absolute_time()*time_base.numer)/((1000*1000)*time_base.denom));`. To get the mach timebase, you can do `(void)mach_timebase_info(&your_timebase);` – Nate Symer May 19 '12 at 20:06
  • 15
    According to the docs for CFAbsoluteTimeGetCurrent(), "The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock." I don't understand why anyone would want to use something like this to measure elapsed time, if it can go backwards. – Kristopher Johnson Oct 09 '13 at 12:11
7

Functions based on mach_absolute_time are good for short measurements.
But for long measurements important caveat is that they stop ticking while device is asleep.

There is a function to get time since boot. It doesn't stop while sleeping. Also, gettimeofday is not monotonic, but in my experiments I've always see that boot time changes when system time changes, so I think it should work fine.

func timeSinceBoot() -> TimeInterval
{
    var bootTime = timeval()
    var currentTime = timeval()
    var timeZone = timezone()

    let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 2)
    mib[0] = CTL_KERN
    mib[1] = KERN_BOOTTIME
    var size = MemoryLayout.size(ofValue: bootTime)

    var timeSinceBoot = 0.0

    gettimeofday(&currentTime, &timeZone)

    if sysctl(mib, 2, &bootTime, &size, nil, 0) != -1 && bootTime.tv_sec != 0 {
        timeSinceBoot = Double(currentTime.tv_sec - bootTime.tv_sec)
        timeSinceBoot += Double(currentTime.tv_usec - bootTime.tv_usec) / 1000000.0
    }
    return timeSinceBoot
}

And since iOS 10 and macOS 10.12 we can use CLOCK_MONOTONIC:

if #available(OSX 10.12, *) {
    var uptime = timespec()
    if clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) == 0 {
        return Double(uptime.tv_sec) + Double(uptime.tv_nsec) / 1000000000.0
    }
}

To sum it up:

  • Date.timeIntervalSinceReferenceDate — changes when system time changes, not monotonic
  • CFAbsoluteTimeGetCurrent() — not monotonic, may go backward
  • CACurrentMediaTime() — stops ticking when device is asleep
  • timeSinceBoot() — doesn't sleep, but might be not monotonic
  • CLOCK_MONOTONIC — doesn't sleep, monotonic, supported since iOS 10
Pavel Alexeev
  • 6,026
  • 4
  • 43
  • 51
5
#define CTTimeStart() NSDate * __date = [NSDate date]
#define CTTimeEnd(MSG) NSLog(MSG " %g",[__date timeIntervalSinceNow]*-1)

Usage:

CTTimeStart();
...
CTTimeEnd(@"that was a long time:");

Output:

2013-08-23 15:34:39.558 App-Dev[21229:907] that was a long time: .0023
gngrwzrd
  • 5,902
  • 4
  • 43
  • 56
  • `NSDate` depends on system clock which can be altered at any time, potentially resulting in wrong or even negative time intervals. Use `mach_absolute_time` instead to get correct elapsed time. – Cœur Nov 25 '15 at 12:19
  • `mach_absolute_time` can be affected by device reboots. Use server-time instead. – kelin Dec 26 '15 at 22:15
5

Also, here is how to calculate a 64-bit NSNumber initialized with the Unix epoch in milliseconds, in case that is how you want to store it in CoreData. I needed this for my app which interacts with a system that stores dates this way.

  + (NSNumber*) longUnixEpoch {
      return [NSNumber numberWithLongLong:[[NSDate date] timeIntervalSince1970] * 1000];
  }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
John Wright
  • 2,418
  • 4
  • 29
  • 34
1

I know this is an old one but even I found myself wandering past it again, so I thought I'd submit my own option here.

Best bet is to check out my blog post on this: Timing things in Objective-C: A stopwatch

Basically, I wrote a class that does stop watching in a very basic way but is encapsulated so that you only need to do the following:

[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];

And you end up with:

MyApp[4090:15203]  -> Stopwatch: [My Timer] runtime: [0.029]

in the log...

Again, check out my post for a little more or download it here: MMStopwatch.zip

bladnman
  • 2,591
  • 1
  • 25
  • 20
  • Your implementation is not recommended. `NSDate` depends on system clock which can be altered at any time, potentially resulting in wrong or even negative time intervals. Use `mach_absolute_time` instead to get correct elapsed time. – Cœur Nov 25 '15 at 12:26
0

You can get current time in milliseconds since January 1st, 1970 using an NSDate:

- (double)currentTimeInMilliseconds {
    NSDate *date = [NSDate date];
    return [date timeIntervalSince1970]*1000;
}
Camilo Ortegón
  • 3,414
  • 3
  • 26
  • 34
-2

For those we need the Swift version of the answer of @Jeff Thompson:

// Get a current time for where you want to start measuring from
var date = NSDate()

// do work...

// Find elapsed time and convert to milliseconds
// Use (-) modifier to conversion since receiver is earlier than now
var timePassed_ms: Double = date.timeIntervalSinceNow * -1000.0

I hope this help you.

Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
  • 2
    `NSDate` depends on system clock which can be altered at any time, potentially resulting in wrong or even negative time intervals. Use `mach_absolute_time` instead to get correct elapsed time. – Cœur Nov 25 '15 at 12:27