Convert a Date (Absolute Time) to Be Sent/Received Across the Network as Data in Swift

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"

How to convert `Date` to `Data`, and vice versa in Swift 5?

A Date is a juste a Timestamp (number of seconds since reference date of 1970 at UTC) as an object with fancy methods and other utilities. That's nothing more.

So based on the answer Double to Data (and reverse):

Input

let date = Date()
print(date)

Date -> Timestamp -> Data

let timestamp = date.timeIntervalSinceReferenceDate
print(timestamp)
let data = withUnsafeBytes(of: timestamp) { Data($0) }
print("\(data) - \(data.map{ String(format: "%02hhx", $0) }.joined())")

Data -> Timestamp -> Date

let retrievedTimestamp = data.withUnsafeBytes { $0.load(as: Double.self) }
print(retrievedTimestamp)
let retrievedDate = Date(timeIntervalSinceReferenceDate: retrievedTimestamp)
print(retrievedDate)

Output:

$>2021-09-13 08:17:50 +0000
$>1631521070.6852288
$>8 bytes - cadaab4bc24fd841
$>1631521070.6852288
$>2021-09-13 08:17:50 +0000

Conversion of dates in swift without loss

A Date is just a number of seconds, as a Double, since the reference date. (This is aliased to "TimeInterval," but it's just a Double.)

If you want it to be a string without losing any information, that's just the string form of the Double:

let nowString = "\(now.timeIntervalSinceReferenceDate)" // "595531191.461246"

And to convert it back, turn the Double into a Date:

let originalDate = Date(timeIntervalSinceReferenceDate: TimeInterval(nowString)!)
originalDate == now // true

You definitely don't want to remove the decimal point. That's an important part of the number.

how to get time difference form formatted time string values

You just need to get the minutes instead of hours and divide it by 60. Btw you should also set your dateFormatter's default date to today:

func timeDifferenceBetweenTwoTime(startTime: String, endTime: String) -> CGFloat {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.defaultDate = Calendar(identifier: .iso8601).startOfDay(for: Date())
dateFormatter.dateFormat = "hh:mm a"
if let startDate = dateFormatter.date(from: startTime),
var endDate = dateFormatter.date(from: endTime) {
if startDate > endDate {
endDate = Calendar.current.date(byAdding: .day, value: 1, to: endDate)!
}
let minute = Calendar.current.dateComponents([.minute], from: startDate, to: endDate).minute!
return CGFloat(minute) / 60
}
return 0
}


let startTime = "09:00 AM"
let endTime = "05:30 PM"
timeDifferenceBetweenTwoTime(startTime: startTime, endTime: endTime) // 8.5

Convert UInt32 to 4 bytes Swift

let value: UInt32 = 1
var u32LE = value.littleEndian // or simply value
let dataLE = Data(bytes: &u32LE, count: 4)
let bytesLE = Array(dataLE) // [1, 0, 0, 0]

var u32BE = value.bigEndian
let dataBE = Data(bytes: &u32BE, count: 4)
let bytesBE = Array(dataBE) // [0, 0, 0, 1]

Swift: Precision with timeIntervalSince1970

The issue there is that DateFormatter is limited to milliseconds. There is no way to display more than 3 fraction digits using it.

extension Formatter {
static let iso8601withFractionalSeconds: 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.SSSSSSSSSXXXXX"
return formatter
}()
}


Formatter.iso8601withFractionalSeconds.string(from: Date())  // "2020-10-29T16:12:27.111000000Z"

If you would like to get the initial value back you just need top get the timeIntervalSince1970 from your convertedTime date:

let epochTime: TimeInterval = 1597269862.9328
print("\(epochTime) Precise Epoch Time")

let convertedTime = Date(timeIntervalSince1970: epochTime)
print("\(convertedTime) Time converted To Swift Date")

print(convertedTime.timeIntervalSince1970) // "1597269862.9328\n"

Note that Swift Date is stored as the time interval since reference date. If you would like to preserve the date accuracy you should use timeIntervalSinceReferenceDate instead of timeIntervalSince1970 when archiving it. Check this post.

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.

Storing the data representations of multiple, differently typed objects in a single Data instance

The issue about misaligned data is that you need to use Data's subdata method. Besides that you can create some helpers to make your life easier as follow:

This would convert any numeric type to Data:

extension Numeric {
var data: Data {
var bytes = self
return .init(bytes: &bytes, count: MemoryLayout<Self>.size)
}
}

This would convert any type that conforms to String Protocol to Data (String/Substring)

extension StringProtocol {
var data: Data { .init(utf8) }
}

This would convert any valid utf8 encoded sequence of bytes (UInt8) to string

extension DataProtocol {
var string: String? { String(bytes: self, encoding: .utf8) }
}

This is a generic method to convert the bytes to object or to a collection (array) of objects:

extension ContiguousBytes {
func object<T>() -> T { withUnsafeBytes { $0.load(as: T.self) } }
func objects<T>() -> [T] { withUnsafeBytes { .init($0.bindMemory(to: T.self)) } }
}

and a simplified generic version to concatenate an array of data:

extension Collection where Element == DataProtocol {
var data: Data { .init(joined()) }
}

Usage:

let a: UInt8 = 39
let b: Int32 = -20001
let string: String = "How awesome is this data?!"
let data = [a.data, b.data, string.data].data

// just set the cursor (index) at the start position
var cursor = data.startIndex
// get the subdata from that position onwards
let loadedA: UInt8 = data.subdata(in: cursor..<data.endIndex).object() // 39
// advance your cursor for the next position
cursor = cursor.advanced(by: MemoryLayout<UInt8>.size)
// get your next object
let loadedB: Int32 = data.subdata(in: cursor..<data.endIndex).object() // -20001
// advance your position to the start of the string data
cursor = cursor.advanced(by: MemoryLayout<Int32>.size)
// load the subdata as string
let loadedString = data.subdata(in: cursor..<data.endIndex).string // "How awesome is this data?!"

edit/update:
Of course loading the string only works because it is located at the end of your collection of bytes otherwise you would need to use 8 bytes to store its size:

let a: UInt8 = 39
let b: Int32 = -20001
let string: String = "How awesome is this data?!"
let c: Int = .max
let data = [a.data, b.data, string.count.data, string.data, c.data].data

var cursor = data.startIndex
let loadedA: UInt8 = data.subdata(in: cursor..<data.endIndex).object() // 39
print(loadedA)
cursor = cursor.advanced(by: MemoryLayout<UInt8>.size)
let loadedB: Int32 = data.subdata(in: cursor..<data.endIndex).object() // -20001
print(loadedB)
cursor = cursor.advanced(by: MemoryLayout<Int32>.size)
let stringCount: Int = data.subdata(in: cursor..<data.endIndex).object()
print(stringCount)
cursor = cursor.advanced(by: MemoryLayout<Int>.size)
let stringEnd = cursor.advanced(by: stringCount)

if let loadedString = data.subdata(in: cursor..<stringEnd).string { // "How awesome is this data?!"
print(loadedString)
cursor = stringEnd
let loadedC: Int = data.subdata(in: cursor..<data.endIndex).object() // 9223372036854775807
print(loadedC)
}

This would print

39

-20001

26

How awesome is this data?!

9223372036854775807



Related Topics



Leave a reply



Submit