Keywords: Swift | NSTimeInterval | Time Conversion
Abstract: This article delves into precise methods for converting NSTimeInterval (time intervals) to hours, minutes, seconds, and milliseconds in Swift programming. By analyzing common error cases, it explains how to correctly extract the millisecond component and provides solutions based on floating-point remainder calculations. The article also introduces extension implementations in Swift 4, demonstrating how to encapsulate functionality for better code reusability. Additionally, it compares the pros and cons of different approaches, helping developers choose suitable methods based on practical needs.
Introduction
In iOS and macOS development, NSTimeInterval (type-aliased as TimeInterval in Swift) is the standard data type for representing time intervals, typically in seconds with fractional parts. In practical applications, it is often necessary to convert such intervals into more readable formats, such as "hours:minutes:seconds.milliseconds". However, many developers encounter calculation errors when handling the millisecond component. This article provides an in-depth analysis of this issue and offers accurate conversion methods.
Problem Analysis
Consider the following common but flawed implementation:
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)
}
The main issue in this code lies in the millisecond calculation. Since ti is the integer part of interval (obtained via NSInteger(interval)), ms = ti * 1000 actually converts the entire integer second portion to milliseconds, rather than extracting the fractional millisecond part. For example, with interval = 12345.67 (representing 12345 seconds and 670 milliseconds), ti = 12345, so ms = 12345 * 1000 = 12345000, which is clearly incorrect.
Solution
To correctly extract the millisecond component, the fractional part of NSTimeInterval must be utilized. Swift supports remainder calculations on floating-point numbers, providing a concise solution. The core idea is:
- Use
interval % 1to obtain the fractional part (i.e., the portion less than one second). - Multiply the fractional part by 1000 to convert it to an integer representing milliseconds.
Here is the corrected code:
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)
}
For interval = 12345.67, the calculation proceeds as follows:
ti = 12345(integer seconds part)interval % 1 = 0.67(fractional part)ms = Int(0.67 * 1000) = 670(correct millisecond value)seconds = 12345 % 60 = 45minutes = (12345 / 60) % 60 = 205 % 60 = 25hours = 12345 / 3600 = 3- Output:
"03:25:45.670"
Swift 4 Extension Implementation
To enhance code reusability and readability, this functionality can be encapsulated as an extension of TimeInterval. In Swift 4, it is recommended to use the truncatingRemainder(dividingBy:) method instead of the % operator for floating-point remainder calculations, to clarify semantics and avoid potential type confusion.
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)
}
}
Usage example:
let duration: TimeInterval = 12345.67
self.timeLabel.text = duration.stringFromTimeInterval() // Output: "03:25:45.670"
Comparison with Other Implementations
Beyond the above method, developers can choose different formats based on requirements. For instance, a more flexible extension might offer multiple output styles:
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"
}
}
}
This approach offers clear structure and ease of modification but may be less concise than a single-format method. Developers should select the appropriate method based on specific scenarios, such as user interface display or logging.
Conclusion
Accurately converting NSTimeInterval to hours, minutes, seconds, and milliseconds hinges on correctly extracting the fractional millisecond value. Using floating-point remainder calculations (e.g., % 1 or truncatingRemainder(dividingBy: 1)) helps avoid common errors. Encapsulating this as an extension improves code reusability and readability. In practice, developers should choose between standard formats or flexible outputs based on needs, while considering Swift version differences to ensure compatibility and performance.