Last active
May 2, 2019 01:38
-
-
Save kristopherjohnson/5c75d92b8f2edc6f8686 to your computer and use it in GitHub Desktop.
Parsing or formatting an RFC 3339 timestamp using NSDateFormatter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In RFC 3339 we can find a note:
Does it cover as well date format without
T
eg:2016-09-21 21:05:10+00:00
?