如何配置 DateFormatter 以捕获微秒
How to configure DateFormatter to capture microseconds
iOS Date()
returns 精度至少为微秒的日期。
我通过调用 Date().timeIntervalSince1970
检查了这条语句,结果是 1490891661.074981
然后我需要将日期转换为精度为微秒的字符串。
我按以下方式使用 DateFormatter
:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZZ"
print(formatter.string(from: date))
这导致
"2017-03-30T16:34:21.075000Z"
现在如果我们比较两个结果:
1490891661.074981
和 "2017-03-30T16:34:21.075000Z"
我们可以注意到 DateFormatter
将日期舍入到毫秒精度,同时仍然以微秒为单位显示零。
有人知道如何配置 DateFormatter
以便我可以保持微秒并获得正确的结果:"2017-03-30T16:34:21.074981Z"
?
(NS)DateFormatter
的分辨率限制在毫秒,比较
NSDateFormatter milliseconds bug。一个可能的解决方案是检索所有日期组件(最多
纳秒)作为数字并进行自定义字符串格式设置。日期格式化程序仍可用于时区字符串。
示例:
let date = Date(timeIntervalSince1970: 1490891661.074981)
let formatter = DateFormatter()
formatter.dateFormat = "ZZZZZ"
let tzString = formatter.string(from: date)
let cal = Calendar.current
let comps = cal.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond],
from: date)
let microSeconds = lrint(Double(comps.nanosecond!)/1000) // Divide by 1000 and round
let formatted = String(format: "%04ld-%02ld-%02ldT%02ld:%02ld:%02ld.%06ld",
comps.year!, comps.month!, comps.day!,
comps.hour!, comps.minute!, comps.second!,
microSeconds) + tzString
print(formatted) // 2017-03-30T18:34:21.074981+02:00
感谢@MartinR 解决了我的问题的前半部分,感谢@ForestKunecke 为我提供了解决问题后半部分的技巧。
在他们的帮助下,我创建了即用型解决方案,该解决方案以微秒精度从字符串转换日期,反之亦然:
public final class MicrosecondPrecisionDateFormatter: DateFormatter {
private let microsecondsPrefix = "."
override public init() {
super.init()
locale = Locale(identifier: "en_US_POSIX")
timeZone = TimeZone(secondsFromGMT: 0)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func string(from date: Date) -> String {
dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let components = calendar.dateComponents(Set([Calendar.Component.nanosecond]), from: date)
let nanosecondsInMicrosecond = Double(1000)
let microseconds = lrint(Double(components.nanosecond!) / nanosecondsInMicrosecond)
// Subtract nanoseconds from date to ensure string(from: Date) doesn't attempt faulty rounding.
let updatedDate = calendar.date(byAdding: .nanosecond, value: -(components.nanosecond!), to: date)!
let dateTimeString = super.string(from: updatedDate)
let string = String(format: "%@.%06ldZ",
dateTimeString,
microseconds)
return string
}
override public func date(from string: String) -> Date? {
dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
guard let microsecondsPrefixRange = string.range(of: microsecondsPrefix) else { return nil }
let microsecondsWithTimeZoneString = String(string.suffix(from: microsecondsPrefixRange.upperBound))
let nonDigitsCharacterSet = CharacterSet.decimalDigits.inverted
guard let timeZoneRangePrefixRange = microsecondsWithTimeZoneString.rangeOfCharacter(from: nonDigitsCharacterSet) else { return nil }
let microsecondsString = String(microsecondsWithTimeZoneString.prefix(upTo: timeZoneRangePrefixRange.lowerBound))
guard let microsecondsCount = Double(microsecondsString) else { return nil }
let dateStringExludingMicroseconds = string
.replacingOccurrences(of: microsecondsString, with: "")
.replacingOccurrences(of: microsecondsPrefix, with: "")
guard let date = super.date(from: dateStringExludingMicroseconds) else { return nil }
let microsecondsInSecond = Double(1000000)
let dateWithMicroseconds = date + microsecondsCount / microsecondsInSecond
return dateWithMicroseconds
}
}
用法:
let formatter = MicrosecondPrecisionDateFormatter()
let date = Date(timeIntervalSince1970: 1490891661.074981)
let formattedString = formatter.string(from: date) // 2017-03-30T16:34:21.074981Z
@Vlad Papko 的解决方案有一些问题:
对于如下日期:
2019-02-01T00:01:54.3684Z
它可以使字符串带有额外的零:
2019-02-01T00:01:54.03684Z
这是固定的解决方案,它很丑陋,但没有问题:
override public func string(from date: Date) -> String {
dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let components = calendar.dateComponents(Set([Calendar.Component.nanosecond]), from: date)
let nanosecondsInMicrosecond = Double(1000)
let microseconds = lrint(Double(components.nanosecond!) / nanosecondsInMicrosecond)
// Subtract nanoseconds from date to ensure string(from: Date) doesn't attempt faulty rounding.
let updatedDate = calendar.date(byAdding: .nanosecond, value: -(components.nanosecond!), to: date)!
let dateTimeString = super.string(from: updatedDate)
let stingWithMicroseconds = "\(date.timeIntervalSinceReferenceDate)"
let dotIndex = stingWithMicroseconds.lastIndex(of: ".")!
let hasZero = stingWithMicroseconds[stingWithMicroseconds.index(after: dotIndex)] == "0"
let format = hasZero ? "%@.%06ldZ" : "%@.%6ldZ"
let string = String(format: format,
dateTimeString,
microseconds)
return string
}
有点hack,但没那么复杂,100% Swift:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.'MICROS'xx"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// Get the number of microseconds with a precision of 6 digits
let now = Date()
let dateParts = Calendar.current.dateComponents([.nanosecond], from: now)
let microSeconds = Int((Double(dateParts.nanosecond!) / 1000).rounded(.toNearestOrEven))
let microSecPart = String(microSeconds).padding(toLength: 6, withPad: "0", startingAt: 0)
// Format the date and add in the microseconds
var timestamp = dateFormatter.string(from: now)
timestamp = timestamp.replacingOccurrences(of: "MICROS", with: microSecPart)
iOS Date()
returns 精度至少为微秒的日期。
我通过调用 Date().timeIntervalSince1970
检查了这条语句,结果是 1490891661.074981
然后我需要将日期转换为精度为微秒的字符串。
我按以下方式使用 DateFormatter
:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZZZ"
print(formatter.string(from: date))
这导致
"2017-03-30T16:34:21.075000Z"
现在如果我们比较两个结果:
1490891661.074981
和 "2017-03-30T16:34:21.075000Z"
我们可以注意到 DateFormatter
将日期舍入到毫秒精度,同时仍然以微秒为单位显示零。
有人知道如何配置 DateFormatter
以便我可以保持微秒并获得正确的结果:"2017-03-30T16:34:21.074981Z"
?
(NS)DateFormatter
的分辨率限制在毫秒,比较
NSDateFormatter milliseconds bug。一个可能的解决方案是检索所有日期组件(最多
纳秒)作为数字并进行自定义字符串格式设置。日期格式化程序仍可用于时区字符串。
示例:
let date = Date(timeIntervalSince1970: 1490891661.074981)
let formatter = DateFormatter()
formatter.dateFormat = "ZZZZZ"
let tzString = formatter.string(from: date)
let cal = Calendar.current
let comps = cal.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond],
from: date)
let microSeconds = lrint(Double(comps.nanosecond!)/1000) // Divide by 1000 and round
let formatted = String(format: "%04ld-%02ld-%02ldT%02ld:%02ld:%02ld.%06ld",
comps.year!, comps.month!, comps.day!,
comps.hour!, comps.minute!, comps.second!,
microSeconds) + tzString
print(formatted) // 2017-03-30T18:34:21.074981+02:00
感谢@MartinR 解决了我的问题的前半部分,感谢@ForestKunecke 为我提供了解决问题后半部分的技巧。
在他们的帮助下,我创建了即用型解决方案,该解决方案以微秒精度从字符串转换日期,反之亦然:
public final class MicrosecondPrecisionDateFormatter: DateFormatter {
private let microsecondsPrefix = "."
override public init() {
super.init()
locale = Locale(identifier: "en_US_POSIX")
timeZone = TimeZone(secondsFromGMT: 0)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func string(from date: Date) -> String {
dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let components = calendar.dateComponents(Set([Calendar.Component.nanosecond]), from: date)
let nanosecondsInMicrosecond = Double(1000)
let microseconds = lrint(Double(components.nanosecond!) / nanosecondsInMicrosecond)
// Subtract nanoseconds from date to ensure string(from: Date) doesn't attempt faulty rounding.
let updatedDate = calendar.date(byAdding: .nanosecond, value: -(components.nanosecond!), to: date)!
let dateTimeString = super.string(from: updatedDate)
let string = String(format: "%@.%06ldZ",
dateTimeString,
microseconds)
return string
}
override public func date(from string: String) -> Date? {
dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
guard let microsecondsPrefixRange = string.range(of: microsecondsPrefix) else { return nil }
let microsecondsWithTimeZoneString = String(string.suffix(from: microsecondsPrefixRange.upperBound))
let nonDigitsCharacterSet = CharacterSet.decimalDigits.inverted
guard let timeZoneRangePrefixRange = microsecondsWithTimeZoneString.rangeOfCharacter(from: nonDigitsCharacterSet) else { return nil }
let microsecondsString = String(microsecondsWithTimeZoneString.prefix(upTo: timeZoneRangePrefixRange.lowerBound))
guard let microsecondsCount = Double(microsecondsString) else { return nil }
let dateStringExludingMicroseconds = string
.replacingOccurrences(of: microsecondsString, with: "")
.replacingOccurrences(of: microsecondsPrefix, with: "")
guard let date = super.date(from: dateStringExludingMicroseconds) else { return nil }
let microsecondsInSecond = Double(1000000)
let dateWithMicroseconds = date + microsecondsCount / microsecondsInSecond
return dateWithMicroseconds
}
}
用法:
let formatter = MicrosecondPrecisionDateFormatter()
let date = Date(timeIntervalSince1970: 1490891661.074981)
let formattedString = formatter.string(from: date) // 2017-03-30T16:34:21.074981Z
@Vlad Papko 的解决方案有一些问题:
对于如下日期:
2019-02-01T00:01:54.3684Z
它可以使字符串带有额外的零:
2019-02-01T00:01:54.03684Z
这是固定的解决方案,它很丑陋,但没有问题:
override public func string(from date: Date) -> String {
dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let components = calendar.dateComponents(Set([Calendar.Component.nanosecond]), from: date)
let nanosecondsInMicrosecond = Double(1000)
let microseconds = lrint(Double(components.nanosecond!) / nanosecondsInMicrosecond)
// Subtract nanoseconds from date to ensure string(from: Date) doesn't attempt faulty rounding.
let updatedDate = calendar.date(byAdding: .nanosecond, value: -(components.nanosecond!), to: date)!
let dateTimeString = super.string(from: updatedDate)
let stingWithMicroseconds = "\(date.timeIntervalSinceReferenceDate)"
let dotIndex = stingWithMicroseconds.lastIndex(of: ".")!
let hasZero = stingWithMicroseconds[stingWithMicroseconds.index(after: dotIndex)] == "0"
let format = hasZero ? "%@.%06ldZ" : "%@.%6ldZ"
let string = String(format: format,
dateTimeString,
microseconds)
return string
}
有点hack,但没那么复杂,100% Swift:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.'MICROS'xx"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// Get the number of microseconds with a precision of 6 digits
let now = Date()
let dateParts = Calendar.current.dateComponents([.nanosecond], from: now)
let microSeconds = Int((Double(dateParts.nanosecond!) / 1000).rounded(.toNearestOrEven))
let microSecPart = String(microSeconds).padding(toLength: 6, withPad: "0", startingAt: 0)
// Format the date and add in the microseconds
var timestamp = dateFormatter.string(from: now)
timestamp = timestamp.replacingOccurrences(of: "MICROS", with: microSecPart)