0

I understand that in iOS/Swift creating DateFormatters and setting .dateFormats are expensive, I've read a bunch of different takes here on SO and in blogs, but am still unsure of the best way to efficiently deal with DateFormatter and .dateFormat. Specifically I'm working with an app that roughly mimics Apple's iPhone Weather app UI. Dates arrive via API in timeIntervalSince1970/Unix format. On each view controller I'll use one .dateFormat for the current weather e.g. "EEEE, MMM dd, y", another for formatting date in each of 7 cell in the daily weather table view e.g. "EEEE", and another for the hour in 24 cells in a horizontally scrolling collection view e.g. "ha". Users also page through various view controllers for each location they've saved. I haven't noticed much of a performance hit, but I'd really like to ensure I'm being most efficient, and properly thinking this through (e.g. is Singleton the way to go?).

I had created a Singleton with a single DateFormatter as a static variable, and also created a function to set the .dateFormat and .timeZone for the date formatter (see below). I've assumed based on prior reading that if I stick to iOS 10 or later I don't have to worry about thread safety & that it's likely not a concern in this kind of app, anyway.

class Formatter {
    static var dateFormatter = DateFormatter()

    private init() {}

    static func formatTimeForTimeZone(unixDate: TimeInterval, formatString: String, timeZone: String) -> String {
        let usableDate = Date(timeIntervalSince1970: unixDate)
        self.dateFormatter.dateFormat = formatString
        self.dateFormatter.timeZone = TimeZone(identifier: timeZone)
        let dateString = self.dateFormatter.string(from: usableDate)
        return dateString
    }
}

Each time I'd need to set the date in a view or custom cell class, I'd just call the function, passing in appropriate values like this:

let dateString = Formatter.formatTimeForTimeZone(unixDate: someTime, formatString: someFormatString, timeZone: someTimeZone)

Is it correct that this approach doesn't save me much because I'm setting a .formatString at each call (in each cell)? If so, is there a more sound approach? Advanced thanks for setting me straight.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Gallaugher
  • 1,593
  • 16
  • 27
  • 3
    This seems like premature optimisation to me. Unless you have thousands/tens of thousands of dates to be formatted using `DateFormatter`s, you shouldn't really worry about the performance of it. Especially if you tested your code on the oldest device your app supports and didn't see any performance issues, your code should be good to go. – Dávid Pásztor Oct 27 '17 at 00:56
  • You shouldn't name your class Formatter. Swift already has a class band formatter. Just use extension to add your static method or properties to it. https://developer.apple.com/documentation/foundation/formatter – Leo Dabus Oct 27 '17 at 03:06
  • Btw no need to declare your dateFormatter a variable using `var`. `DateFormatter` it is a class so you can change its properties even if you declare is as a constant using `let`. – Leo Dabus Oct 27 '17 at 03:08
  • And regarding your question. This is not a singleton. You don't even instantiate your Formatter class anywhere. If you would like to learn how to create a Singleton you should read Adopting Cocoa Design Patterns guidelines. https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID177 and this answer https://stackoverflow.com/questions/28016578/swift-how-to-create-a-date-time-stamp-and-format-as-iso-8601-rfc-3339-utc-tim/28016692#28016692 – Leo Dabus Oct 27 '17 at 03:48
  • Thanks, Leo - very helpful. I posted an alternative DateFormatter cacheing solution below modified from Sandmoose's code (hope this etiquette is correct), but am wondering if setting a cached DateFormatter's .timeZone or using the .string(from: somePOSIXdate) is as costly as setting the .dateFormat or creating a new Date. If it is, then I'm not really getting the hoped-for efficiency. Thanks for advice! – Gallaugher Oct 28 '17 at 02:20

1 Answers1

0

EDITED FOR BETTER ANSWER: Thanks Leo for setting me straight & offering suggestions. After reviewing options & trying out a few things, the solution I ended up using was creating a private global data formatter with class-specific dateFormat String on top of each class that needed a dateFormatter. Being global gives persistence, so I'm not re-creating the DateFormatter or setting .dateFormat String (all allegedy expensive). The global is below, with only change class-to-class being the .dateFormat String

private let dateFormatter: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "EEEE, MMM dd, y"
    return dateFormatter
}()

I then created an extension of TimeInterval that handled the formatting that I needed, taking an IANA Time Zone String and DateFormatter (passing in the class's global, when called).

extension TimeInterval {
    func format(timeZone: String, dateFormatter: DateFormatter) -> String {
        let usableDate = Date(timeIntervalSince1970: self)
        dateFormatter.timeZone = TimeZone(identifier: timeZone)
        let dateString = dateFormatter.string(from: usableDate)
        return dateString
    }
}

If anyone's also looking at alternatives, the caching approach offered by mluton/sandmoose, here, is quite nice: https://gist.github.com/mluton/98ab2b82bd18f7a7f762#file-cacheddateformatter-swift

Gallaugher
  • 1,593
  • 16
  • 27