Search code examples
iosswiftnsdateformatterdateformatter

Is there a better way to format Date with DateFormatter according to user's "Date & Time" preference?


My use-case requires me to display different format of strings with respect to user's 12-hour/24-hour preference in "Date & Time" settings.

To be precise, my string needs to ignore the minute component and include the "AM/PM" suffix for 12-hour time and do the exact opposite for the 24-hour time.

I recently got to know about using "jj" template to achieve this. More on this (Thanks to @larme)

Here's my approach:

    let df = DateFormatter()
    df.setLocalizedDateFormatFromTemplate("jj:mm") // As of current time in my locale, this'll display "17:01"
    df.locale = .current
    
    if df.string(from: passedDate).count > 5 {
        // User has 12-hour time setting i.e: The string has AM/PM suffix
        df.setLocalizedDateFormatFromTemplate("jj") // Setting this will ignore the minutes and provide me with the hour and the AM/PM suffix i.e: "5 PM" according to current time
    }

Now, this solves my problem but I wonder if there is a cleaner way to get this done.


Solution

  • You can use DateFormatter static method dateFormat(fromTemplate tmplate: String, options opts: Int, locale: Locale?) -> String? passing j format and .current locale and check if it contains "a":

    extension DateFormatter {
        static var is24Hour: Bool {
            dateFormat(fromTemplate: "j", options: 0, locale: .current)?.contains("a") == false
        }
    }
    

    extension Formatter {
        static let customHour: DateFormatter = {
            let dateFormatter = DateFormatter()
            dateFormatter.setLocalizedDateFormatFromTemplate("jj")
            return dateFormatter
        }()
    }
    

    extension Date {
        var customHour: String { Formatter.customHour.string(from: self) }
    }
    

    DateFormatter.is24Hour  // false
    Date().customHour       // "11 AM"
    


    Note that there is no need to check if 24hour setting is on or not unless the user changes it after the formatter has been initialized. If want to make sure it reflects this as well:


    extension Formatter {
        static let date = DateFormatter()
    }
    

    extension Date {
        var customHour: String {
            Formatter.date.setLocalizedDateFormatFromTemplate("jj")
            return Formatter.date.string(from: self)
        }
    }
    

    Date().customHour       // "11 AM"