Search code examples
swiftduration

How to create a custom TimeFormatStyle for just seconds for a Swift Duration


With Swift's relatively new Duration feature, there are pre-created common patterns (.hourMinute, .hourMinuteSecond, and .minuteSecond) for formatting Durations that handle hours and minutes of time, but the formats don't work for times that are just seconds or are greater than a day.

How do I create a custom TimeFormatStyle pattern that allows me to show some amount of time that is less than a minute such as 9 seconds and 54 hundredths of a second as simply "9.54"? I would need to have control over the padding of the seconds so that I could optionally format to "09.54" and I would want to round milliseconds rather than truncate them, so 9 seconds and 541 milliseconds should format as "9.54" and 9 seconds and 547 milliseconds should format as "9.55".


Solution

  • As you just want to format it as a number, you can write your own FormatStyle that wraps one of the existing format styles for numbers (e.g. FloatingPointFormatStyle), with a FormatInput being Duration. You can then convert the Duration to a number of seconds in the format method.

    Here I have wrapped a Decimal.FormatStyle, just because Duration is represented in a base-10 way.

    struct SecondsOnlyFormatStyle: FormatStyle {
        let numberFormat: Decimal.FormatStyle
        
        func format(_ value: Duration) -> String {
            let (seconds, attoseconds) = value.components
            let decimal = Decimal(seconds) + Decimal(attoseconds) / 1e18
            return numberFormat.format(decimal)
        }
    }
    
    // extension on FormatStyle so that you don't need to write "SecondsOnlyFormatStyle" 
    // when there is already a contextual type, just like other format styles.
    extension FormatStyle where Self == SecondsOnlyFormatStyle {
        static var secondsOnly: SecondsOnlyFormatStyle {
            .init(numberFormat: .number)
        }
        
        static func secondsOnly(integerLength: Int, fractionalLength: Int) -> SecondsOnlyFormatStyle {
            .init(numberFormat: .number.precision(.integerAndFractionLength(integer: integerLength, fraction: fractionalLength)))
        }
    }
    

    Usage:

    Text(someDuration, format: .secondsOnly(integerLength: 2, fractionalLength: 2))