Crash when trying to get track length from URL file Swift [duplicate] - swift

When I call this method, an error received. How to handle NaN or infinite value during assign to hour, min or sec?
Here is my code:
private func secondsToFormattedString(totalSeconds: Float64) -> String{
let hours:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 86400) / 3600)
let minutes:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 3600) / 60)
let seconds:Int = Int(totalSeconds.truncatingRemainder(dividingBy: 60))
if hours > 0 {
return String(format: "%i:%02i:%02i", hours, minutes, seconds)
} else {
return String(format: "%02i:%02i", minutes, seconds)
}
}

You should check if totalSeconds is a valid value, like:
guard !(totalsSeconds.isNaN || totalSeconds.isInfinite) else {
return "illegal value" // or do some error handling
}
And check this:
Convert Float to Int in Swift

Another option is to use a formatter for this, I just wanted to convert a Double to an Int to make it easier to display in my UI so a NumberFormatter was perfect, my Double was NaN so that's what the NumberFormatter provided me, rather than the fatalError that the Int 'cast' provided (why doesn't it return an optional like Int(String) does?)
But in the context of this question a DateFormatter is a great solution, which would do the whole function's work for you (bear in mind that creating DateFormatters is a little costly so you wouldn't want to create one for every string formatting you do but keep it hanging out if it makes sense)

Related

AVPlayer - Fatal error: Double value cannot be converted to Int because it is either infinite or NaN

I have this function:
func getSeconds()->Int{
return Int(Double(self.value) * (self.player.currentItem?.duration.seconds)!)
}
It sometimes crashes:
Fatal error: Double value cannot be converted to Int because it is either infinite or NaN
Of course I've seen this:
Swift 3:fatal error: Double value cannot be converted to Int because it is either infinite or NaN
But how EXACTLY do I have to use this?
What is this?:
guard !(totalSeconds.isNaN || totalSeconds.isInfinite) else {
return "illegal value"
}
Where do I return totalSeconds if everything is fine?
EDIT:
I'm doing it like this now:
func getSeconds()->Int{
let totalSeconds = self.player.currentItem?.duration.seconds
if(totalSeconds != nil){
guard !(totalSeconds!.isNaN || totalSeconds!.isInfinite) else {
return 0 // or do some error handling
}
return Int(Double(self.value) * (self.player.currentItem?.duration.seconds)!)
}else{
return 0
}
}
Does it make sense?
As mentioned in the answer to the question you linked to, the code is there to verify that the value is a valid number.
Your edit is in the correct direction but it could use some improvements to the handling of optionals. Something like the following might be better:
func getSeconds() -> Int {
guard let totalSeconds = self.player.currentItem?.duration.seconds,
!totalSeconds.isNaN && !totalSeconds.isInfinite else { return 0 }
return Int(Double(self.value) * totalSeconds)
}
The guard first gets the value of self.player.currentItem?.duration.seconds but only if there is a non-optional value. If self.player.currentItem is currently nil then there won't be a duration.
Once the guard gets a value in seconds, it makes sure the value is neither NaN nor infinite.
So the guard's else will be reached if there is no current item to get the duration for, or if the duration isn't a finite number.
If you have a good number, the code can move on the calculation and return a final value.
Note there is no use of force-unwraps in the code. You should avoid those to eliminate likely crashes. Learning proper optional handling in Swift is critical.
As stated in a comment to the answer you referenced, the whole !(totalSeconds.isNaN || totalSeconds.isInfinite) can be written as totalSeconds.isFinite. Doing that in your function would change the guard to be:
guard let totalSeconds = self.player.currentItem?.duration.seconds,
totalSeconds.isFinite else { return 0 }
This is a bit easier to read and more to the point of the needed check in this case.

Adding Time Interval. e.g hours, minutes and seconds

