NSDateFormatter milliseconds bug
It seems that NSDateFormatter
works only with millisecond resolution, for the
following reasons:
By setting a breakpoint in
CFDateFormatterCreateDateFromString
, one can
see that this function is called fromdateFromString:
:(lldb) bt
* thread #1: tid = 0x26d03f, 0x018f47d0 CoreFoundation`CFDateFormatterCreateDateFromString, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x018f47d0 CoreFoundation`CFDateFormatterCreateDateFromString
frame #1: 0x0116e0ea Foundation`getObjectValue + 248
frame #2: 0x0116dfc7 Foundation`-[NSDateFormatter getObjectValue:forString:errorDescription:] + 206
frame #3: 0x0116879f Foundation`-[NSDateFormatter dateFromString:] + 71
* frame #4: 0x00002d56 foo`main(argc=1, argv=0xbfffee54) + 182 at main.mm:25CFDateFormatterCreateDateFromString()
is from
CFDateFormatter.c
which is open source. One can see that all calendrical calculations are made using the
ICU Calendar Classes.It is stated in calendar.h that
Calendar
usesUDate
which has a millisecond resolution:/**
* <code>Calendar</code> is an abstract base class for converting between
* a <code>UDate</code> object and a set of integer fields such as
* <code>YEAR</code>, <code>MONTH</code>, <code>DAY</code>, <code>HOUR</code>,
* and so on. (A <code>UDate</code> object represents a specific instant in
* time with millisecond precision. See UDate
* for information about the <code>UDate</code> class.)
* ...
How to configure DateFormatter to capture microseconds
Thanks to @MartinR for solving first half of my problem and to @ForestKunecke for giving me tips how to solve second half of the problem.
Based on their help I created ready to use solution which converts date from string and vice versa with microsecond precision:
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
}
}
Usage:
let formatter = MicrosecondPrecisionDateFormatter()
let date = Date(timeIntervalSince1970: 1490891661.074981)
let formattedString = formatter.string(from: date) // 2017-03-30T16:34:21.074981Z
Format string for NSDateFormatter to produce milliseconds
Try to use 'SS' specifier (with number of S's equal to number of digits you want to get - 3 in your case), e.g.
[formatter setDateFormat:@"yyyy/MM/dd hh:mm:ss:SSS"];
Swift DateFormatter Optional Milliseconds
Two suggestions:
Convert the string with the date format including the milliseconds. If it returns
nil
convert it with the other format.Strip the milliseconds from the string with Regular Expression:
var dateString = "2018-01-21T20:11:20.057Z"
dateString = dateString.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
// -> 2018-01-21T20:11:20Z
Edit:
To use it with Codable
you have to write a custom initializer, specifying dateDecodingStrategy
does not work
struct Foo: Decodable {
let birthDate : Date
let name : String
private enum CodingKeys : String, CodingKey { case born, name }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var rawDate = try container.decode(String.self, forKey: .born)
rawDate = rawDate.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression)
birthDate = ISO8601DateFormatter().date(from: rawDate)!
name = try container.decode(String.self, forKey: .name)
}
}
let jsonString = """
[{"name": "Bob", "born": "2018-01-21T20:11:20.057Z"}, {"name": "Matt", "born": "2018-01-21T20:11:20Z"}]
"""
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode([Foo].self, from: data)
print(result)
} catch {
print("error: ", error)
}
Swift full date with milliseconds
Updated for Swift 3
let d = Date()
let df = DateFormatter()
df.dateFormat = "y-MM-dd H:mm:ss.SSSS"
df.string(from: d) // -> "2016-11-17 17:51:15.1720"
When you have a Date
d
, you can get the formatted string using a NSDateFormatter. You can also use a formatter to turn a string date based on your format into a Date
See this chart for more on what dateFormat can do http://waracle.net/iphone-nsdateformatter-date-formatting-table/
How to parse ISO 8601 using NSDateFormatter with optional milliseconds part
As far as I know there is no way to make optional parameters.
The usual solution is to use two formatters, one for each format.
To decide which formatter to use, you can either
Count the number of characters in the date string (as suggested in Parsing a RFC 822 date with NSDateFormatter)
Just try both formatters and get the first non-
nil
result.
Since your date formats are similar, you can go with only one formatter and if the date string is too short, append .000
before using the formatter.
Date to milliseconds and back to date in Swift
I don't understand why you're doing anything with strings...
extension Date {
var millisecondsSince1970:Int64 {
Int64((self.timeIntervalSince1970 * 1000.0).rounded())
}
init(milliseconds:Int64) {
self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
}
}
Date().millisecondsSince1970 // 1476889390939
Date(milliseconds: 0) // "Dec 31, 1969, 4:00 PM" (PDT variant of 1970 UTC)
NSDateFormatter and default locale cause bug
The problem solved by setting the formatter's locale to en_US_POSIX
. It is necessary while parsing fixed-format date strings because the format of the date strings is constant unlike device' locale.
The code should be like
+(NSDate*)dateTimeFromJSONDateString:(NSString*)dateString{
static NSDateFormatter *_dateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZ"];
});
if (string)
return [_dateFormatter dateFromString:string];
else
return nil;
}
Getting current time of day down to milliseconds error
You can use the code below:
let formatter = NSDateFormatter()
//formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) // you can set GMT time
formatter.timeZone = NSTimeZone.localTimeZone() // or as local time
formatter.dateFormat = "HH:mm:ss.SSS"
println(formatter.stringFromDate(NSDate()))
Output: "05:57:26.123"
Related Topics
Send Messages Between iOS and Watchos with Watchconnectivity in Watchos2
Firebase with Swift 3 Counting the Number of Children
Pull to Refresh Uitableview Without Uitableviewcontroller
Since Xcode 8 and iOS10, Views Are Not Sized Properly on Viewdidlayoutsubviews
Auto Layout Uiscrollview with Subviews with Dynamic Heights
How to Convert an Int to Hex String in Swift
Wkwebview in Interface Builder
Record Audio and Save Permanently in iOS
Getting Client Certificate to Work for Mutual Authentication Using Swift 3 and Alamofire 4
Swift 2.0 Sorting Array of Objects by Property
How to Present a Uiviewcontroller from Skscene
Xcode - Bundle Format Unrecognized, Invalid, or Unsuitable
What Exactly Can Corebluetooth Applications Do Whilst in the Background
How to Change Text on a Back Button
Enterprise In-House App Distribution
How to Set a Uitableview to Grouped Style
Centering Subview's X in Autolayout Throws "Not Prepared for the Constraint"