Swift DateComponentsFormatter 删除前导零但在分钟位置至少保留一位数字

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

我在 Swift 中使用以下 DateComponentsFormatter:

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

这会产生类似 2:41(2 分 41 秒)和 08(8 秒)的结果。但是,我希望在分钟位置至少保留一位数字,返回 0:08 而不是 08。DateComponentsFormatter 是否可行?我更喜欢 returns 本地化结果的解决方案。

检查不同的 zeroFormattingBehavior 属性 值。我用它来获得我认为您正在寻找的结果,但必要时进行实验...

    formatter.zeroFormattingBehavior = [.pad]

是的。这可以很容易地完成,在设置日期组件格式化程序允许的单位时添加基于时间间隔的三元条件:


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
    }
}

用法

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

可以根据持续时间修改 allowedUnitszeroFormattingBehavior 来创建适当的位置时间字符串。请注意,当只需要一个零时,这将在几分钟内填充两个前导零,以便事后以安全的方式处理这种情况。

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
}

这是 durationformattedDuration 的列表:

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"

想出这个解决方案,使用正则表达式,似乎涵盖所有情况:

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