Calculate time difference from Int in Swift - swift

I need to calculate the difference between two Int and format it to show hours, minutes. I can the function below to partially apart from the initial int difference.
Example: If i have a start Int of 0811 and then an end Int of 0912 the difference is 101. If I then take 12 from the result I should have 91. When I use the result to convert to time it returns 1 hour and 31 mins - which is correct. However I need to somehow convert it further up the chain and then take it off to format the time correctly. This should mean the 101 should be 1 hour 1 minute.
func calculateTimeDifference(start: Int, end: Int, longVersion: Bool) -> String {
let count = end - start
let total = minutesToHoursMinutes(minutes: count)
var formatted = ""
if total.hours != 0 {
formatted += "\(total.hours)"
let amount = total.hours > 1 ? " hours " : " hour "
formatted += amount
}
if total.leftMinutes != 0 {
formatted += "\(total.leftMinutes)"
let amount = total.leftMinutes > 1 ? " minutes " : " minute "
formatted += amount
}
return String(describing: formatted)
}
func minutesToHoursMinutes(minutes : Int) -> (hours : Int, leftMinutes : Int) {
return (minutes / 60, (minutes % 60))
}

You shouldn't calculate the difference between two times expressed in Ints by subtracting them. Use the proper Date API.
Here, I converted the Ints to strings first and then parsed them using a date formatter. After that timeIntervalSince can tell you the difference in seconds. You just need some modulus and division to get the hours and minutes from that:
func calculateTimeDifference(start: Int, end: Int) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "HHmm"
var startString = "\(start)"
if startString.characters.count < 4 {
for _ in 0..<(4 - startString.characters.count) {
startString = "0" + startString
}
}
var endString = "\(end)"
if endString.characters.count < 4 {
for _ in 0..<(4 - endString.characters.count) {
endString = "0" + endString
}
}
let startDate = formatter.date(from: startString)!
let endDate = formatter.date(from: endString)!
let difference = endDate.timeIntervalSince(startDate)
return "\(Int(difference) / 3600)Hr \(Int(difference) % 3600 / 60)Min"
}

Inherintly you are approaching this from a strange angle and causing yourself issues.
Instead of Int, which is inappropriate for storing a time, use TimeInterval which can hold a full date information, e.g.:
let second:TimeInterval = 1.0
let minute:TimeInterval = 60.0
let hour:TimeInterval = 60.0 * minute
let day:TimeInterval = 24 * hour
Then when you want to determine the time difference between two times it's very basic arithmetic.

SWIFT 4
In Swift 4 characters is deprecated, so Sweepers solution in Swift 4 is
func calculateTimeDifference(start: Int, end: Int) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "HHmm"
var startString = "\(start)"
if startString.count < 4 {
for _ in 0..<(4 - startString.count) {
startString = "0" + startString
}
}
var endString = "\(end)"
if endString.count < 4 {
for _ in 0..<(4 - endString.count) {
endString = "0" + endString
}
}
let startDate = formatter.date(from: startString)!
let endDate = formatter.date(from: endString)!
let difference = endDate.timeIntervalSince(startDate)
return "\(Int(difference) / 3600)Hr \(Int(difference) % 3600 / 60)Min"
}

Related

Create TimeZone object from timeZoneOffset string?

