How can I parse / create a date time stamp formatted with fractional seconds UTC timezone (ISO 8601, RFC 3339) in Swift?

后端 未结 12 1709
北恋
北恋 2020-11-21 22:43

How to generate a date time stamp, using the format standards for ISO 8601 and RFC 3339?

The goal is a string that looks like this:

\"2015-01-01T00:0         


        
相关标签:
12条回答
  • 2020-11-21 23:38

    In the future the format might need to be changed which could be a small head ache having date.dateFromISO8601 calls everywhere in an app. Use a class and protocol to wrap the implementation, changing the date time format call in one place will be simpler. Use RFC3339 if possible, its a more complete representation. DateFormatProtocol and DateFormat is great for dependency injection.

    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        internal static let rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
        internal static let localeEnUsPosix = "en_US_POSIX"
    }
    
    import Foundation
    
    protocol DateFormatProtocol {
    
        func format(date: NSDate) -> String
        func parse(date: String) -> NSDate?
    
    }
    
    
    import Foundation
    
    class DateFormat:  DateFormatProtocol {
    
        func format(date: NSDate) -> String {
            return date.rfc3339
        }
    
        func parse(date: String) -> NSDate? {
            return date.rfc3339
        }
    
    }
    
    
    extension NSDate {
    
        struct Formatter {
            static let rfc3339: NSDateFormatter = {
                let formatter = NSDateFormatter()
                formatter.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierISO8601)
                formatter.locale = NSLocale(localeIdentifier: AppDelegate.localeEnUsPosix)
                formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
                formatter.dateFormat = rfc3339DateFormat
                return formatter
            }()
        }
    
        var rfc3339: String { return Formatter.rfc3339.stringFromDate(self) }
    }
    
    extension String {
        var rfc3339: NSDate? {
            return NSDate.Formatter.rfc3339.dateFromString(self)
        }
    }
    
    
    
    class DependencyService: DependencyServiceProtocol {
    
        private var dateFormat: DateFormatProtocol?
    
        func setDateFormat(dateFormat: DateFormatProtocol) {
            self.dateFormat = dateFormat
        }
    
        func getDateFormat() -> DateFormatProtocol {
            if let dateFormatObject = dateFormat {
    
                return dateFormatObject
            } else {
                let dateFormatObject = DateFormat()
                dateFormat = dateFormatObject
    
                return dateFormatObject
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 23:39

    There is a new ISO8601DateFormatter class that let's you create a string with just one line. For backwards compatibility I used an old C-library. I hope this is useful for someone.

    Swift 3.0

    extension Date {
        var iso8601: String {
            if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
                return ISO8601DateFormatter.string(from: self, timeZone: TimeZone.current, formatOptions: .withInternetDateTime)
            } else {
                var buffer = [CChar](repeating: 0, count: 25)
                var time = time_t(self.timeIntervalSince1970)
                strftime_l(&buffer, buffer.count, "%FT%T%z", localtime(&time), nil)
                return String(cString: buffer)
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:39

    Based on the acceptable answer in an object paradigm

    class ISO8601Format
    {
        let format: ISO8601DateFormatter
    
        init() {
            let format = ISO8601DateFormatter()
            format.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
            format.timeZone = TimeZone(secondsFromGMT: 0)!
            self.format = format
        }
    
        func date(from string: String) -> Date {
            guard let date = format.date(from: string) else { fatalError() }
            return date
        }
    
        func string(from date: Date) -> String { return format.string(from: date) }
    }
    
    
    class ISO8601Time
    {
        let date: Date
        let format = ISO8601Format() //FIXME: Duplication
    
        required init(date: Date) { self.date = date }
    
        convenience init(string: String) {
            let format = ISO8601Format() //FIXME: Duplication
            let date = format.date(from: string)
            self.init(date: date)
        }
    
        func concise() -> String { return format.string(from: date) }
    
        func description() -> String { return date.description(with: .current) }
    }
    

    callsite

    let now = Date()
    let time1 = ISO8601Time(date: now)
    print("time1.concise(): \(time1.concise())")
    print("time1: \(time1.description())")
    
    
    let time2 = ISO8601Time(string: "2020-03-24T23:16:17.661Z")
    print("time2.concise(): \(time2.concise())")
    print("time2: \(time2.description())")
    
    0 讨论(0)
  • 2020-11-21 23:41

    Uses ISO8601DateFormatter on iOS10 or newer.

    Uses DateFormatter on iOS9 or older.

    Swift 4

    protocol DateFormatterProtocol {
        func string(from date: Date) -> String
        func date(from string: String) -> Date?
    }
    
    extension DateFormatter: DateFormatterProtocol {}
    
    @available(iOS 10.0, *)
    extension ISO8601DateFormatter: DateFormatterProtocol {}
    
    struct DateFormatterShared {
        static let iso8601: DateFormatterProtocol = {
            if #available(iOS 10, *) {
                return ISO8601DateFormatter()
            } else {
                // iOS 9
                let formatter = DateFormatter()
                formatter.calendar = Calendar(identifier: .iso8601)
                formatter.locale = Locale(identifier: "en_US_POSIX")
                formatter.timeZone = TimeZone(secondsFromGMT: 0)
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
                return formatter
            }
        }()
    }
    
    0 讨论(0)
  • 2020-11-21 23:43

    If you want to use the ISO8601DateFormatter() with a date from a Rails 4+ JSON feed (and don't need millis of course), you need to set a few options on the formatter for it to work right otherwise the the date(from: string) function will return nil. Here's what I'm using:

    extension Date {
        init(dateString:String) {
            self = Date.iso8601Formatter.date(from: dateString)!
        }
    
        static let iso8601Formatter: ISO8601DateFormatter = {
            let formatter = ISO8601DateFormatter()
            formatter.formatOptions = [.withFullDate,
                                              .withTime,
                                              .withDashSeparatorInDate,
                                              .withColonSeparatorInTime]
            return formatter
        }()
    }
    

    Here's the result of using the options verses not in a playground screenshot:

    0 讨论(0)
  • 2020-11-21 23:47

    Swift 4 • iOS 11.2.1 or later

    extension ISO8601DateFormatter {
        convenience init(_ formatOptions: Options, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!) {
            self.init()
            self.formatOptions = formatOptions
            self.timeZone = timeZone
        }
    }
    

    extension Formatter {
        static let iso8601withFractionalSeconds = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
    }
    

    extension Date {
        var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
    }
    

    extension String {
        var iso8601withFractionalSeconds: Date? { return Formatter.iso8601withFractionalSeconds.date(from: self) }
    }
    

    Usage:

    Date().description(with: .current)  //  Tuesday, February 5, 2019 at 10:35:01 PM Brasilia Summer Time"
    let dateString = Date().iso8601withFractionalSeconds   //  "2019-02-06T00:35:01.746Z"
    
    if let date = dateString.iso8601withFractionalSeconds {
        date.description(with: .current) // "Tuesday, February 5, 2019 at 10:35:01 PM Brasilia Summer Time"
        print(date.iso8601withFractionalSeconds)           //  "2019-02-06T00:35:01.746Z\n"
    }
    

    iOS 9 • Swift 3 or later

    extension Formatter {
        static let iso8601withFractionalSeconds: DateFormatter = {
            let formatter = DateFormatter()
            formatter.calendar = Calendar(identifier: .iso8601)
            formatter.locale = Locale(identifier: "en_US_POSIX")
            formatter.timeZone = TimeZone(secondsFromGMT: 0)
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
            return formatter
        }()
    }
    

    Codable Protocol

    If you need to encode and decode this format when working with Codable protocol you can create your own custom date encoding/decoding strategies:

    extension JSONDecoder.DateDecodingStrategy {
        static let iso8601withFractionalSeconds = custom {
            let container = try $0.singleValueContainer()
            let string = try container.decode(String.self)
            guard let date = Formatter.iso8601withFractionalSeconds.date(from: string) else {
                throw DecodingError.dataCorruptedError(in: container,
                      debugDescription: "Invalid date: " + string)
            }
            return date
        }
    }
    

    and the encoding strategy

    extension JSONEncoder.DateEncodingStrategy {
        static let iso8601withFractionalSeconds = custom {
            var container = $1.singleValueContainer()
            try container.encode(Formatter.iso8601withFractionalSeconds.string(from: $0))
        }
    }
    

    Playground Testing

    let dates = [Date()]   // ["Feb 8, 2019 at 9:48 PM"]
    

    encoding

    let encoder = JSONEncoder()
    encoder.dateEncodingStrategy = .iso8601withFractionalSeconds
    let data = try! encoder.encode(dates)
    print(String(data: data, encoding: .utf8)!)
    

    decoding

    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601withFractionalSeconds
    let decodedDates = try! decoder.decode([Date].self, from: data)  // ["Feb 8, 2019 at 9:48 PM"]
    

    0 讨论(0)
提交回复
热议问题