преобразование из NSTimeInterval в часы, минуты, секунды, миллисекунды в Swift

Мой код здесь:

func stringFromTimeInterval(interval:NSTimeInterval) -> NSString {

    var ti = NSInteger(interval)
    var ms = ti * 1000
    var seconds = ti % 60
    var minutes = (ti / 60) % 60
    var hours = (ti / 3600)

      return NSString(format: "%0.2d:%0.2d:%0.2d",hours,minutes,seconds,ms)
}

на выходе миллисекунды дают неправильный результат. Пожалуйста, дайте представление о том, как правильно найти миллисекунды.


person Lydia    schedule 05.03.2015    source источник
comment
Поскольку NSTimeInterval обычно обрабатывает не миллисекунды, а секунды, мой вопрос заключается в том, почему *1000 всегда должно быть 0. Если ваши реализации управляют миллисекундами, вместо этого должно быть% 1000, нет?   -  person Larme    schedule 05.03.2015
comment
да, вы правы, это должно быть% 1000. Мне нужно точное время, поэтому я работаю с миллисекундами.   -  person Lydia    schedule 05.03.2015
comment
Конечно, NSTimeInterval обрабатывает доли секунды (например, миллисекунды). Это тип с плавающей запятой, а не целое число.   -  person Matthias Bauch    schedule 05.03.2015
comment
@MatthiasBauch, @larme ya, но моя проблема в том, что когда я нахожу разницу между двумя временными интервалами, например: 1:36:22 и 1:36:24 (формат чч:мм:сс), он дает 00:00:01 вместо 00: 00:02. Вот почему я подумал, что это может быть изменение в миллисекундах.   -  person Lydia    schedule 05.03.2015
comment
Я тоже хочу решение этой проблемы.   -  person Lydia    schedule 05.03.2015


Ответы (15)


Swift поддерживает вычисления остатка для чисел с плавающей запятой, поэтому мы можем использовать % 1.

var ms = Int((interval % 1) * 1000)

as in:

func stringFromTimeInterval(interval: TimeInterval) -> NSString {

  let ti = NSInteger(interval)

  let ms = Int((interval % 1) * 1000)

  let seconds = ti % 60
  let minutes = (ti / 60) % 60
  let hours = (ti / 3600)

  return NSString(format: "%0.2d:%0.2d:%0.2d.%0.3d",hours,minutes,seconds,ms)
}

результат:

stringFromTimeInterval(12345.67)                   "03:25:45.670"

Свифт 4:

extension TimeInterval{

        func stringFromTimeInterval() -> String {

            let time = NSInteger(self)

            let ms = Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
            let seconds = time % 60
            let minutes = (time / 60) % 60
            let hours = (time / 3600)

            return String(format: "%0.2d:%0.2d:%0.2d.%0.3d",hours,minutes,seconds,ms)

        }
    }

Использовать:

self.timeLabel.text = player.duration.stringFromTimeInterval()
person Matthias Bauch    schedule 05.03.2015
comment
Возможно, потребуется добавить % 24 для часов, т.е. пусть часы = (ti / 3600) % 24 - person Gabriel Wamunyu; 17.07.2017
comment
ms неверны, вместо этого я использовал ti*1000, иначе я всегда получаю 0 - person Michał Ziobro; 30.05.2018
comment
«%» недоступен: вместо чисел с плавающей запятой используйте truncatingRemainder - person JBarros35; 22.11.2019

Расширение SWIFT 3

Я думаю, что таким образом легче увидеть, откуда взялась каждая часть, чтобы вам было легче изменить ее в соответствии со своими потребностями.

extension TimeInterval {
    private var milliseconds: Int {
        return Int((truncatingRemainder(dividingBy: 1)) * 1000)
    } 

    private var seconds: Int {
        return Int(self) % 60
    } 

    private var minutes: Int {
        return (Int(self) / 60 ) % 60
    } 

