How to Resolve Error in Unit Testing When We Have Date Comparison in Codable

Swift Why isn't my date object that's (Equatable) equal after converting it to a string and back?

The problem there is that Date is stored as a FloatingPoint value (timeIntervalSinceReferenceDate). There is fractional seconds being discarded there when converting your Date to String and back to Date. Take a look at post.

Unit testing conformance to Decodable using a SingleValueDecodingContainer

I gave up trying to accomplish this without creating a wrapping type on the assumption that it's very hard to decode a string that isn't valid JSON to begin with ('1' in my example).

So, I guess, the answer is: just create a wrapping type. ¯\_(ツ)_/¯

Swift DateFormatter doesn't convert date according to preset Dateformat

Here's an alternative solution to this question, which is suitable for a broader context of application. This solution is based on the suggestions of my mentor on the Exercism website.

My thanks also go to Leo Dabus who suggested that I add time zone information to the date formatter to avoid the effects of the implicitly adopted default local time zone.

The code is as follows:

import Foundation

struct Gigasecond {
public let gigasecond : Double = 1_000_000_000
private let rfc3339DateFormatter : DateFormatter = {
let myFormatter = DateFormatter()
myFormatter.timeZone = TimeZone(identifier: "UTC")
myFormatter.locale = Locale(identifier: "en_US_POSIX")
myFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"

return myFormatter
}()
let description : String

init?(from dateString: String) {

let sourceDate = rfc3339DateFormatter.date(from: dateString)
guard let srcDate = sourceDate else {
description = ""
return
}
let destinationDate = Date(timeInterval: gigasecond, since: srcDate)
description = rfc3339DateFormatter.string(from: destinationDate)
}
}

Please let me know if there's anything I can do to improve the above code. Thank you ver much.

Swift Codable Decode Manually Optional Variable

Age is optional:

let age: String? 

So try to decode in this way:

let age: String? = try values.decodeIfPresent(String.self, forKey: .age)

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 from dateFromString::

    (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:25
  • CFDateFormatterCreateDateFromString() 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 uses UDate 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


Related Topics



Leave a reply



Submit