Swift Why Isn't My Date Object That's (Equatable) Equal After Converting It to a String and Back

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.

Date comparison failing

A Date represents an absolute point in time, and includes fractional seconds (up to some precision, limited by the precision of TimeInterval aka Double, and probably other factors).

You can truncate the time interval to integral seconds, or use the proper Calendar methods:

let date = Date()
let date1 = Calendar.current.dateInterval(of: .second, for: date)!.start

print(date.timeIntervalSinceReferenceDate) // 571003391.256104
print(date1.timeIntervalSinceReferenceDate) // 571003391.0

You can also compare two dates with a “granularity” of whole seconds:

if Calendar.current.compare(currentDate, to: endDate, toGranularity: .second) != .orderedAscending { }

AnyObject try cast to Equatable

Easy way - class stay NonGeneric, Generic only init, and in GenericInit create isEquals method

class FieldItem: CustomStringConvertible, Equatable {
let value: Any?
let title: String
private let equals: (Any?) -> Bool
init<Value: Equatable>(title: String, value: Value?) {
func isEquals(_ other: Any?) -> Bool {
if let l = value, let r = other {
if let r = r as? Value {
return l == r
} else {
return false
}
} else {
return true
}
}
self.title = title
self.value = value
self.equals = isEquals
}
//CustomStringConvertible
var description: String { get { return title } }
//Equatable
public static func ==(lhs: FieldItem, rhs: FieldItem) -> Bool {
return ((lhs.title == rhs.title) && lhs.equals(rhs.value))
}

}

Swift 3 - Comparing Date objects

I have tried this snippet (in Xcode 8 Beta 6), and it is working fine.

let date1 = Date()
let date2 = Date().addingTimeInterval(100)

if date1 == date2 { ... }
else if date1 > date2 { ... }
else if date1 < date2 { ... }

Convert a Date (absolute time) to be sent/received across the network as Data in Swift?

You can send your Date converting it to Data (8-bytes floating point) and back to Date as follow:

extension Numeric {
var data: Data {
var source = self
return .init(bytes: &source, count: MemoryLayout<Self>.size)
}
init<D: DataProtocol>(_ data: D) {
var value: Self = .zero
let size = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(size == MemoryLayout.size(ofValue: value))
self = value
}
}

extension UInt64 {
var bitPattern: Double { .init(bitPattern: self) }
}

extension Date {
var data: Data { timeIntervalSinceReferenceDate.bitPattern.littleEndian.data }
init<D: DataProtocol>(data: D) {
self.init(timeIntervalSinceReferenceDate: data.timeIntervalSinceReferenceDate)
}
}

extension DataProtocol {
func value<N: Numeric>() -> N { .init(self) }
var uint64: UInt64 { value() }
var timeIntervalSinceReferenceDate: TimeInterval { uint64.littleEndian.bitPattern }
var date: Date { .init(data: self) }
}

Playground Testing

let date = Date()            // "Nov 15, 2019 at 12:13 PM"
let data = date.data // 8 bytes
print(Array(data)) // "[25, 232, 158, 22, 124, 191, 193, 65]\n"
let loadedDate = data.date // "Nov 15, 2019 at 12:13 PM"
print(date == loadedDate) // "true"

Why doesn't array conform to Equatable, when its items are Equatable in Swift?

Swift 4.1 update:

With the introduction of conditional conformance in Swift 4.1, Array now conforms to Equatable, so the issue should be resolved without the need to resort to any workarounds.

Also, Swift now allows a type to automatically synthesize Equatable conformance, provided all its members are Equatable, simply by declaring Equatable conformance as part of the original type definition (not an extension) but without implementing any of its requirements. This works for enums provided associated values, if any, are Equatable.

The code from this question can now be written much more concisely as below:

import Foundation

struct Post: Equatable {
let text: String
}

enum Result<T>: Equatable where T: Equatable {
case success(result: T)
case error
}

This code will pass all the tests specified in the question:

func test() {

// Test 1: Check Post type for equality: OK
let post1 = Post(text: "post")
let post2 = Post(text: "post")

if post1 == post2 {
print("equal posts")
}

// Test 2: Check [Post] type for equality: OK
let arrayOfPosts1 = [post1, post2]
let arrayOfPosts2 = [post1, post2]

if arrayOfPosts1 == arrayOfPosts2 {
print("equal arrays of post")
}

// Test 3: Check Result<Post> type for equality: OK
let result1 = Result<Post>.success(result: post1)
let result2 = Result<Post>.success(result: post2)

if result1 == result2 {
print("equal results of post")
}

// Test 4: Check Result<[Post]> type for equality: OK
let arrayResult1: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts1)
let arrayResult2: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts2)

if arrayResult1 == arrayResult2 {
print("equal results of array of posts")
}
}

Here is the output:

test()
/*
prints:
equal posts
equal arrays of post
equal results of post
equal results of array of posts
*/

Is there a date only (no time) class in Swift?

There is no date only class that's part of the Foundation framework.

This is a quick way to get a date only representation of an NSDate object:

let now = NSDate()

let dateFormatter = NSDateFormatter()
dateFormatter.timeStyle = NSDateFormatterStyle.NoStyle
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle

print(dateFormatter.stringFromDate(now)) // Mar 3, 2016

NSDate's always have times because a date is a single point in time. If you're so inclined you can create a date without a time component but it usually defaults to 12AM:

let dateString = "2016-03-03"
let dateFromStringFormatter = NSDateFormatter()
dateFromStringFormatter.dateFormat = "yyyy-MM-dd"

let dateFromString = dateFromStringFormatter.dateFromString(dateString)
// dateFromString shows "Mar 3, 2016, 12:00 AM"

For Swift 3.0+

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

// optional
let date = dateFormatter.date(from: "2016-03-03") // Mar 3, 2015 at 12:00 AM

parse JSON array in swift , sort it and find overlapping dates

Considering this is a follow up question from your previous post. You can use Swift DateInterval initialiser to create a DateInverval with your start and end dates and check if they intersects with each other:

extension Event {
var interval: DateInterval { .init(start: start, end: end) }
func intersects(with event: Event) -> Bool { interval.intersects(event.interval) }
}

To check for conflicting events you would need to make your Event conform to Equatable and filter the ones that intersects with them but are not the same event:

struct Event: Codable, Equatable {
let title: String
let start: Date
let end: Date
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .monthDayYearTime
do {
let events = try decoder.decode([Event].self, from: Data(json.utf8))
print(events.sorted())
let conflictingEvents: [(Event, Event)] = events.compactMap {
for event in events where event != $0 {
if event.intersects(with: $0) && $0.end != event.start && event.end != $0.start { return ($0, event) }
}
return nil
}
print(events.count) // "21\n"
print(conflictingEvents.count) // "11\n"
} catch {
print(error)
}


Related Topics



Leave a reply



Submit