    private var hours: Int {
        return Int(self) / 3600
    } 

    var stringTime: String {
        if hours != 0 {
            return "\(hours)h \(minutes)m \(seconds)s"
        } else if minutes != 0 {
            return "\(minutes)m \(seconds)s"
        } else if milliseconds != 0 {
            return "\(seconds)s \(milliseconds)ms"
        } else {
            return "\(seconds)s"
        }
    }
}
person Jake Cronin    schedule 04.07.2017
comment
Джейк, это может быть отличным ответом, но вычисления не на 100% верны: | 3599 возвращает -1 с | 12601 возвращает 3ч -29м 1с - person Kqtr; 05.08.2017
comment
Я предложил редактирование, которое исправляет исчисление. Еще раз спасибо за ваш ответ! - person Kqtr; 05.08.2017
comment
@ПолС. Понятия не имею, возможно, это не было одобрено (пока?) - person Kqtr; 02.11.2017
comment
@ПолС. Оказывается, мое предложение по редактированию было отклонено, поскольку оно отклоняется от первоначального замысла сообщения, но вот ссылка: stackoverflow.com /review/suggested-edits/16940597 - person Kqtr; 02.11.2017
comment
Чтобы избежать проблем с вычислениями, вы также можете посчитать остаток самостоятельно: interval = interval.rounded() let секунды = Int(interval) - (Int(interval / 60) * 60) - person mirap; 19.11.2017

Эквивалент в Objective-C, основанный на ответе @matthias-bauch.

+ (NSString *)stringFromTimeInterval:(NSTimeInterval)timeInterval
{
    NSInteger interval = timeInterval;
    NSInteger ms = (fmod(timeInterval, 1) * 1000);
    long seconds = interval % 60;
    long minutes = (interval / 60) % 60;
    long hours = (interval / 3600);

    return [NSString stringWithFormat:@"%0.2ld:%0.2ld:%0.2ld,%0.3ld", hours, minutes, seconds, (long)ms];
}
person Minos    schedule 01.10.2015

Решение Swift 3 для iOS 8+, macOS 10.10+, если нулевое заполнение часов не имеет значения:

func stringFromTime(interval: TimeInterval) -> String {
    let ms = Int(interval.truncatingRemainder(dividingBy: 1) * 1000)
    let formatter = DateComponentsFormatter()
    formatter.allowedUnits = [.hour, .minute, .second]
    return formatter.string(from: interval)! + ".\(ms)"
}

print(stringFromTime(interval: 12345.67)) // "3:25:45.670"
person vadian    schedule 04.07.2017
comment
используйте formatter.zeroFormattingBehavior = .pad, если вы хотите показать ноль - person Sébastien REMY; 04.11.2017
comment
@iLandes .pad не добавляет начальный ноль к наиболее значимому компоненту (часы в ответе). - person vadian; 04.11.2017
comment
@vadian Мне подходит, если zeroFormattingBehavior = .pad - person idrougge; 15.10.2018
comment
@idrougge Еще раз, не для самого важного компонента. В примере вы не получите "03:25:45.670" даже с .pad - person vadian; 15.10.2018
comment
Ага, теперь я понимаю, что вы имеете в виду. Нет, вы не получите двухзначное число часов, используя .pad. - person idrougge; 15.10.2018

Свифт 4:

extension TimeInterval{

        func stringFromTimeInterval() -> String {

            let time = NSInteger(self)

            let ms = Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
            let seconds = time % 60
            let minutes = (time / 60) % 60
            let hours = (time / 3600)

            return String(format: "%0.2d:%0.2d:%0.2d.%0.3d",hours,minutes,seconds,ms)

        }
    }

Использовать:

self.timeLabel.text = player.duration.stringFromTimeInterval()
person Mohammad Razipour    schedule 27.03.2018
comment
Int((self.truncatingRemainder(dividingBy: 1)) * 1000) всегда дает 0 - person Michał Ziobro; 15.06.2018

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

