iosswiftdatensdatecomponentsnsdatecomponentsformatter

Swift DateComponentsFormatter drop leading zeroes but keep at least one digit in Minutes place


I am using the following DateComponentsFormatter in Swift:

let formatter = DateComponentsFormatter()
formatter.unitsStyle   = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = [.default]

This produces results like 2:41 (for 2 minutes and 41 seconds) and 08 (for 8 seconds). I would, however, like to keep at least one digit in the Minutes place, returning 0:08 instead of 08. Is this possible with the DateComponentsFormatter? I would prefer a solution that returns a localized result.


Solution

  • With iOS 16 or later you can use the new Duration.TimeFormatStyle.Pattern.

    Xcode 14.1 • Swift 5.7.1 • iOS 16 or later

    extension TimeInterval {
        var duration: Duration { .seconds(self) }
        var positionalTime: String {
            duration.formatted(
                .time(pattern: self >= 3600 ? .hourMinuteSecond : .minuteSecond)
            )
        }
    }
    
    8.0.positionalTime     //    "0:08"
    161.0.positionalTime   //    "2:41"
    3600.0.positionalTime  // "1:00:00"
    

    For older Xcode/Swift versions

    Yes. This can be easily accomplished adding a ternary conditional based on the time interval when setting the date components formatter allowed units:


    Xcode 11 • Swift 5.1

    extension Formatter {
        static let positional: DateComponentsFormatter = {
            let positional = DateComponentsFormatter()
            positional.unitsStyle = .positional
            positional.zeroFormattingBehavior = .pad
            return positional
        }()
    }
    

    extension TimeInterval {
        var positionalTime: String {
            Formatter.positional.allowedUnits = self >= 3600 ?
                                                [.hour, .minute, .second] :
                                                [.minute, .second]
            let string = Formatter.positional.string(from: self)!
            return string.hasPrefix("0") && string.count > 4 ?
                .init(string.dropFirst()) : string
        }
    }
    

    Usage

    8.0.positionalTime     //    "0:08"
    161.0.positionalTime   //    "2:41"
    3600.0.positionalTime  // "1:00:00"