I might advise not bothering to calculate hours and minutes at all, but rather let DateComponentsFormatter
do this, creating the final string for you.
For example:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
Then supply this formatter the elapsed time measured in seconds (a TimeInterval
, which is just an alias for Double
):
let remaining: TimeInterval = 90 * 60 // e.g. 90 minutes represented in seconds
if let result = formatter.string(from: remaining) {
print(result)
}
On a English speaking device, that will produce:
1 hour, 30 minutes
The virtue of this approach is that not only does it get you out of the business of manually calculating hours and minutes yourself, but also that the result is easily localized. So, if and when you get around to localizing your app, this string will be localized automatically for you, too, with no further work on your part. For example, if you add German to your app localizations, then the US user will still see the above, but on a German device, it will produce:
1 Stunde und 30 Minuten
If you want it to say how much time is remaining, set includesTimeRemainingPhrase
:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesTimeRemainingPhrase = true
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
That will produce:
1 hour, 30 minutes remaining
If you want a “hh:mm” sort of representation:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
Will produce:
01:30
By the way, if you are wondering how to get the quotient and remainder (notably, avoiding the undesired rounding in your example), refer to the truncatingRemainder(dividingBy:)
documentation, which says:
Returns the remainder of this value divided by the given value using
truncating division.
Performing truncating division with floating-point values results in a
truncated integer quotient and a remainder. For values x
and y
and
their truncated integer quotient q
, the remainder r
satisfies
x == y * q + r
.
The following example calculates the truncating remainder of dividing
8.625 by 0.75:
let x = 8.625
print(x / 0.75)
// Prints "11.5"
let q = (x / 0.75).rounded(.towardZero)
// q == 11.0
let r = x.truncatingRemainder(dividingBy: 0.75)
// r == 0.375
let x1 = 0.75 * q + r
// x1 == 8.625
If this value and other
are both finite numbers, the truncating
remainder has the same sign as this value and is strictly smaller in
magnitude than other
. The truncatingRemainder(dividingBy:)
method
is always exact.
I might be inclined to write a floating point rendition of quotientAndRemainder
:
extension FloatingPoint {
func quotientAndRemainder(dividingBy divisor: Self) -> (Self, Self) {
let q = (self / divisor).rounded(.towardZero)
let r = truncatingRemainder(dividingBy: divisor)
return (q, r)
}
}
Then, in your example, use that method:
func decimalHoursConv(hours input: Double) -> (_hrs: String, mins: String) {
let (q, r) = input.quotientAndRemainder(dividingBy: 1)
let minutes = abs((r * 60).rounded(.towardZero))
let mins = String(format: "%.0f", minutes)
let hrs = String(format: "%.0f", q)
return (hrs, mins)
}
Note, if the value of the input is negative, e.g., -1.75 hours, the quotient is -1 hours, and the remainder is -0.75 hours. That is mathematically correct, but clearly if you are going to display these in the UI, you probably would want to show hours with the negative value, but not the minutes, too. So I have performed the abs
of the remainder.
Also, you probably want to round minutes down (so that 1.9999 hours does not report 1:60, but rather 1:59). So, I have rounded down (to zero) the product of the remainder times 60.
Bottom line, if you really want to calculate minutes and seconds, feel free, but if it is solely to create a string representation, you might consider letting the DateComponentFormatter
do this for you. It handles positive and negative values correctly, localized strings, etc.