I have an array of strings that contains a time pattern (e.g '30 min'):
let arrayOfStrings: [String] = ["Episode 1 - 23 min", "Episode 2 - 42 min", "Episode 3 - 45 min"]
func convertToTimeInterval(from string: String) -> TimeInterval {
// logic here...
return timeInterval // Double
}
So using such a method, I want from the results:
arrayOfStrings.forEach { string in
print(convertToTimeInterval(from: string))
}
// Prints
1380 // timeinterval from "23 min"
2520 // timeinterval from "42 min"
2700 // timeinterval from "45 min"
I have tried splitting from the the 'min' character but the problem is that it has to support multiple language format. So there is no way to know in advance the character to split from.
I have also tried getting the CharacterSet.separatedBy but the string can contains multiple decimal that have nothing to do with the time (e.g Episode 1)
Thanks for your help!
A rather underestimated but powerful API in Objective-C and Swift is (NS)Scanner.
Assuming there is always a hyphen and a whitespace character before the duration value just scanUpTo "- " then scan the pattern and then scan the numeric value as Double and multiply it with 60.
func convertToTimeInterval(from string: String) -> TimeInterval? {
let scanner = Scanner(string: string)
scanner.scanUpToString("- ")
scanner.scanString("- ")
guard let duration = scanner.scanDouble() else { return nil }
return duration * 60.0
}
Or – a bit more elaborate – with NSRegularExpression. The pattern captures one or more digits after " - "
func convertToTimeInterval(from string: String) -> TimeInterval? {
let regex = try! NSRegularExpression(pattern: #"\s-\s(\d+)"#)
guard let firstMatch = regex.firstMatch(in: string, range: NSRange(string.startIndex..., in: string)) else { return nil }
let durationRange = Range(firstMatch.range(at: 1), in: string)!
return Double((string[durationRange]))! * 60.0
}
However it becomes more convenient with the new Regex API introduced in Swift 5.7
func convertToTimeInterval(from string: String) -> TimeInterval? {
let regex = Regex {
" - "
Capture { OneOrMore(.digit) } transform: {
Double($0)! * 60.0
}
}
return string.firstMatch(of: regex)?.output.1
}
you could try this approach, where each string in the array is
split into sections, and the minutes extracted and returned as a Double/TimeInterval.
This should (not tested) cater for multiple languages that uses Western Arabic numerals
let arrayOfStrings: [String] = ["Episode 1 - 23 min", "Episode 2 - 42 min", "Episode 3 - 45 min"]
arrayOfStrings.forEach { string in
let t = convertToTimeInterval(from: string)
print("--> timeInterval: \(t) minutes or \(t * 60) seconds")
}
func convertToTimeInterval(from string: String) -> TimeInterval {
if let last = string.split(separator: "-").last {
let term2 = last.split(separator: " ")
if let minutes = term2.first {
if let result = Double(minutes.trimmingCharacters(in: .whitespacesAndNewlines)) {
return result
}
}
}
return 0.0 // <-- adjust as desired
}
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
}
}
}
}
Currently working on a simple function which does a great job for me..
For example:
If i have 1000, It'll print out 1.0K, or 1,000,000 it'll be 1M, everything works fine until here,
What if i wanted to turn 1,000,000,000 into 1B?
I tried the following ->
func formatPoints(from: Int) -> String {
let number = Double(from)
let thousand = number / 1000
let million = number / 1000000
let billion = number / 1000000000
if million >= 1.0 {
return "\(round(million*10)/10)M"
} else if thousand >= 1.0 {
return "\(round(thousand*10)/10)K"
} else if billion >= 1.0 {
return ("\(round(billion*10/10))B")
} else {
return "\(Int(number))"}
}
print(formatPoints(from: 1000000000))
But it returns 1000.0M, not 1B
Thanks!
This answer formats by truncating (versus rounding). 1,515 rounded would generate 2k whereas truncated would generate 1.5k. The function requires reducing a number's scale (removing digits to the right of the decimal) which I've just packaged as an extension so it can be used anywhere (not just in the function).
extension Double {
func reduceScale(to places: Int) -> Double {
let multiplier = pow(10, Double(places))
let newDecimal = multiplier * self // move the decimal right
let truncated = Double(Int(newDecimal)) // drop the fraction
let originalDecimal = truncated / multiplier // move the decimal back
return originalDecimal
}
}
func formatNumber(_ n: Int) -> String {
let num = abs(Double(n))
let sign = (n < 0) ? "-" : ""
switch num {
case 1_000_000_000...:
var formatted = num / 1_000_000_000
formatted = formatted.reduceScale(to: 1)
return "\(sign)\(formatted)B"
case 1_000_000...:
var formatted = num / 1_000_000
formatted = formatted.reduceScale(to: 1)
return "\(sign)\(formatted)M"
case 1_000...:
var formatted = num / 1_000
formatted = formatted.reduceScale(to: 1)
return "\(sign)\(formatted)K"
case 0...:
return "\(n)"
default:
return "\(sign)\(n)"
}
}
You can fine tune this method for specific cases, such as returning 100k instead of 100.5k or 1M instead of 1.1M. This method handles negatives as well.
print(formatNumber(1515)) // 1.5K
print(formatNumber(999999)) // 999.9K
print(formatNumber(1000999)) // 1.0M
The following logic of if-else statements shows you what goes first and what last:
import Foundation
func formatPoints(from: Int) -> String {
let number = Double(from)
let billion = number / 1_000_000_000
let million = number / 1_000_000
let thousand = number / 1000
if billion >= 1.0 {
return "\(round(billion * 10) / 10)B"
} else if million >= 1.0 {
return "\(round(million * 10) / 10)M"
} else if thousand >= 1.0 {
return "\(round(thousand * 10) / 10)K"
} else {
return "\(Int(number))"
}
}
print(formatPoints(from: 1000)) /* 1.0 K */
print(formatPoints(from: 1000000)) /* 1.0 M */
print(formatPoints(from: 1000000000)) /* 1.0 B */
Billion must go first.
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"
}
I need to save files in an alphabetical order.
Now my code is saving files in numeric order
1.png
2.png
3.png ...
The problem is when i read this files again I read this files as described here
So I was thinking of changing the code and to save the files not in a numeric order but in an alphabetical order as:
a.png b.png c.png ... z.png aa.png ab.png ...
But in Swift it's difficult to increment even Character type.
How can I start from:
var s: String = "a"
and increment s in that way?
You can keep it numeric, just use the right option when sorting:
let arr = ["1.png", "19.png", "2.png", "10.png"]
let result = arr.sort {
$0.compare($1, options: .NumericSearch) == .OrderedAscending
}
// result: ["1.png", "2.png", "10.png", "19.png"]
If you'd really like to make them alphabetical, try this code to increment the names:
/// Increments a single `UInt32` scalar value
func incrementScalarValue(_ scalarValue: UInt32) -> String {
return String(Character(UnicodeScalar(scalarValue + 1)))
}
/// Recursive function that increments a name
func incrementName(_ name: String) -> String {
var previousName = name
if let lastScalar = previousName.unicodeScalars.last {
let lastChar = previousName.remove(at: previousName.index(before: previousName.endIndex))
if lastChar == "z" {
let newName = incrementName(previousName) + "a"
return newName
} else {
let incrementedChar = incrementScalarValue(lastScalar.value)
return previousName + incrementedChar
}
} else {
return "a"
}
}
var fileNames = ["a.png"]
for _ in 1...77 {
// Strip off ".png" from the file name
let previousFileName = fileNames.last!.components(separatedBy: ".png")[0]
// Increment the name
let incremented = incrementName(previousFileName)
// Append it to the array with ".png" added again
fileNames.append(incremented + ".png")
}
print(fileNames)
// Prints `["a.png", "b.png", "c.png", "d.png", "e.png", "f.png", "g.png", "h.png", "i.png", "j.png", "k.png", "l.png", "m.png", "n.png", "o.png", "p.png", "q.png", "r.png", "s.png", "t.png", "u.png", "v.png", "w.png", "x.png", "y.png", "z.png", "aa.png", "ab.png", "ac.png", "ad.png", "ae.png", "af.png", "ag.png", "ah.png", "ai.png", "aj.png", "ak.png", "al.png", "am.png", "an.png", "ao.png", "ap.png", "aq.png", "ar.png", "as.png", "at.png", "au.png", "av.png", "aw.png", "ax.png", "ay.png", "az.png", "ba.png", "bb.png", "bc.png", "bd.png", "be.png", "bf.png", "bg.png", "bh.png", "bi.png", "bj.png", "bk.png", "bl.png", "bm.png", "bn.png", "bo.png", "bp.png", "bq.png", "br.png", "bs.png", "bt.png", "bu.png", "bv.png", "bw.png", "bx.png", "by.png", "bz.png"]`
You will eventually end up with
a.png
b.png
c.png
...
z.png
aa.png
ab.png
...
zz.png
aaa.png
aab.png
...
Paste this code in the playground and check result. n numbers supported means you can enter any high number such as 99999999999999 enjoy!
you can uncomment for loop code to check code is working fine or not
but don't forget to assign a lesser value to counter variable otherwise Xcode will freeze.
var fileName:String = ""
var counter = 0.0
var alphabets = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
let totalAlphaBets = Double(alphabets.count)
let numFiles = 9999
func getCharacter(counter c:Double) -> String {
var chars:String
var divisionResult = Int(c / totalAlphaBets)
let modResult = Int(c.truncatingRemainder(dividingBy: totalAlphaBets))
chars = getCharFromArr(index: modResult)
if(divisionResult != 0){
divisionResult -= 1
if(divisionResult > alphabets.count-1){
chars = getCharacter(counter: Double(divisionResult)) + chars
}else{
chars = getCharFromArr(index: divisionResult) + chars
}
}
return chars
}
func getCharFromArr(index i:Int) -> String {
if(i < alphabets.count){
return alphabets[i]
}else{
print("wrong index")
return ""
}
}
for _ in 0...numFiles {
fileName = getCharacter(counter: counter)+".png"
print(fileName)
counter += 1
}
fileName = getCharacter(counter: Double(numFiles))+".png"
print(fileName)