-1

I want to make DateFormatter static instead because creating DateFormatter is costly operation. I wonder how to achieve that ?

extension Date {

  func toString(format: String = "yyyy-MM-dd") -> String {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.dateFormat = format
    return formatter.string(from: self)
  }

  func dateAndTimetoString(format: String = "yyyy-MM-dd HH:mm") -> String {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.dateFormat = format
    return formatter.string(from: self)
  }
}
casillas
  • 16,351
  • 19
  • 115
  • 215
  • 1
    I usually use a `Formatters` class that provides functions for all the various types of formatting operations my application needs, with private static storage for the formatters themselves – Alexander Aug 21 '20 at 19:34
  • Could you please give an example how you use it? – casillas Aug 21 '20 at 19:37
  • 1
    Not related to your question but you shouldn't use fixed dateFormat when displaying a date to the user. You should always display it localised. The only thing you can do is to choose if it will be displayed short, medium, long or full. You should always use DateFormater's date and time style properties instead of a fixed DateFormat. https://stackoverflow.com/a/28347285/2303865. And you should only use one or another not both – Leo Dabus Aug 21 '20 at 20:48

2 Answers2

3

I do something like this:

import Foundation

enum Formatters {
     static private var numberFormatter: NumberFormatter = {
        let nf = NumberFormatter()
        nf.numberStyle = .decimal
        return nf
    }()
    
    static private var percentageFormatter: NumberFormatter {
        let nf = NumberFormatter()
        nf.numberStyle = .percent
        nf.minimumFractionDigits = 1
        nf.minimumFractionDigits = 1
        return nf
    }

    static private var memoryColumnFormatter: ByteCountFormatter = {
        let bcf = ByteCountFormatter()
        bcf.countStyle = .memory
        bcf.allowsNonnumericFormatting = false
        bcf.formattingContext = .listItem
        return bcf
    }()
    
    // Mark: Convenience overloads
    // I really wish NSNumebr had an initializer that took a generic BinaryInteger
    
    static func format(number: Int32) -> String {
        return self.format(number: NSNumber(value: number))
    }
    
    static func format(number: UInt32) -> String {
        return self.format(number: NSNumber(value: number))
    }
    
    static func format(pointer: UInt64) -> String {
        "0x" + String(pointer, radix: 16, uppercase: true, width: 16)
    }
    
    static func format(percentage: Double) -> String {
        return percentageFormatter.string(from: NSNumber(value: percentage))!
    }
    
    static func format(byteCountForMemoryColumn byteCount: UInt64) -> String {
        return memoryColumnFormatter.string(fromByteCount: Int64(byteCount))
    }
    
    static func format(number: NSNumber) -> String {
        return numberFormatter.string(from:number)!
    }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • I would extend `Formatter` abstract class and drop the formatter from the property name. Btw no need to coerce the numbers to NSNumber. You can simply pass Any to the Formatter string(forL) method and create a single method. – Leo Dabus Aug 21 '20 at 20:25
  • @LeoDabus I wouldn't, because this is an aggregation of multiple formatters. Sub-classing `Formatter` would be wholly inappropriate, because that has entirely different semantics. – Alexander Aug 21 '20 at 20:29
  • Never said to subclass. `Formatter.number.whatever` – Leo Dabus Aug 21 '20 at 20:30
  • where is `String(pointer, radix: 16, uppercase: true, width: 16)` declared? – Leo Dabus Aug 21 '20 at 20:34
  • Oooo, you mean in an extension, I see. In my impl, I intentionally made the formatters `private`, for 2 reasons: 1) They're shared state, and I don't want them to be mutable (I don't want anyone going in and saying `Formatters.percentage.numberStyle = .decimal`). 2) I want to force a particular usage. E.g. all my pointers start with `"0x"`. The public `format(pointer:)` function ensures that all formatting of pointers happen through threre, so I have a central place to configure this behavior. E.g. I was considering adding spacing to my pointers for readability, this is where I can do that. – Alexander Aug 21 '20 at 20:35
  • I did notice that and I understand. you can still implement it like this – Leo Dabus Aug 21 '20 at 20:35
  • `You can simply pass Any to the Formatter string(forL) method and create a single method.`, right, thanks! I didn't know about that inherit method from `Formatter` at the time I wrote this. `String(pointer, radix: 16, uppercase: true, width: 16) ` It's a separate extension I've made for myself, that I haven't included here (it's not really relevant, should I include it anyway?) – Alexander Aug 21 '20 at 20:36
  • 1
    It's not really intended to, it's just an example for the general idea of the structure. Most of this specific code wouldn't even make sense for most apps. – Alexander Aug 21 '20 at 20:38
1

Create one statically, with an immediately-executed closure

extension Date {
   static var myFormatter: DateFormatter = {
       let formatter = DateFormatter()
       formatter.dateStyle = .short
       return formatter
   }()
}
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • 3
    OP: Just to add to this: not only is that static, it's lazy. So if creating a formatter is expensive, you are spared that expense entirely if it happens that your code never actually calls `Date.myFormatter`. Cool! – matt Aug 21 '20 at 19:48