1

I'm trying to save a date in Keychain, and (from my understanding) this first requires converting the Date object into a Data object.

This Stack Overflow Q/A explains how to do it (or at least part of it) in Swift 3, but I'm hoping someone can provide the to/from functions in Swift 5 (since at least one of the methods in that solution are deprecated and I'm worried something else hasn't stood the test of time either).

Eric
  • 569
  • 4
  • 21
  • 2
    Simple way: Since `Date` is just an object with highlever level method of a Timestamp (number of seconds since reference date 1970), just get that time timestamp( its a Double), and transform it into `Data`. See https://stackoverflow.com/questions/38023838/round-trip-swift-number-types-to-from-data ? – Larme Sep 13 '21 at 08:03
  • @Larme I like it. Thank you. – Eric Sep 13 '21 at 20:53
  • @Eric check this post on how to properly convert a date to data and preserve its nanoseconds as well as its endianness. https://stackoverflow.com/a/47502712/2303865 – Leo Dabus Sep 14 '21 at 13:46
  • [A Date value encapsulate a single point in time, independent of any particular calendrical system or time zone. Date values represent a time interval relative to an absolute reference date](https://developer.apple.com/documentation/foundation/date) – Leo Dabus Sep 14 '21 at 14:11

2 Answers2

8

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
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
Larme
  • 24,190
  • 6
  • 51
  • 81
  • Better to use timeIntervalSinceReferenceDate. If you use since 1970 there is no guarantee that if you convert it back to date it will be the exactly same date. – Leo Dabus Sep 14 '21 at 13:43
  • 1
    Isn't it the same? There is just "30 years" offset, no? – Larme Sep 14 '21 at 13:45
  • This won't be as precise as using `timeIntervalSinceReferenceDate`. Which is how it is stored under the hood. Check this post. https://stackoverflow.com/a/47502712/2303865 – Leo Dabus Sep 14 '21 at 13:46
  • [A Date value encapsulate a single point in time, independent of any particular calendrical system or time zone. Date values represent a time interval relative to an absolute reference date](https://developer.apple.com/documentation/foundation/date) – Leo Dabus Sep 14 '21 at 14:12
3

A "safer" (but less efficient) way to do this would be to use an Encoder/Decoder type, such as Foundations JSONEncoder/JSONDecoder. If the data gets corrupted in some way, these types will throw an Error, rather than trapping. You just need to ensure that the date encoding/decoding strategy is the same for each.

let myDate = Date()

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970

// TODO: handle errors correctly
let dateData = try! encoder.encode(myDate)
let retrievedDate = try! decoder.decode(Date.self, from: dateData)

assert(myDate == retrievedDate)
Bradley Mackey
  • 6,777
  • 5
  • 31
  • 45