Substring from range of two characters - swift

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)

Related

sorting dates to closest by a given time in swift

I have a list of date strings in which I want to sort the list closest to the given time. If two times are clashing then earlier date priority would be considered.
var givenTIme = "10:00AM"
var strDates = ["2021-04-30 10:00AM", "2021-04-16 10:00AM", "2021-04-26 12:00AM", "2021-04-28 09:00AM"]
var output = ["2021-04-16 10:00AM", "2021-04-30 10:00AM", "2021-04-28 09:00AM", "2021-04-26 12:00AM"]
in here we have to find sort the array dates close 10:00AM
If anybody knows the solution please help me out.
Not 100% sure what you need but something like this could be a start?
The strategy is to transform the data into things that can be easily compared to give the sort order that we want.
Working with dates and times is always tricky because of calendars and locale issues.
var givenTIme = "10:00AM"
var calendar = Calendar.current
let strDates = ["2021-04-30 10:00AM", "2021-04-16 10:00AM", "2021-04-26 12:00AM", "2021-04-28 09:00AM"]
let formatter = DateFormatter()
formatter.dateFormat = "y-M-d hh:mma"// hh:mma"
let dates = strDates.compactMap(formatter.date(from:))
struct CompareHelper {
let date: Date
let deltaT: Int
}
let hhmmformatter = DateFormatter()
hhmmformatter.dateFormat = "hh:mma"
let target = hhmmformatter.date(from: givenTIme)!
let dts = dates.map { date -> CompareHelper in
let minute = calendar.component(.minute, from: date)
let hour = calendar.component(.hour, from: date)
let targetMinute = calendar.component(.minute, from: target)
let targetHour = calendar.component(.hour, from: target)
let deltaT = (targetMinute + targetHour * 60) - (minute + hour * 60)
return CompareHelper(date: date, deltaT: deltaT)
}
let sorted = dts.sorted { (lhs: CompareHelper, rhs:CompareHelper) -> Bool in
if lhs.deltaT == rhs.deltaT {
return lhs.date < rhs.date
}
else {
return lhs.deltaT < rhs.deltaT
}
}

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 sort WBS format string in Realm?

I want to sort my Realm object, using one of it properties. It has WBS like format (1.1.3 ,1.1.11, etc) using String as it type.
I'm using RealmSwift 3.11.2 and I've already tried using sorted(by:) and it works! But the 1.1.10 and 1.1.11 will be sorted ahead of 1.1.2
This is the code that I'm using
tasks = tasks.sorted(byKeyPath: "wbs", ascending: true)
I expect the output will be ordered correctly, like [1.1.2, 1.1.10, 1.1.11].
Any help is appreciated, I'm drawing a blank here, is there any way I can do it in Realm ?
You may want to take a minute and do an Internet search on sorting strings.
The issue is that if you have a list of strings like this 1, 2, 3, 10 and they are sorted, they will sort to 1, 10, 2, 3.
This is because how strings are sorted; for this example they are they are generally sorted by the first letter/object in the string (1's) and then by the second letter (0) and so on, so then the strings that start with 1 get grouped up. Then 2's etc.
So you need to either pad them so the formats are the same like
01.01.02, 01.01.10, 01.01.11 etc
which will sort correctly or store the three sections in separate properties as numbers (int's)
class TriadClass {
var first = 0
var second = 0
var third = 0
init(x: Int, y: Int, z: Int) {
self.first = x
self.second = y
self.third = z
}
func getPaddedString() -> String {
let formatter = NumberFormatter()
formatter.format = "00"
let firstNum = NSNumber(value: self.first)
let secondNum = NSNumber(value: self.second)
let thirdNum = NSNumber(value: self.third)
let firstString = formatter.string(from: firstNum)
let secondString = formatter.string(from: secondNum)
let thirdString = formatter.string(from: thirdNum)
let finalString = "\(firstString!).\(secondString!).\(thirdString!)"
return finalString
}
func getPureString() -> String {
let formatter = NumberFormatter()
formatter.format = "0"
let firstNum = NSNumber(value: self.first)
let secondNum = NSNumber(value: self.second)
let thirdNum = NSNumber(value: self.third)
let firstString = formatter.string(from: firstNum)
let secondString = formatter.string(from: secondNum)
let thirdString = formatter.string(from: thirdNum)
let finalString = "\(firstString!).\(secondString!).\(thirdString!)"
return finalString
}
}
Then sort by first, second and third. To get the number for display in the UI I included to functions to format them. Note both functions could be greatly improved and shortened by I left them verbose so you could step through.
The usage would be
let myNum = TriadClass(x: 1, y: 1, z: 10)
let paddedString = myNum.getPaddedString()
print(paddedString)
let nonPaddedString = myNum.getPureString()
print(nonPaddedString)
and an output of
01.01.10
1.1.10
There are a number of other solutions as well but these two are a place to start.

Swift hour and minute from a string to a Date

I'm trying to apply some calcul between two hours.
I receive from firebase my too hours from a String format (HH:mm) and I try to convert it to a Date format (HH:mm). Then apply the calcul before converting again to a string and seed it to the archive in Firebase.
My code:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) {(snapshot) in
if let value = snapshot.value as? [String:String]{
let value = snapshot.value as? NSDictionary
let total_time = value?["TOTAL_TIME"] as? String ?? "00:00" // Received from Firebase
self.oldDateTotalTime = dateFormatter.date(from: total_time)! //trying to convert into a Date format
print(self.oldDateTotalTime)
self.oldStringTotalTime = total_time //just for test
}
This code didn't work because, I think, of the dateFormatter.
S my questions are:
How can I get my Date from my String?
How can I get after my String from my Date?
edit:
to precise my question, my objective is to subtract one time value to a total time value. I've got a total user experience i.e 700 H 50 min with is given on Firebase like this : 700:50. In an other part I've got some specific experiences i.e 1h 40min with is given as before 1:40 in firebase.
My request is to subtract the specific experience from the global.
700:50 - 1:40 = 699:10
On my current Swift experience I only use Date() & DateFormatter to deal with the time and I don't know how to deal only with time and not with date ...
Hope you will better understand this with my edit
If you need, here is the regex code ; can certainly be optimised.
func stringToTime(_ timeStr: String) -> (Int, Int) {
var hours = 0
var minutes = 0
let patternH = "[0-9]*[:]" // digits, followed by :
let regexH = try! NSRegularExpression(pattern: patternH, options: .caseInsensitive)
if let matchH = regexH.firstMatch(in: timeStr, range: NSRange(0..<timeStr.utf16.count)) {
let hStr = String(timeStr[Range(matchH.range(at: 0), in: timeStr)!]).dropLast()
hours = Int(hStr) ?? 0
let patternM = "[:][0-9]{1,2}" // 1 or 2 digits
let regexM = try! NSRegularExpression(pattern: patternM, options: .caseInsensitive)
if let matchM = regexM.firstMatch(in: timeStr, range: NSRange(0..<timeStr.utf16.count)) {
let mStr = String(timeStr[Range(matchM.range(at: 0), in: timeStr)!]).dropFirst()
minutes = Int(mStr) ?? 0
}
}
return (hours, minutes)
}
let timeStr = "700:50"
let time1 = stringToTime("700:50")
let time2 = stringToTime("1:40")
var time = (time1.0 - time2.0, time1.1 - time2.1)
if time.1 < 0 {
time.1 = time.1 + 60
time.0 -= 1
}
print(time)

Calculate time difference from Int in 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"
}