What would be a clean way to initialise a Swift TimeZone object from timeZoneOffset string of the form: "+HH:MM".
I am looking for something of the form:
extension TimeZone {
init?(UTCOffsetString ofs: String) {
let signIndex = ofs.firstIndex(of: "+") ?? ofs.firstIndex(of: "-")
let sign = ofs[signIndex!]
let separatorIndex = ofs.firstIndex(of: ":")!
let hhRange = ofs.index(signIndex!, offsetBy: 1)..<separatorIndex
let hh = ofs[hhRange]
let mmRange = ofs.index(separatorIndex, offsetBy: 1)..<ofs.index(separatorIndex, offsetBy: 3)
let mm = ofs[mmRange]
var offsetInMin = (Int(String(hh))! * 60) + Int(String(mm))!
if sign == "-" {
offsetInMin.negate()
}
let offsetInSec = offsetInMin * 60
// Convert string to TimeZone, eg.
self.init(secondsFromGMT: offsetInSec)
}
}
let tz = TimeZone.init(UTCOffsetString: "-07:30")
print(tz?.identifier ?? "unknown")
The above code block is a correct solution and prints:
GMT-0730
However I am looking for a cleaner solution where I don't need to extract substrings in order to compute the offset.
My suggestion is to use DateFormatter which is able to parse the time zone string format. refZoneString is the reference to UTC in the current time zone.
extension TimeZone {
init?(UTCOffsetString ofs: String) {
let refZoneString = "+0000"
let formatter = DateFormatter()
formatter.dateFormat = "Z"
guard let refDate = formatter.date(from: refZoneString),
let date = formatter.date(from: ofs) else { return nil }
self.init(secondsFromGMT: Calendar.current.dateComponents([.second], from: date, to: refDate).second!)
}
}
let tz = TimeZone.init(UTCOffsetString: "-07:30")
print(tz?.identifier ?? "unknown")
I don't know what you mean by a cleaner but you can combine collection methods suffix and prefix to avoid the need to use String index to access the desired values:
let time = "-02:00"
let hours = Int(time.suffix(5).prefix(2)) ?? 0
let minutes = Int(time.suffix(2)) ?? 0
var offset = hours * 3600 + minutes * 60
if time.first == "-" { offset = -offset }
print(offset) // -7200

How to iterate through a function each time altering the value of the parameters provided

Hi so I have a class Calculations with a series of functions one of these is keplerianElementsToEcef. In my view controller I hard code the values for the parameters and then call the function. However later on in a seperate class I have a bool isInRange. If my spacecraft is out of cellular range, I return false and a string as well. I also want to then iterate through the keplerianElementsToEcef function, each time increasing the timeOfCalculation parameter by two minutes until at some point in time in the future the satellite is in range.
I've tried to simply call the function but increase the value used initially as the time, current time, by two minutes. The other variables rangeMeanMotion etc, are the same as those hardcoded in the view controller
var isInRange: Bool
var rangeString: String
if distance < range {
isInRange = true
rangeString = "In Range"
} else {
isInRange = false
rangeString = "Not In Range"
while isInRange == false {
var dateString = dateFormatter.date(from: calculationTime!)!
var updatedDate = dateString.addingTimeInterval(TimeInterval(5.0 * 60.0))
var updateDateAsString = dateFormatter.string(from: updatedDate)
Calculations.shared.keplerianElementsToECEF(meanMotion: rangeMeanMotion, eccentricity: rangeEccentricity, Inclination: rangeInclination, LongitudeAscendingNode: rangeLongitudeAscendingNode, argumentPerigee: rangeArgumentPerigee, M0: rangeM0, epoch: rangeEpoch, date: updateDateAsString) {
}
}
}
In the function parameters under date: updateDateAsString I get the following error: Extra argument 'date' in call
var timeOfCalculation : TimeInterval = 0
func doItUntilSpacecraftIsInRange(){
repeat {
timeOfCalculation += TimeInterval(2.0 * 60.0)
Calculations.shared.keplerianElementsToECEF(meanMotion: rangeMeanMotion, eccentricity: rangeEccentricity, Inclination: rangeInclination, LongitudeAscendingNode: rangeLongitudeAscendingNode, argumentPerigee: rangeArgumentPerigee, M0: rangeM0, epoch: rangeEpoch, date: updateDateAsString)
} while spacecraft.isInRange == false
}
doItUntilSpacecraftIsInRange()
I solved this issue. I made the statement iterate during a certain time period (1 day) and my code looks like this:
else {
isInRange = false
rangeString = "Not In Range"
print(calculationTime)
if let calcTime = calculationTime {
let parsedDate = dateFormatter.date(from: calcTime) ?? Date()
for interval in stride(from: 0, to: 1440, by: 2) {
var updatedDate = parsedDate.addingTimeInterval(TimeInterval(interval * 60))
var updateDateAsString = dateFormatter.string(from: updatedDate)
Calculations.shared.keplerianElementsToECEF(meanMotion: rangeMeanMotion, eccentricity: rangeEccentricity, Inclination: rangeInclination, LongitudeAscendingNode: rangeLongitudeAscendingNode, argumentPerigee: rangeArgumentPerigee, M0: rangeM0, epoch: rangeEpoch, date: updateDateAsString)
let xDistance = ecefX - wgs84X
let yDistance = ecefY - wgs84Y
let zDistance = ecefZ - wgs84Z
let iteratedDistance = sqrt(xDistance*xDistance + yDistance*yDistance + zDistance*zDistance)
if iteratedDistance < 7000 {
nextVisible = updateDateAsString
break
}
}
}
}

