Home > Enterprise >  How to convert Swift Date into Byte Array?
How to convert Swift Date into Byte Array?

Time:06-17

I'm trying to send date information to a BLE device as part of pairing process. The BLE device documentation says that my application should set time and date to the BLE device at pairing mode, which I'm guessing is how the bond is set? (please correct me if I am wrong. Nowhere in the documentation does it say how to send encryption keys or anything of the sort).

The BLE documentation says it requires to receive 7 bytes

Name Byte Format Value
Year 2 16bit 07DD~08CF
Month 1 8bit 01~0C
Day 1 8bit 01~1F
Hours 1 8bit 00~18
Minutes 1 8bit 00~3B
Second 1 8bit 00~3B

When I use nRF connect to attempt connection with the BLE device, I see the Date UUID (0x2A08), and when I retrieve the value, it gives me 0xDD-07-01-01-00-12-09 in Byte Array (Hex) format, or in Date: Tue, Jan 1, 2013, 00:18:09

I followed this post to be able to convert date object into byte array.

But when I do:

let date = Date()
let array = Array(date.data)

I get back:

2022-06-17 04:05:51  0000
[0, 0, 128, 143, 26, 46, 196, 65]

I think the timezone 0000 is adding onto the byte array, but I am not sure how to remove that timezone part of the date. Even using DateFormatter to specify yyyy-MM-dd HH:mm:ss gives me the timezone.

Also, I know very very little about bytes and all that stuff, but the array of bytes printed above doesnt look like the 8bit format that my BLE documentation requests.

Is there any way that I can better retrieve current Date (or custom set time) into 8bit byte array that I can write back to the BLE device?

Thank you

CodePudding user response:

You can create a method to convert FixedWidthInteger to bytes (big or little endian) for the year component, convert the other date componentes to bytes and append them to the result:

enum Endianess {
    case big, little
}

extension FixedWidthInteger {
    func data<D: DataProtocol & RangeReplaceableCollection>(
        using endianess: Endianess = .big
    ) -> D {
        withUnsafeBytes(of: endianess == .big ? bigEndian : littleEndian, D.init)
    }
}

extension Calendar {
    static let iso8601 = Self(identifier: .iso8601)
}

extension Date {
    func component(_ component: Calendar.Component, using calendar: Calendar = .iso8601) -> Int {
        calendar.component(component, from: self)
    }
    func data<D: DataProtocol & RangeReplaceableCollection>() -> D {
        var dataProtocol: D = .init()
        dataProtocol  = UInt16(component(.year)).data() as D
        dataProtocol.append(UInt8(component(.month)))
        dataProtocol.append(UInt8(component(.day)))
        dataProtocol.append(UInt8(component(.hour)))
        dataProtocol.append(UInt8(component(.minute)))
        dataProtocol.append(UInt8(component(.second)))
        return dataProtocol
    }
}

Usage:

let bytes: [UInt8] = Date().data()
print(bytes)  

This will print

[7, 230, 6, 17, 2, 10, 46]

CodePudding user response:

First you need to get clear on the byte ordering so you can convert the 2-byte year into an array of bytes. I will assume that BLE expects byte ordering in big endian format. If not, you will have to change this up:

extension UInt16 {
    var fromLocalToBLE: UInt16 {
        CFSwapInt16HostToBig(self)
    }
    var fromBLEToLocal: UInt16 {
        CFSwapInt16BigToHost(self)
    }
}

To convert a Date to a byte array you need to start with the year. We extract the year, cast it to a UInt16, convert it to a byte array, then swap the byte ordering if necessary. For the remaining components just cast them to a UInt8 and store them at the tail of the byte array.

extension Date {
    func bleData(calendar: Calendar) -> [UInt8] {

        func byte(_ c: Calendar.Component) -> UInt8 {
            return UInt8(calendar.component(c, from: self))
        }

        let yearBytes: [UInt8] = {
            let year = UInt16(calendar.component(.year, from: self)).fromLocalToBLE
            return withUnsafeBytes(of: year) { pointer in
                pointer.map { $0 }
            }
        }()

        return yearBytes   [
            byte(.month),
            byte(.day),
            byte(.hour),
            byte(.minute),
            byte(.second),
        ]
    }
}

If you want to convert a byte array to a Date extract the first 16-bits for the year and swap the bytes if necessary. Take the year and all the remaining bytes to construct your DateComponents. Once you have that you can convert them to a Date.

extension Date {
    init(bleData: [UInt8], calendar: Calendar) {

        let year = bleData.withUnsafeBytes { src -> Int in
            var year: UInt16 = 0
            withUnsafeMutableBytes(of: &year) { dest in
                _ = src.copyBytes(to: dest, count: 2)
            }
            return Int(year.fromBLEToLocal)
        }

        let comp = DateComponents(
            year: year,
            month: Int(bleData[2]),
            day: Int(bleData[3]),
            hour: Int(bleData[4]),
            minute: Int(bleData[5]),
            second: Int(bleData[6])
        )

        self = calendar.date(from: comp)!
    }
}

You can do a round-trip test as follows:

    let origDate = Date()
    let origDateBytes = origDate.bleData(calendar: .current)

    print(origDateBytes.map { String(format: "0xX", $0) })

    let reverseDate = Date(bleData: origDateBytes, calendar: .current)

    func showDate(name: String, date: Date) {
        let df = DateFormatter()
        df.dateStyle = .medium
        df.timeStyle = .medium
        print(name, df.string(from: date))
    }

    showDate(name: "Orig:", date: origDate)
    showDate(name: "Reversed:", date: reverseDate)

Prints:

["0x07", "0xE6", "0x06", "0x11", "0x01", "0x21", "0x18"]
Orig: Jun 17, 2022 at 1:33:24 AM
Reversed: Jun 17, 2022 at 1:33:24 AM
  • Related