I am getting time from the response in hours, minutes and seconds like "01:32:34" of multiple objects. I have saved it in a custom date object, where i am saving the date value and the string value, there are a large number of records so i am saving it in my local db and upon retrieving i get the date value in the format 1999-12-31 19:01:04 +0000 whereas my string value which i am getting from response as well is 19:01:04. now i would like to add all of these values to return a string e.g 1:15:16, 00:15:02, 00:45:27 should return 2 hours 15 minutes 45 seconds. I have explored Calendar.Components.byAdding but the methods there only let me add one single component, it doesn't receive an array and return Date. Is there any swifty way to achieve this, I want to achieve this via an elegant and proper method, i could think of a few fixes but they don't seem appropriate.
I’m going to assume that “01:32:34” represents an elapsed time of 5,554 seconds, not 1:32am. So, I’d convert it to a TimeInterval, not a Date:
func timeInterval(from string: String) -> TimeInterval? {
let components = string.components(separatedBy: ":").map { Double($0) }
guard
components.count == 3,
let hours = components[0],
let minutes = components[1],
let seconds = components[2]
else { return nil }
return ((hours * 60) + minutes) * 60 + seconds
}
You can chose to store either the original “01:32:34” string or this value, 5,554.0.
Anyway, then adding these numeric time intervals is trivial addition. And to display a resulting TimeInterval, you’d use a DateComponentsFormatter, e.g.
let timeIntervalFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
return formatter
}()
func totalElapsed(_ strings: [String]) -> String? {
let total = strings.reduce(TimeInterval.zero) { sum, string in
sum + (timeInterval(from: string) ?? 0)
}
return timeIntervalFormatter.string(from: total)
}
let strings = ["1:15:16", "00:15:02", "00:45:27"]
let result = totalElapsed(strings)
02:15:45
Or if you want more of a (localized) natural language representation, use unitsStyle of .full:
2 hours, 15 minutes, 45 seconds
This approach (using TimeInterval, not Date) has the virtue that it also can represent intervals that exceed 24 hours.

Confusion with unwrapping an optional value when rounding numbers

Very new to Swift concepts and I'm having trouble conceptualizing how to convert a long decimal value type var distance: String? into a shorter one. This code is crashing due to a:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
let distance = Int(item.distance!) // long decimal value
let x = Float((distance)!)
let y = Double(round(1000*x)/1000)
print(y)
A couple of observations:
You should not only excise the Int from your code snippet, but the Float, too. If your numbers can be large, Float can impose undesirable limitations in the precision of your calculation. So, you would likely want to remove both Int and Float like so:
guard let string = item.distance, let value = Double(string) else {
return
}
let result: Double = (value * 1000).rounded() / 1000
If you’re doing this rounding just so you can show it to 3 decimal places in your UI, you probably wouldn’t round the value at all, but rather just round the output using a NumberFormatter, e.g.:
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 3
formatter.maximumFractionDigits = 3
guard let string = item.distance, let value = Double(string) else {
return
}
let result: String = formatter.string(for: value)
We do this when showing fractional values in our UI because:
The resulting string will be “localized”. This is important because not all locales use . for the decimal point (e.g., in many countries, the value ½ is displayed as 0,5, not 0.5). We always want to show numbers in our UI in the manner preferred by the user. The NumberFormatter does all of this localization of string representations of numbers for us.
If the value had trailing zeros, the above formatter will generate all of those decimal places (e.g. 0.5 will be shown as 0.500), which is often preferable when dealing with decimal places in your UI. (And even if if you don’t want those trailing zeros, you’d still use the NumberFormatter and just set minimumFractionDigits to whatever is appropriate for your app.)
If you really need to round the number to three decimal places (which is unlikely in this case, but we encounter this in financial apps), you shouldn’t use Double at all, but rather Decimal. Again, I’m guessing this is unlikely in your scenario, but I mention it for the sake of completeness.
First of all, This will help you find the issue:
if let distance = item.distance {
if let distanceInt = Int(distance) {
let x = Float(distanceInt)
let y = Double(round(1000*x)/1000)
print(y)
} else {
print("Distance (\(distance)) is not convertible to Int. It has a value, but this value is not representing an integer number.")
}
} else {
print("distance is nil. It should be some number but it is not set yet")
}
Here you can see this string: "0.45991480288961" can not be converted to an Int. So you need to convert it directly to a Double:
if let distance = item.distance {
if let distanceDouble = Double(distance) {
let x = Float(distanceDouble)
let y = Double(round(1000*x)/1000)
print(y)
} else {
print("Distance (\(distance)) is not convertible to Double. It has a value, but this value is not representing a double number.")
}
} else {
print("distance is nil. It should be some number but it is not set yet")
}