Substring from range of two characters

Im trying to convert a time displaying string to a Int.
The syntax looks like this and i want to extract the integers and multiply the first and add the later to get the time in minutes.
12h 10m
3h 14m
16h 0m
Since the displayed string can be either hhmm, hmm, hhm or hm I cant substring with a fixed offset.
I'm trying to substring the string by first finding the " " and then the m.
In other languages this would be easy but for some reson I cant get it to work in swift.
Please help me, you're my only hope.
You can get your time components (hour and minutes) using components(separated:) and get the first component (hour), multiply it by 60 and add the last component (minutes) to it.
extension String {
var minutes: Int {
var minutes = 0
if let hourChars = components(separatedBy: " ").first?.characters.dropLast(),
let hours = Int(String(hourChars)) {
minutes += hours * 60
}
if let minChars = components(separatedBy: " ").last?.characters.dropLast(),
let mins = Int(String(minChars)) {
minutes += mins
}
return minutes
}
}
Testing
let str1 = "12h 10m"
let minutes1 = str1.minutes // 730
let str2 = "3h 14m"
let minutes2 = str2.minutes // 194
let str3 = "16h 0m"
let minutes3 = str3.minutes // 960
i think in your case it's better to spilt the string that you have twice
let string = "12h 10m 3h 14m 16h 0m"
var array = string.characters.split(separator: "h").map(String.init)
var result: [String] = []
array.forEach {
result.append(contentsOf: $0.characters.split(separator: "m").map(String.init))
}
// ["12", " 10", " 3", " 14", " 16", " 0"]
print(result)
Since you need to manipulate with date/time strings only I think you should better use a build-in DateFormatter for doing it
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH'h' mm'm'"
let date = dateFormatter.date(from: "2h 10m")!
let hour = Calendar.current.component(.hour, from: date)
// returns 2
let minutes = Calendar.current.component(.minute, from: date)
// returns 10
best solution for your question is
func getTime(time:String)->(Int,Int)
{
let arry = time.components(separatedBy: " ")
let hours = arry[0]
let min = arry[1]
let indexForHour = hours.index(hours.startIndex, offsetBy: (hours.characters.count - 1))
let indexForMin = min.index(min.startIndex, offsetBy: (min.characters.count - 1))
let hour = Int(hours.substring(to: indexForHour))
let minut = Int(min.substring(to: indexForMin))
return (hour!,minut!)
}
let str1 = "12h 10m"
let str2 = "3h 14m"
let str3 = "16h 0m"
let firstTime:(hour:Int,min:Int) = getTime(time:str1)
print(firstTime)
let secondTime:(hour:Int,min:Int) = getTime(time:str2)
print(secondTime)
let thirdTime:(hour:Int,min:Int) = getTime(time:str3)
print(thirdTime)
OUTPUT
(hour: 12, min: 10)
(hour: 3, min: 14)
(hour: 16, min: 0)