person lilpit    schedule 27.06.2018
comment
Это верно для 99% случаев. Когда необходимы NSCalendar.Unit, которые не поддерживаются DateComponentsFormatter (например, .nanosecond), полезно использовать один из этих методов. - person valeCocoa; 21.05.2019

Swift 4 без использования .remainder (который возвращает неправильные значения):

func stringFromTimeInterval(interval: Double) -> NSString {

    let hours = (Int(interval) / 3600)
    let minutes = Int(interval / 60) - Int(hours * 60)
    let seconds = Int(interval) - (Int(interval / 60) * 60)

    return NSString(format: "%0.2d:%0.2d:%0.2d",hours,minutes,seconds)
}
person mirap    schedule 19.11.2017

для преобразования часов и минут в секунды в Swift 2.0:

///RETORNA TOTAL DE SEGUNDOS DE HORA:MINUTOS
func horasMinutosToSeconds (HoraMinutos:String) -> Int {

    let formatar = NSDateFormatter()
    let calendar = NSCalendar.currentCalendar()
    formatar.locale = NSLocale.currentLocale()
    formatar.dateFormat = "HH:mm"

    let Inicio = formatar.dateFromString(HoraMinutos)
    let comp = calendar.components([NSCalendarUnit.Hour, NSCalendarUnit.Minute], fromDate: Inicio!)

    let hora = comp.hour
    let minute = comp.minute

    let hours = hora*3600
    let minuts = minute*60

    let totseconds = hours+minuts

    return totseconds
}
person Pablo Ruan    schedule 05.03.2016
comment
так усложнить простую задачу. - person Sébastien REMY; 04.07.2016
comment
Стоит отметить, что использование DateFormatter требует больших ресурсов, и его можно легко избежать. Однако я бы не рекомендовал использовать этот подход, это допустимое решение. - person OhadM; 21.11.2019
comment
Я проголосовал против, потому что он не отформатирован, vars и let на португальском или испанском языках, var в регистре Pascal и другие проблемы. Это плохо для моих глаз - person firetrap; 16.12.2019

версия swift 3 ответа @hixField, теперь с днями и обработкой предыдущих дат:

extension TimeInterval {
    func timeIntervalAsString(_ format : String = "dd days, hh hours, mm minutes, ss seconds, sss ms") -> String {
        var asInt   = NSInteger(self)
        let ago = (asInt < 0)
        if (ago) {
            asInt = -asInt
        }
        let ms = Int(self.truncatingRemainder(dividingBy: 1) * (ago ? -1000 : 1000))
        let s = asInt % 60
        let m = (asInt / 60) % 60
        let h = ((asInt / 3600))%24
        let d = (asInt / 86400)

        var value = format
        value = value.replacingOccurrences(of: "hh", with: String(format: "%0.2d", h))
        value = value.replacingOccurrences(of: "mm",  with: String(format: "%0.2d", m))
        value = value.replacingOccurrences(of: "sss", with: String(format: "%0.3d", ms))
        value = value.replacingOccurrences(of: "ss",  with: String(format: "%0.2d", s))
        value = value.replacingOccurrences(of: "dd",  with: String(format: "%d", d))
        if (ago) {
            value += " ago"
        }
        return value
    }

}
person Ohad Cohen    schedule 25.12.2016

Swift 4 (с проверкой диапазона ~ без сбоев)

import Foundation

extension TimeInterval {

var stringValue: String {
    guard self > 0 && self < Double.infinity else {
        return "unknown"
    }
    let time = NSInteger(self)

    let ms = Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
    let seconds = time % 60
    let minutes = (time / 60) % 60
    let hours = (time / 3600)

    return String(format: "%0.2d:%0.2d:%0.2d.%0.3d", hours, minutes, seconds, ms)

}
}
person maslovsa    schedule 24.09.2018

