strftime_l from swift with specific NSDate

I'm struggling to figure out how to call strftime_l from swift with a specific date. If I just want the current moment, I can do this, found on Stack Overflow


let bufferSize = 255
var buffer = [Int8](count: bufferSize, repeatedValue: 0)
var timeValue = time(nil)
let tmValue = localtime(&timeValue)
strftime_l(&buffer, bufferSize, "%d %B %Y", tmValue, nil)
let dateFormat = String(CString: buffer, encoding: NSUTF8StringEncoding)!


But I can't figure out how to get my tmValue from an NSDate object. Also, I'm feeling like there should be an "withUnsafeMutablePointer" type block in here somewhere.


To answer the "Just use NSDateFormatter"...no. This code will be called tons of time where it's not convenient to pass in a date formatter, and so I don't want to constantly create them. Also, I need an EXACT date format spit out, and the documentation for NSDateFormatter says that setting the dateFormat property doesn't guarantee you'll get that exact format.

Accepted Reply

Try this:

import Foundation

func stringForDate(date: NSDate, includeTime: Bool = true) -> String {
    var buffer = [Int8](count: 17, repeatedValue: 0) 

    var time = time_t(date.timeIntervalSince1970)

    let format = includeTime ? "%Y%m%dT%H%M%SZ" : "%Y%m%d"
    strftime_l(&buffer, buffer.count, format, localtime(&time), nil) 
    return String.fromCString(buffer)!
}

print(stringForDate(NSDate(timeIntervalSinceReferenceDate: 0.0)))
// prints "20010101T000000Z"

The trick is to realise that

time_t
is a
__darwin_time_t
is an
Int
, so you can just initialise it from the
NSTimeInterval
(aka
Double
) returned by
timeIntervalSince1970
.

Be aware that Swift will trap if that conversion is out of bounds. That’s unlikely on 64-bit systems but something to watch out for if you deploy to iOS (which supports Swift on 32-bit systems).

To answer the "Just use NSDateFormatter"...no. This code will be called tons of time where it's not convenient to pass in a date formatter, and so I don't want to constantly create them. Also, I need an EXACT date format spit out, and the documentation for NSDateFormatter says that setting the dateFormat property doesn't guarantee you'll get that exact format.

While I don’t want to be seen advocating NSDateFormatter for this sort of thing, neither of your objections is a showstopper:

  • On modern systems NSDateFormatter is thread safe, so you can have a single global date formatter and use that everywhere.

    IMPORTANT For the details on the thread safety of NSDateFormatter, see NSDateFormatter Class Reference.

  • NSDateFormatter is intended to be used for localised dates, but it is possible to force it to create a fixed-format date. QA1480 NSDateFormatter and Internet Dates has the details.

Having said that,

strftime_l
is a good alternative, something that I pointed out at the bottom of QA1480.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

DTS will close for the winter holidays at the end of business on Wed, 23 Dec 2015 and re-open on Mon, 4 Jan 2016.

Replies

OK, this is what I came up with. It works, but not sure if it's 100% right.


        let bufferSize = 17
        var buffer = [Int8](count: bufferSize, repeatedValue: 0)

        let time = UnsafeMutablePointer<time_t>.alloc(1)
        time.initialize(Int(theDate.timeIntervalSince1970))

        let format = includeTime ? "%Y%m%dT%H%M%SZ" : "%Y%m%d"
        strftime_l(&buffer, bufferSize, format, localtime(time), nil)
        let dateFormat = String(CString: buffer, encoding: NSUTF8StringEncoding)!

        time.dealloc(1)
        time.destroy()

Try this:

import Foundation

func stringForDate(date: NSDate, includeTime: Bool = true) -> String {
    var buffer = [Int8](count: 17, repeatedValue: 0) 

    var time = time_t(date.timeIntervalSince1970)

    let format = includeTime ? "%Y%m%dT%H%M%SZ" : "%Y%m%d"
    strftime_l(&buffer, buffer.count, format, localtime(&time), nil) 
    return String.fromCString(buffer)!
}

print(stringForDate(NSDate(timeIntervalSinceReferenceDate: 0.0)))
// prints "20010101T000000Z"

The trick is to realise that

time_t
is a
__darwin_time_t
is an
Int
, so you can just initialise it from the
NSTimeInterval
(aka
Double
) returned by
timeIntervalSince1970
.

Be aware that Swift will trap if that conversion is out of bounds. That’s unlikely on 64-bit systems but something to watch out for if you deploy to iOS (which supports Swift on 32-bit systems).

To answer the "Just use NSDateFormatter"...no. This code will be called tons of time where it's not convenient to pass in a date formatter, and so I don't want to constantly create them. Also, I need an EXACT date format spit out, and the documentation for NSDateFormatter says that setting the dateFormat property doesn't guarantee you'll get that exact format.

While I don’t want to be seen advocating NSDateFormatter for this sort of thing, neither of your objections is a showstopper:

  • On modern systems NSDateFormatter is thread safe, so you can have a single global date formatter and use that everywhere.

    IMPORTANT For the details on the thread safety of NSDateFormatter, see NSDateFormatter Class Reference.

  • NSDateFormatter is intended to be used for localised dates, but it is possible to force it to create a fixed-format date. QA1480 NSDateFormatter and Internet Dates has the details.

Having said that,

strftime_l
is a good alternative, something that I pointed out at the bottom of QA1480.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

DTS will close for the winter holidays at the end of business on Wed, 23 Dec 2015 and re-open on Mon, 4 Jan 2016.

"Be aware that Swift will trap if that conversion is out of bounds. That’s unlikely on 64-bit systems but something to watch out for if you deploy to iOS (which supports Swift on 32-bit systems)."


NSDate(timeIntervalSince1970: Double(Int32.max)) returns January 18, 2038. I'm going to just assume by then we won't have 32 bit iphones, and even if we do, I wouldn't be supporting those old beasts 🙂


And for *this* application, it wouldn't make sense for somebody to schedule out that far as it's used for sports team game/practice type events.