Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Swift, how can I get an NSDate from a dispatch_time_t?

"Walltime" is a little-known time format used by Grand Central Dispatch. Apple talks about it here:

https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/

There are some things it's really handy for, though, but it's a sticky wicket. It's hard to make it play nice with other time formats, which is what my question's about.

I can make a walltime by turning an NSDate into a timespec, and then using with dispatch_walltime:

 let now = NSDate().timeIntervalSince1970
 let nowWholeSecsFloor = floor(now)
 let nowNanosOnly = now - nowWholeSecsFloor
 let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
 var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
 tv_nsec: Int(nowNanosFloor))
 let wallTime = dispatch_walltime(& thisStruct, 0)

But lord love a duck, I can't figure out how to get it back into an NSDate. Here's my try:

public func toNSDate(wallTime: dispatch_time_t)->NSDate {
    let wallTimeAsSeconds = Double(wallTime) / Double(NSEC_PER_SEC)
    let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
    return date
}

The resulting NSDate is not just off, but somewhat hilariously off, like five hundred years or something. As Martin R pointed out, the problem is that dispatch_time_t is an opaque value, with an undocumented representation of time.

Does anyone know how to do this?

EDIT: if the process of creating the walltime is confusing, this is basically what's going on:

NSDate defines time with a Double, and everything after the decimal point is the nanoseconds. dispatch_time, which can create a walltime, defines time with UInt64, so you have to convert between Double and UInt64 to use it. To do that conversion you need to use a timespec, which takes seconds and nanoseconds as separate arguments, each of which must be Int.

A whole lotta convertin' going on!

like image 285
Le Mot Juiced Avatar asked Aug 03 '15 16:08

Le Mot Juiced


1 Answers

The real answer is: you can't.

In the "time.h" header file it is stated:

/*!
 * @typedef dispatch_time_t
 *
 * @abstract
 * A somewhat abstract representation of time; where zero means "now" and
 * DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
 * opaque encoding.
 */
typedef uint64_t dispatch_time_t;

So dispatch_time_t uses an undocumented "abstract" representation of time, which may even change between releases.

That being said, let's have some fun and try to figure out what a dispatch_time_t really is. So we have a look at "time.c", which contains the implementation of dispatch_walltime():

dispatch_time_t
dispatch_walltime(const struct timespec *inval, int64_t delta)
{
    int64_t nsec;
    if (inval) {
        nsec = inval->tv_sec * 1000000000ll + inval->tv_nsec;
    } else {
        nsec = (int64_t)_dispatch_get_nanoseconds();
    }
    nsec += delta;
    if (nsec <= 1) {
        // -1 is special == DISPATCH_TIME_FOREVER == forever
        return delta >= 0 ? DISPATCH_TIME_FOREVER : (dispatch_time_t)-2ll;
    }
    return (dispatch_time_t)-nsec;
}

The interesting part is the last line: it takes the negative value of the nanoseconds, and this value is cast back to an (unsigned) dispatch_time_t. There are also some special cases.

Therefore, to reverse the conversion, we have to negate the dispatch_time_t and take that as nanoseconds:

public func toNSDate(wallTime: dispatch_time_t)->NSDate {

    // Tricky part HERE:
    let nanoSeconds = -Int64(bitPattern: wallTime)

    // Remaining part as in your question:
    let wallTimeAsSeconds = Double(nanoSeconds) / Double(NSEC_PER_SEC)
    let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
    return date
}

And indeed, this converts the walltime correctly back to the original NSDate, at least when I test it in an OS X application.

But again: don't do it! You would rely on an undocumented representation which could change between OS releases. There may also be special cases that are not considered in the above code. Also the representation in the iOS runtime could be different, I did not try that. You have been warned!

like image 143
Martin R Avatar answered Nov 13 '22 23:11

Martin R