Swift 5. Нет мс и некоторое условное форматирование (т.е. не отображать часы, если есть 0 часов).

extension TimeInterval{

func stringFromTimeInterval() -> String {

    let time = NSInteger(self)

    let seconds = time % 60
    let minutes = (time / 60) % 60
    let hours = (time / 3600)

    var formatString = ""
    if hours == 0 {
        if(minutes < 10) {
            formatString = "%2d:%0.2d"
        }else {
            formatString = "%0.2d:%0.2d"
        }
        return String(format: formatString,minutes,seconds)
    }else {
        formatString = "%2d:%0.2d:%0.2d"
        return String(format: formatString,hours,minutes,seconds)
    }
}
}
person Clay Martin    schedule 22.11.2019

Вы можете использовать Measurement и UnitDuration для преобразования значения TimeInterval в любую единицу продолжительности. Чтобы увидеть миллисекунды в результате, вам нужно UnitDuration.milliseconds, для которого требуется iOS 13.0, tvOS 13.0, watchOS 6.0 или macOS 10.15. Я помещаю все действия, которые должны быть выполнены в func convertDurationUnitValueToOtherUnits(durationValue:durationUnit:smallestUnitDuration:) (Swift 5.1.3/Xcode 11.3.1):

import Foundation

@available(iOS 10.0, tvOS 10.0, watchOS 3.0, macOS 10.12, *)
func convert<MeasurementType: BinaryInteger>(
    measurementValue: Double, unitDuration: UnitDuration, smallestUnitDuration: UnitDuration
) -> (MeasurementType, Double) {
    let measurementSmallest = Measurement(
        value: measurementValue,
        unit: smallestUnitDuration
    )
    let measurementSmallestValue = MeasurementType(measurementSmallest.converted(to: unitDuration).value)
    let measurementCurrentUnit = Measurement(
        value: Double(measurementSmallestValue),
        unit: unitDuration
    )
    let currentUnitCount = measurementCurrentUnit.converted(to: smallestUnitDuration).value
    return (measurementSmallestValue, measurementValue - currentUnitCount)
}

@available(iOS 10.0, tvOS 10.0, watchOS 3.0, macOS 10.12, *)
func convertDurationUnitValueToOtherUnits<MeasurementType: BinaryInteger>(
    durationValue: Double,
    durationUnit: UnitDuration,
    smallestUnitDuration: UnitDuration
) -> [MeasurementType] {
    let basicDurationUnits: [UnitDuration] = [.hours, .minutes, .seconds]
    let additionalDurationUnits: [UnitDuration]
    if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
        additionalDurationUnits = [.milliseconds, .microseconds, .nanoseconds, .picoseconds]
    } else {
        additionalDurationUnits = []
    }
    let allDurationUnits = basicDurationUnits + additionalDurationUnits
    return sequence(
        first: (
            convert(
                measurementValue: Measurement(
                    value: durationValue,
                    unit: durationUnit
                ).converted(to: smallestUnitDuration).value,
                unitDuration: allDurationUnits[0],
                smallestUnitDuration: smallestUnitDuration
            ),
            0
        )
    ) {
        if allDurationUnits[$0.1] == smallestUnitDuration || allDurationUnits.count <= $0.1 + 1 {
            return nil
        } else {
            return (
                convert(
                    measurementValue: $0.0.1,
                    unitDuration: allDurationUnits[$0.1 + 1],
                    smallestUnitDuration: smallestUnitDuration
                ),
                $0.1 + 1
            )
        }
    }.compactMap { $0.0.0 }
}

Вот как вы можете это назвать:

let intervalToConvert: TimeInterval = 12345.67
let result: [Int] = convertDurationUnitValueToOtherUnits(
    durationValue: intervalToConvert,
    durationUnit: .seconds,
    smallestUnitDuration: .milliseconds
)
print("\(result[0]) hours, \(result[1]) minutes, \(result[2]) seconds, \(result[3]) milliseconds") // 3 hours, 25 minutes, 45 seconds, 670 milliseconds

