Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Last active May 2, 2019 01:38
Show Gist options
  • Save kristopherjohnson/5c75d92b8f2edc6f8686 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/5c75d92b8f2edc6f8686 to your computer and use it in GitHub Desktop.
Parsing or formatting an RFC 3339 timestamp using NSDateFormatter
import Foundation
/// Parse RFC 3339 date string to NSDate
///
/// :param: rfc3339DateTimeString string with format "yyyy-MM-ddTHH:mm:ssZ"
/// :returns: NSDate, or nil if string cannot be parsed
public func dateForRFC3339DateTimeString(rfc3339DateTimeString: String) -> NSDate? {
let formatter = getThreadLocalRFC3339DateFormatter()
return formatter.dateFromString(rfc3339DateTimeString)
}
/// Generate RFC 3339 date string for an NSDate
///
/// :param: date NSDate
/// :returns: String
public func rfc3339DateTimeStringForDate(date: NSDate) -> String {
let formatter = getThreadLocalRFC3339DateFormatter()
return formatter.stringFromDate(date)
}
// Date formatters are not thread-safe, so use a thread-local instance
private func getThreadLocalRFC3339DateFormatter() -> NSDateFormatter {
return cachedThreadLocalObjectWithKey("net.kristopherjohnson.getThreadLocalRFC3339DateFormatter") {
let en_US_POSIX = NSLocale(localeIdentifier: "en_US_POSIX")
let rfc3339DateFormatter = NSDateFormatter()
rfc3339DateFormatter.locale = en_US_POSIX
rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssXXX"
rfc3339DateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
return rfc3339DateFormatter
}
}
/// Return a thread-local object, creating it if it has not already been created
///
/// :param: create closure that will be invoked to create the object
/// :returns: object of type T
private func cachedThreadLocalObjectWithKey<T: AnyObject>(key: String, create: () -> T) -> T {
if let threadDictionary = NSThread.currentThread().threadDictionary {
if let cachedObject = threadDictionary[key] as T? {
return cachedObject
}
else {
let newObject = create()
threadDictionary[key] = newObject
return newObject
}
}
else {
assert(false, "threadDictionary should never be nil")
// If we don't have a thread-local dictionary for some reason,
// then just call create() on every call
return create()
}
}
import UIKit
import XCTest
class RFC3339Test: XCTestCase {
let en_US_POSIX = NSLocale(localeIdentifier: "en_US_POSIX")
let utcTimeZone = NSTimeZone(forSecondsFromGMT: 0)
lazy var calendar: NSCalendar = {
let cal = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
cal.locale = self.en_US_POSIX
cal.timeZone = self.utcTimeZone
return cal
}()
let calendarUnits: NSCalendarUnit =
.YearCalendarUnit | .MonthCalendarUnit | .DayCalendarUnit |
.HourCalendarUnit | .MinuteCalendarUnit | .SecondCalendarUnit
func testUTCStringToDate() {
let string = "1967-01-17T16:34:25Z"
let optDate = dateForRFC3339DateTimeString(string)
XCTAssertNotNil(optDate)
if let date = optDate {
let components = calendar.components(calendarUnits, fromDate: date)
XCTAssertEqual(1967, components.year)
XCTAssertEqual(1, components.month)
XCTAssertEqual(17, components.day)
XCTAssertEqual(16, components.hour)
XCTAssertEqual(34, components.minute)
XCTAssertEqual(25, components.second)
}
}
func testESTExtendedStringToDate() {
let string = "1967-01-17T11:34:25-05:00"
let optDate = dateForRFC3339DateTimeString(string)
XCTAssertNotNil(optDate)
if let date = optDate {
let components = calendar.components(calendarUnits, fromDate: date)
XCTAssertEqual(1967, components.year)
XCTAssertEqual(1, components.month)
XCTAssertEqual(17, components.day)
XCTAssertEqual(16, components.hour)
XCTAssertEqual(34, components.minute)
XCTAssertEqual(25, components.second)
let newString = rfc3339DateTimeStringForDate(date)
XCTAssertEqual("1967-01-17T16:34:25Z", newString)
}
}
func testESTBasicFormatStringToDate() {
let string = "2001-01-02T10:43:42-0500"
let optDate = dateForRFC3339DateTimeString(string)
XCTAssertNotNil(optDate)
if let date = optDate {
let components = calendar.components(calendarUnits, fromDate: date)
XCTAssertEqual(2001, components.year)
XCTAssertEqual(1, components.month)
XCTAssertEqual(2, components.day)
XCTAssertEqual(15, components.hour)
XCTAssertEqual(43, components.minute)
XCTAssertEqual(42, components.second)
let newString = rfc3339DateTimeStringForDate(date)
XCTAssertEqual("2001-01-02T15:43:42Z", newString)
}
}
}
@peengle
Copy link

peengle commented Sep 29, 2016

In RFC 3339 we can find a note:

NOTE: ISO 8601 defines date and time separated by "T".
Applications using this syntax may choose, for the sake of
readability, to specify a full-date and full-time separated by
(say) a space character.

Does it cover as well date format without T eg: 2016-09-21 21:05:10+00:00 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment