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

后端 未结 12 1714
北恋
北恋 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:23

    Remember to set the locale to en_US_POSIX as described in Technical Q&A1480. In Swift 3:

    let date = Date()
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.timeZone = TimeZone(secondsFromGMT: 0)
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
    print(formatter.string(from: date))
    

    The issue is that if you're on a device which is using a non-Gregorian calendar, the year will not conform to RFC3339/ISO8601 unless you specify the locale as well as the timeZone and dateFormat string.

    Or you can use ISO8601DateFormatter to get you out of the weeds of setting locale and timeZone yourself:

    let date = Date()
    let formatter = ISO8601DateFormatter()
    formatter.formatOptions.insert(.withFractionalSeconds)  // this is only available effective iOS 11 and macOS 10.13
    print(formatter.string(from: date))
    

    For Swift 2 rendition, see previous revision of this answer.

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

    Swift 5

    If you're targeting iOS 11.0+ / macOS 10.13+, you simply use ISO8601DateFormatter with the withInternetDateTime and withFractionalSeconds options, like so:

    let date = Date()
    
    let iso8601DateFormatter = ISO8601DateFormatter()
    iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    let string = iso8601DateFormatter.string(from: date)
    
    // string looks like "2020-03-04T21:39:02.112Z"
    
    0 讨论(0)
  • 2020-11-21 23:28

    To further compliment Andrés Torres Marroquín and Leo Dabus, I have a version that preserves fractional seconds. I can't find it documented anywhere, but Apple truncate fractional seconds to the microsecond (3 digits of precision) on both input and output (even though specified using SSSSSSS, contrary to Unicode tr35-31).

    I should stress that this is probably not necessary for most use cases. Dates online do not typically need millisecond precision, and when they do, it is often better to use a different data format. But sometimes one must interoperate with a pre-existing system in a particular way.

    Xcode 8/9 and Swift 3.0-3.2

    extension Date {
        struct Formatter {
            static let iso8601: DateFormatter = {
                let formatter = DateFormatter()
                formatter.calendar = Calendar(identifier: .iso8601)
                formatter.locale = Locale(identifier: "en_US_POSIX")
                formatter.timeZone = TimeZone(identifier: "UTC")
                formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXXXX"
                return formatter
            }()
        }
    
        var iso8601: String {
            // create base Date format 
             var formatted = DateFormatter.iso8601.string(from: self)
    
            // Apple returns millisecond precision. find the range of the decimal portion
             if let fractionStart = formatted.range(of: "."),
                 let fractionEnd = formatted.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: formatted.endIndex) {
                 let fractionRange = fractionStart.lowerBound..<fractionEnd
                // replace the decimal range with our own 6 digit fraction output
                 let microseconds = self.timeIntervalSince1970 - floor(self.timeIntervalSince1970)
                 var microsecondsStr = String(format: "%.06f", microseconds)
                 microsecondsStr.remove(at: microsecondsStr.startIndex)
                 formatted.replaceSubrange(fractionRange, with: microsecondsStr)
            }
             return formatted
        }
    }
    
    extension String {
        var dateFromISO8601: Date? {
            guard let parsedDate = Date.Formatter.iso8601.date(from: self) else {
                return nil
            }
    
            var preliminaryDate = Date(timeIntervalSinceReferenceDate: floor(parsedDate.timeIntervalSinceReferenceDate))
    
            if let fractionStart = self.range(of: "."),
                let fractionEnd = self.index(fractionStart.lowerBound, offsetBy: 7, limitedBy: self.endIndex) {
                let fractionRange = fractionStart.lowerBound..<fractionEnd
                let fractionStr = self.substring(with: fractionRange)
    
                if var fraction = Double(fractionStr) {
                    fraction = Double(floor(1000000*fraction)/1000000)
                    preliminaryDate.addTimeInterval(fraction)
                }
            }
            return preliminaryDate
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:32

    Without some manual String masks or TimeFormatters

    import Foundation
    
    struct DateISO: Codable {
        var date: Date
    }
    
    extension Date{
        var isoString: String {
            let encoder = JSONEncoder()
            encoder.dateEncodingStrategy = .iso8601
            guard let data = try? encoder.encode(DateISO(date: self)),
            let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as?  [String: String]
                else { return "" }
            return json?.first?.value ?? ""
        }
    }
    
    let dateString = Date().isoString
    
    0 讨论(0)
  • 2020-11-21 23:33

    In my case I have to convert the DynamoDB - lastUpdated column (Unix Timestamp) to Normal Time.

    The initial value of lastUpdated was : 1460650607601 - converted down to 2016-04-14 16:16:47 +0000 via :

       if let lastUpdated : String = userObject.lastUpdated {
    
                    let epocTime = NSTimeInterval(lastUpdated)! / 1000 // convert it from milliseconds dividing it by 1000
    
                    let unixTimestamp = NSDate(timeIntervalSince1970: epocTime) //convert unix timestamp to Date
                    let dateFormatter = NSDateFormatter()
                    dateFormatter.timeZone = NSTimeZone()
                    dateFormatter.locale = NSLocale.currentLocale() // NSLocale(localeIdentifier: "en_US_POSIX")
                    dateFormatter.dateFormat =  "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
                    dateFormatter.dateFromString(String(unixTimestamp))
    
                    let updatedTimeStamp = unixTimestamp
                    print(updatedTimeStamp)
    
                }
    
    0 讨论(0)
  • 2020-11-21 23:36

    To complement the version of Leo Dabus, I added support for projects written Swift and Objective-C, also added support for the optional milliseconds, probably isn't the best but you would get the point:

    Xcode 8 and Swift 3

    extension Date {
        struct Formatter {
            static let iso8601: 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
            }()
        }
    
        var iso8601: String {
            return Formatter.iso8601.string(from: self)
        }
    }
    
    
    extension String {
        var dateFromISO8601: Date? {
            var data = self
            if self.range(of: ".") == nil {
                // Case where the string doesn't contain the optional milliseconds
                data = data.replacingOccurrences(of: "Z", with: ".000000Z")
            }
            return Date.Formatter.iso8601.date(from: data)
        }
    }
    
    
    extension NSString {
        var dateFromISO8601: Date? {
            return (self as String).dateFromISO8601
        }
    }
    
    0 讨论(0)
提交回复
热议问题