Change Time Conversion Function to Swift Extension

I have a function that converts minutes to either a decimal or a HH:MM string based on a user preference in NSUserDefaults.
For example, 90 minutes would be either 1.5 or 1:30.
Here's my function:
func decimalOrHHMM(value:Int) -> String{
let totalMinutes = Double(value)
let defaults = UserDefaults.standard
if defaults.string(forKey: "displayTotalsAs") == "hhmm"{
//HH:MM
let hours = floor(totalMinutes/60)
let minutes = totalMinutes.truncatingRemainder(dividingBy: 60) //This gives us the remainder
let hrs = String(format: "%.0f", hours) //Remove tenths place
var mins = ""
if minutes < 10{
//Prepend 0
mins = String(format: "0%.0f", minutes)
}else{
mins = String(format: "%.0f", minutes)
}
return "\(hrs):\(mins)"
}else{
//Decimal
return String(format: "%.1f", totalMinutes/60)
}
}
This works great, but I'm wondering if this can be converted to an NSNumberFormatter Swift extension somehow. I'm having trouble knowing what I need to override in order to do all the converting.
Any idea how I can make this an extension?
This is the basic structure for a Swift extension, you could use a function instead of a computed property, but since your original function takes an Int value, a computed property makes sense.
extension Int {
var decimalOrHHMM: String {
return "\(self)" // do your actual conversion here
}
}
You can also choose to extend NSNumberFormatter.

Convert Date to Integer in Swift

I am updating some of my old Swift 2 answers to Swift 3. My answer to this question, though, is not easy to update since the question specifically asks for NSDate and not Date. So I am creating a new version of that question that I can update my answer for.
Question
If I start with a Date instance like this
let someDate = Date()
how would I convert that to an integer?
Related but different
These questions are asking different things:
Swift convert unix time to date and time
Converting Date Components (Integer) to String
Convert Date String to Int Swift
Date to Int
// using current date and time as an example
let someDate = Date()
// convert Date to TimeInterval (typealias for Double)
let timeInterval = someDate.timeIntervalSince1970
// convert to Integer
let myInt = Int(timeInterval)
Doing the Double to Int conversion causes the milliseconds to be lost. If you need the milliseconds then multiply by 1000 before converting to Int.
Int to Date
Including the reverse for completeness.
// convert Int to TimeInterval (typealias for Double)
let timeInterval = TimeInterval(myInt)
// create NSDate from Double (NSTimeInterval)
let myNSDate = Date(timeIntervalSince1970: timeInterval)
I could have also used `timeIntervalSinceReferenceDate` instead of `timeIntervalSince1970` as long as I was consistent. This is assuming that the time interval is in seconds. Note that Java uses milliseconds.
Note
For the old Swift 2 syntax with NSDate, see this answer.
If you are looking for timestamp with 10 Digit seconds since 1970 for API call then, below is code:
Just 1 line code for Swift 4/ Swift 5
let timeStamp = UInt64(Date().timeIntervalSince1970)
print(timeStamp) <-- prints current time stamp
1587473264
let timeStamp = UInt64((Date().timeIntervalSince1970) * 1000) // will give 13 digit timestamp in milli seconds
timeIntervalSince1970 is a relevant start time, convenient and provided by Apple.
If u want the int value to be smaller, u could choose the relevant start time you like
extension Date{
var intVal: Int?{
if let d = Date.coordinate{
let inteval = Date().timeIntervalSince(d)
return Int(inteval)
}
return nil
}
// today's time is close to `2020-04-17 05:06:06`
static let coordinate: Date? = {
let dateFormatCoordinate = DateFormatter()
dateFormatCoordinate.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let d = dateFormatCoordinate.date(from: "2020-04-17 05:06:06") {
return d
}
return nil
}()
}
extension Int{
var dateVal: Date?{
// convert Int to Double
let interval = Double(self)
if let d = Date.coordinate{
return Date(timeInterval: interval, since: d)
}
return nil
}
}
Use like this:
let d = Date()
print(d)
// date to integer, you need to unwrap the optional
print(d.intVal)
// integer to date
print(d.intVal?.dateVal)