Swift DateComponentsFormatter отбрасывает начальные нули, но сохраняет хотя бы одну цифру в минутах

Я использую следующий DateComponentsFormatter в Swift:

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

Это приводит к результатам как 2:41 (в течение 2 минут и 41 секунды) и 08 (в течение 8 секунд). Однако я хотел бы сохранить хотя бы одну цифру в месте "Минуты", возвращая 0:08 вместо 08. Возможно ли это с DateComponentsFormatter? Я бы предпочел решение, которое возвращает локализованный результат.

4 ответа

Решение

Да. Это может быть легко достигнуто добавлением троичного условного выражения на основе временного интервала при установке допустимых единиц форматирования компонентов даты:


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]
        return Formatter.positional.string(from: self)!
    }
}

использование

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

Придумал это решение с использованием регулярного выражения, которое, по-видимому, охватывает все случаи:

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

func durationString(for duration: TimeInterval) -> String {
    formatter.string(from: duration)!
        .replacingOccurrences(of: #"^00:0?|^0"#, with: "", options: .regularExpression)
}

durationString(for: 0)      // 0:00
durationString(for: 1)      // 0:01
durationString(for: 10)     // 0:10
durationString(for: 60)     // 1:00
durationString(for: 600)    // 10:00
durationString(for: 3600)   // 1:00:00
durationString(for: 36000)  // 10:00:00

Можно создать соответствующую строку позиционного времени, изменив allowedUnits а также zeroFormattingBehaviorв зависимости от продолжительности. Обратите внимание, что это приведет к добавлению двух ведущих нулей в течение нескольких минут, когда желательно иметь только один, так что впоследствии этот случай обрабатывается безопасным образом.

let formatter = DateComponentsFormatter()
if duration < 60 {
    formatter.allowedUnits = [.minute, .second]
    formatter.zeroFormattingBehavior = .pad //pad seconds and minutes ie 00:05
} else if duration >= 60*60 {
    formatter.allowedUnits = [.hour, .minute, .second]
}

var formattedDuration = formatter.string(from: duration) ?? "0"
if formattedDuration.hasPrefix("00") {
    formattedDuration = String(formattedDuration.dropFirst()) //remove leading 0 ie 0:05
}

Вот список durations и formattedDurations:

0 ▶️ 0:00
5 ▶️ 0:05
30 ▶️ 0:30
60 ▶️ 1:00
65 ▶️ 1:05
3600 ▶️ 1:00:00
3665 ▶️ 1:00:05
3660 ▶️ 1:01:00

Альтернативное решение

Вы можете достичь того же результата без проверки двух ведущих нулей, используя .dropTrailing zeroFormattingBehavior, как ни странно. Я предупреждаю, что не понимаю, почему это работает, и не знаю, какое влияние это может оказать на локализацию. Обратите внимание, что если продолжительность0, результат "0", поэтому мы можем использовать .pad в этом случае получить результат "00:00"вместо. При этом вы окажетесь в той же ситуации, о которой говорилось выше, но, возможно, вы почувствуете, что это крайний случай, который может не нуждаться в обработке.

let formatter = DateComponentsFormatter()
if duration >= 60 {
    formatter.allowedUnits = [.hour, .minute, .second]
    formatter.zeroFormattingBehavior = .default
} else if duration == 0 {
    formatter.allowedUnits = [.minute, .second]
    formatter.zeroFormattingBehavior = .pad
    //is 00:00 but should be 0:00
} else {
    formatter.allowedUnits = [.minute, .second]
    formatter.zeroFormattingBehavior = .dropTrailing //magic here
}
let formattedDuration = formatter.string(from: duration) ?? "0"

Проверьте другое значение свойства zeroFormattingBehavior. Я использую это, чтобы получить результат, который, я думаю, вы ищете, но экспериментируйте по мере необходимости...

    formatter.zeroFormattingBehavior = [.pad]
Другие вопросы по тегам