Search code examples
iosswiftdateformatter

What is the best way to use DateFormatters in UITableViews cellForRowAt method


I have a table view which has few cells. I need to display the date in a human-readable format whose raw format is retrieved from the API call. I am using the below code to call the convertToDate function which takes care of taking raw date string of format "yyyy-MM-dd'T'HH:mm:ssX" and converting it to "MMMM dd, yyyy 'at' hh:mm a". However as we know Date formatters can be expensive. Can anyone please help me answer a few questions looking at the below code implementation?

Note : I am calling the function convertToDate from my cellForRowAt method inside tableView

  1. Is it good practice to create a singleton class so that only one Dateformatter instance is created ( so it is less expensive )

  2. I am also creating a DispatchQueue which is concurrent so that Dateformatter is thread-safe. Is that good practice?

  3. Will calling convertToDate with convertToDate which is concurrent cause any lag in performance when scroll table views?

  4. Are there any standard references to handle/create Dateformatter in general?

Below code converts: "2023-12-22T13:17:27Z" to "December 22, 2023 at 01:17 PM"

Code

import Foundation

class DateUtils {
    
    static let shared = DateUtils()
    let dateFormatter = DateFormatter()
    
    // Date formatter are not thread safe and adding this to a concurrent queue makes if safer.
    // For table views cells this may have minimal impact and needs to have performance tested in different cases where used.
    private let dateFormatterQueue = DispatchQueue(label: "com.example.dateformatter",
                                                   attributes: .concurrent)
    
    private init(){}
    
    func convertToDate(from date: String,
                       with format: String = "yyyy-MM-dd'T'HH:mm:ssX",
                       to: String = "MMMM dd, yyyy 'at' hh:mm a") -> String? {
        
        var formattedDate: String?
        
        dateFormatter.dateFormat = format
        guard let date = dateFormatter.date(from: date) else {
            return nil
        }
        dateFormatter.dateFormat = to
        
        dateFormatterQueue.sync {
            formattedDate = dateFormatter.string(from: date)
        }
        return formattedDate
    }
}

Solution

    1. No, a static property in an extension of DateFormatter would be the better choice.
    2. No.
    3. No.
    4. No Dateformatter needed

    The API call returns most likely JSON.

    In the model define the property representing the date

    let date: Date
    

    and add the .iso8601 strategy to the decoder to convert the ISO string to Date on the fly

    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    

    In the cell format the date the modern way

    cell.date = item.date.formatted( Date.FormatStyle()
                         .year()
                         .month(.wide)
                         .day(.twoDigits)
                         .hour(.twoDigits(amPM: .abbreviated))
                         .minute()
                         .locale(.init(identifier: "en_US")))
    

    The style can also be defined externally.