How to convert min to hours in swift3?

This is my JSON data
{
"service_facility_id": 1,
"service_id": 4,
"facility_name": "Fitting",
"charge_per_min": 40,
"charge_per_km": 50
},
{
"service_facility_id": 10,
"service_id": 4,
"facility_name": "Health Care",
"charge_per_min": 100,
"charge_per_km": 0
}
Currently i'm using Get method to print specific JSON output in X Code. So i managed to Minute(charge_per_min) value . But I want to display in a label in HH(Hours) format, so how to convert it minute to hours formate and disply into a uilabel..
The code as below.
if let con = country["charge_per_km"] as? Int {
print("+++++\(con) +++ ")
self.chargPerKm.append(con)
print(self.chargPerKm)
let encodedArray : NSData = NSKeyedArchiver.archivedData(withRootObject:self.chargPerKm) as NSData
//Saving
let defaults = UserDefaults.standard
defaults.setValue(encodedArray, forKey:"charge_per_km")
print("\(UserDefaults.standard.value(forKey: "charge_per_km")!)")
defaults.synchronize()
print(defaults)
}
any one help me.
try it in playground
func minutesToHoursAndMinutes(_ minutes: Int) -> (hours: Int , leftMinutes: Int) {
return (minutes / 60, (minutes % 60))
}
let tuple = minutesToHoursAndMinutes(100)
tuple.hours /// 1
tuple.leftMinutes /// 40
To convert from minutes to hours (could be replaced with any supported measurement values)
func calculateTime(_ timeValue: Float) -> String {
let timeMeasure = Measurement(value: Double(timeValue), unit: UnitDuration.minutes)
let hours = timeMeasure.converted(to: .hours)
if hours.value > 1 {
let minutes = timeMeasure.value.truncatingRemainder(dividingBy: 60)
return String(format: "%.f %# %.f %#", hours.value, "h", minutes, "min")
}
return String(format: "%.f %#", timeMeasure.value, "min")
}
As an example
let value = calculateTime(50) //50 min
let value1 = calculateTime(125) // 2 h 5 min

More Functional / Swift way to convert time to minutes

I'm trying to convert a length of time in "Hours:Minutes" to "minutes". Time is given as a String, and I want to return a Double of minutes.
Currently I'm using the following function:
func convertMinHoursToDouble(length: String) -> Double {
var hours = 0.0
var minutes = 0.0
let lengthCleaned = length.stringByReplacingOccurrencesOfString(":", withString: "")
var count = 0
for char in lengthCleaned.characters {
if count == 0 {
hours = Double("\(char)")! * 60
} else if count == 1 {
minutes = Double("\(char)")! * 10
} else if count == 2 {
minutes = Double("\(char)")! + minutes
}
++count
}
return hours+minutes
}
let time = "2:16"
let convertedTime = convertMinHoursToDouble(time)
print(convertedTime) // prints 136.0
This works, however I'm trying to do this in a more functional / Swift way. How can it be done with the reduce function. This is the closest I can get to the solution.
let convertedTime = time.characters.reduce(0) { (dub, char) in dub + Double(String(char))! }
The pure Swift way would be :
let time = "02:16"
let converted = time.characters.split(":")
.flatMap { Int(String($0)) }
.reduce(0) { $0 * 60 + $1 }
print(converted) //"136\n"
Functional solution:
func convertMinHoursToDouble(time: String) -> Int {
let timeComps = (time as NSString).componentsSeparatedByString(":") as [NSString]
return timeComps.reduce(0) { acc, item in
acc * 60 + item.integerValue
}
}
let time = "02:15"
let convertedTime = convertMinHoursToDouble(time)
print(convertedTime)
You split the string into components, and reduce on that array. This works like a charm also for strings like "03:24:34" for which it computes the time in seconds.
You can add additional validation logic if you want to deal with malformed strings: no ":", more than one ":", invalid minutes value (e.g. 78), etc.