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
Understanding Spritekit Collisionbitmask
What Is a Good Example to Differentiate Between Fileprivate and Private in Swift3
Custom Back Button For Navigationview'S Navigation Bar in Swiftui
Swift 3 For Loop With Increment
How to Encode Enum Using Nscoder in Swift
Ios 14 Swiftui Keyboard Lifts View Automatically
Swift Equality Operator on Nested Arrays
Does Swift Have Documentation Generation Support
Xcode 8 Beta 3: Expected ',' Joining Parts of a Multi-Clause Condition
Weak References in Swift Playground Don't Work as Expected
Deletable Table With Textfield on Swiftui
Storing Values in Completionhandlers - Swift