CNContact birthday displayed with a timezone gap

71 Views Asked by At

I'm trying to get the birthday of person that i fetch in the Contacts app using the ContactsCN Framework.

The person's birthday in the Contacts apps is June 22, 1980.

In my app, the actual result is June 21, 1980. The desired result is June 22, 1980, same as the birthday in the Contacts app.

The difference is the -4 hours timezone. I tried to play with timezones and different option on my formatters and I don't have any success. The content of the .description attribute of the date displays the right information "1980-06-22 00:00:00 +0000". How can i get the right date to display in my app ?

I tried may different scenarios :

  • no formating (but at the end, i need to format my date)
  • format without a time zone
  • format with TimeZone.current
  • format with TimeZone.gmt
  • format with TimeZone.init(secondsFromGMT: 14000)
  • format with TimeZone.init(secondsFromGMT: -14000)

Note : 14000 is the time difference between EST and GMT

I did the same test with the current date for comparison.

This is the code of my test

import SwiftUI
import ContactsUI

struct TestWithCNContactView: View {
    
    let contactStore = CNContactStore()
    @State private var searchName:String = "apple"
    @State private var contacts = [CNContact]()
    
    var body: some View {
        Text("locale : \(Locale.preferredLanguages[0]), timezone : \(TimeZone.current)").padding()
        TextField("Enter the name to find in Contacts", text: $searchName, onCommit: fetchContacts).padding()
        Divider()
        
        if contacts.count > 0 {
            ScrollView {
                VStack (alignment: .leading) {
                    ForEach(contacts, id: \.self) { contact in
                        if let date1 = contact.birthday?.date {
                            HStack {Text(contact.givenName)
                                Text(contact.familyName)}.font(.title).padding(.top)
                            testDisplayDate(title: "contact bday",date: date1)
                        }
                    }
                    Divider()
                    testDisplayDate(title: "Date()",date: Date())
                }.padding()
            }
        }
    }
    func fetchContacts()  {
        let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactBirthdayKey] as [CNKeyDescriptor]
        let predicate = CNContact.predicateForContacts(matchingName: searchName)
        contacts = try! contactStore.unifiedContacts(matching: predicate, keysToFetch: keysToFetch )
    }
}

struct testDisplayDate: View {
    var title: String
    var date: Date?
    
    var body: some View {
        Text(title).font(.title2).padding(.top)
        if let date = date {
            Text("date (no formatting) : \(date)")
            Text(".description : \(date.description)")
            Text("dateFormatter : \(date, formatter: dateFormatter)")
            Text("dateFormatterGMT : \(date, formatter: dateFormatterGTM)")
            Text("dateFormatterFull : \(date, formatter: dateFormatterFull)")
            Text("dateFormatterFullGMT : \(date, formatter: dateFormatterFullGMT)")
        } else {
            Text("no date")
        }
    }
    var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "d MMMM"
        formatter.timeZone = TimeZone.current
        return formatter
    }
    var dateFormatterGTM: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "d MMMM"
        formatter.timeZone = TimeZone.gmt
        return formatter
    }
    var dateFormatterFull: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm"
        formatter.timeZone = TimeZone.current
        return formatter
    }
    var dateFormatterFullGMT: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm"
        formatter.timeZone = TimeZone.gmt
        return formatter
    }
    
}

I included a picture with the person's information in the Contacts apps and the result of my test code.

test results and person info in Contacts

Any contribution will be appreciated.

Thanks

2

There are 2 best solutions below

1
On

I pursued my investigation. I found out that, for the attribute birthday of type DateComponents, the field timeZone is nil and it affects the result. So there is 2 ways to compensate for it :

  1. do an extension of CNContact and create your own attribute
extension CNContact {
    var birthDayCurrentTimeZone: DateComponents?  {
        if var bdayDC = birthday {
            bdayDC.timeZone = TimeZone.current
            return bdayDC
        } else {return self.birthday}
    }
}

and use it instead of birthday.

  1. calculate a date before using the date formatter
let bdayWithTimeZone = Calendar.current.date(byAdding: .hour, value: TimeZone.current.secondsFromGMT() / 3600 * -1, to: contact.birthday.date)!)

Thanks again

Pat

2
On

String's init(_:formatter:) seems to be ignoring TimeZone information. Instead, use DateFormatter's .string(from: Date) parameter.

...
var body: some View {
    Text(title).font(.title2).padding(.top)
    if let date = date {
        Text("date (no formatting) : \(date)")
        Text(".description : \(date.description)")
        Text("dateFormatter : \(dateFormatter.string(from: date))")
        Text("dateFormatterGMT : \(dateFormatterGTM.string(from: date))")
        Text("dateFormatterFull : \(dateFormatterFull.string(from: date))")
        Text("dateFormatterFullGMT : \(dateFormatterFullGMT.string(from: date))")
    } else {
        Text("no date")
    }
}
...

In the above code the output is as you'd expect.