Как видите, я не использовал числовые константы вроде 60 и 1000 для получения результата.

person Roman Podymov    schedule 18.01.2020

Расширение Swift 4 — с точностью до наносекунд

import Foundation

extension TimeInterval {

    func toReadableString() -> String {

        // Nanoseconds
        let ns = Int((self.truncatingRemainder(dividingBy: 1)) * 1000000000) % 1000
        // Microseconds
        let us = Int((self.truncatingRemainder(dividingBy: 1)) * 1000000) % 1000
        // Milliseconds
        let ms = Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
        // Seconds
        let s = Int(self) % 60
        // Minutes
        let mn = (Int(self) / 60) % 60
        // Hours
        let hr = (Int(self) / 3600)

        var readableStr = ""
        if hr != 0 {
            readableStr += String(format: "%0.2dhr ", hr)
        }
        if mn != 0 {
            readableStr += String(format: "%0.2dmn ", mn)
        }
        if s != 0 {
            readableStr += String(format: "%0.2ds ", s)
        }
        if ms != 0 {
            readableStr += String(format: "%0.3dms ", ms)
        }
        if us != 0 {
            readableStr += String(format: "%0.3dus ", us)
        }
        if ns != 0 {
            readableStr += String(format: "%0.3dns", ns)
        }

        return readableStr
    }
}
person Mick    schedule 20.09.2018

преобразовано в формат расширения swift 2 + переменная:

extension NSTimeInterval {

    func timeIntervalAsString(format format : String = "hh:mm:ss:sss") -> String {
        let ms      = Int((self % 1) * 1000)
        let asInt   = NSInteger(self)
        let s = asInt % 60
        let m = (asInt / 60) % 60
        let h = (asInt / 3600)

        var value = format
        value = value.replace("hh",  replacement: String(format: "%0.2d", h))
        value = value.replace("mm",  replacement: String(format: "%0.2d", m))
        value = value.replace("sss", replacement: String(format: "%0.3d", ms))
        value = value.replace("ss",  replacement: String(format: "%0.2d", s))
        return value
    }

}

extension String {
    /**
     Replaces all occurances from string with replacement
     */
    public func replace(string:String, replacement:String) -> String {
        return self.stringByReplacingOccurrencesOfString(string, withString: replacement, options: NSStringCompareOptions.LiteralSearch, range: nil)
    }

}
person HixField    schedule 24.09.2016

Вот немного улучшенная версия @maslovsa с входным параметром Precision:

import Foundation

extension TimeInterval {

    enum Precision {
        case hours, minutes, seconds, milliseconds
    }

    func toString(precision: Precision) -> String? {
        guard self > 0 && self < Double.infinity else {
            assertionFailure("wrong value")
            return nil
        }

        let time = NSInteger(self)

        let ms = Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
        let seconds = time % 60
        let minutes = (time / 60) % 60
        let hours = (time / 3600)

        switch precision {
        case .hours:
            return String(format: "%0.2d", hours)
        case .minutes:
            return String(format: "%0.2d:%0.2d", hours, minutes)
        case .seconds:
            return String(format: "%0.2d:%0.2d:%0.2d", hours, minutes, seconds)
        case .milliseconds:
            return String(format: "%0.2d:%0.2d:%0.2d.%0.3d", hours, minutes, seconds, ms)
        }
    }
}

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

let time: TimeInterval = (60 * 60 * 8) + 60 * 24.18
let hours = time.toString(precision: .hours) // 08
let minutes = time.toString(precision: .minutes) // 08:24
let seconds = time.toString(precision: .seconds) // 08:24:10
let milliseconds = time.toString(precision: .milliseconds) // 08:24:10.799
person Stanislau Baranouski    schedule 05.08.2019