Swift hour and minute from a string to a Date - swift

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)

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
}
}

Calculate Asleep time in HealthKit using Swift

I have the following code that gets the sum of sleep hours. However, it is summing inbed and asleep together. What I am trying to get is a sum for just the asleep time.
func readSleepAnalysis(date: Date) {
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
let startDate = convertSleepStartDate(StartDate: date)
let endDate = convertSleepEndDate(EndDate: date)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: 30, sortDescriptors: [sortDescriptor]) {
(query, samples, error) in
guard
error == nil,
samples == samples as? [HKCategorySample] else {
print("Something went wrong getting sleep analysis: \(String(describing: error))")
return
}
let total = samples?.map(self.calculateSleepHours).reduce(0, {$0 + $1}) ?? 0
DispatchQueue.main.async {
self.userSleepMinutes = total
print("userSleepHours = \(self.userSleepMinutes)")
}
}
healthKit.execute(query)
}
}
func calculateSleepHours(sample: HKSample) -> TimeInterval {
let hours = sample.endDate.timeIntervalSince(sample.startDate) / 60 / 60
return hours
}
I previously discovered that Apple records all data based on UTC. Make sense! However, this may work for active energy and other data like that but total sleep time can't be calculated like this. I am calculating the total time from 6 PM the night prior to 05:59 AM that day. Here is how I am doing that (there may be a better way but it's beyond me at this moment).
func convertSleepStartDate(StartDate: Date) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyy-MM-dd '18':'00':'01' +0000"
let dateString = dateFormatter.string(from: StartDate)
dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss +0000"
let date = dateFormatter.date(from: dateString)
let datePrior = Calendar.current.date(byAdding: .hour, value: -24, to: date!)
print(datePrior as Any)
return datePrior!
}
func convertSleepEndDate(EndDate: Date) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyy-MM-dd '17':'59':'59' +0000"
let dateString = dateFormatter.string(from: EndDate)
dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss +0000"
let date = dateFormatter.date(from: dateString)
print(date as Any)
return date!
}
Any ideas?
Better late than never: In your calculateSleepHours function, you should check sample.value against the HKCategoryValueSleepAnalysis constants (inBed, asleep, awake). That will tell you the interval's type. It will also teach you how Apple measures and returns this data. Keep an eye on the intervals, because when you hit the end of the planned sleep time on the watch, it will give you a summary of inBed time.
let value1 = (sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue) ? "InBed" : "Asleep"
let value2 = (sample.value == HKCategoryValueSleepAnalysis.asleep.rawValue) ? "Asleep" : "Awake"
let value3 = (sample.value == HKCategoryValueSleepAnalysis.awake.rawValue) ? "Awake" : "Asleep"
The 3 values contain the type of constant that each interval is measuring. It is almost always asleep, but may differ depending on the Watch OS versions.
You can print the values and see, on a per interval basis, exactly what is being measured. Be sure to print the sample.startDate and sample.endDate too. That will teach you all you need to know to get actual sleep time between two Dates.

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

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 format time and check "working hours" in Swift

I've been playing with NSDate() but I hit a bump and need help. I need to check working hours and if user is using an app during open hours some green dot will appear.
I work with firebase and data for working hours is:
main
---> key
----> working hours
-------------->
Mon: 12:00-18:00
Tue: 11:30-21:00
etc.
I get the logic. Grab day in week. Grab correct line in Firebase. Grab user current time and see if it is given range. I'm still beginner but I would love to learn how to do this.
Can somebody direct me a little bit?
Here is only code I have for now:
// Check current time
let userTime = NSDate()
let formatter = NSDateFormatter();
formatter.dateFormat = "HH:mm"
let now = formatter.stringFromDate(userTime)
print(now)
Since no question should go unanswered ;)
Since your pattern is stable you can use Regular Expressions with named groups. Keep the regex pattern outside the function.
let regex = try! NSRegularExpression(pattern: "(?<day>\\w{3}):\\s(?<openHour>\\d{2}):(?<openMin>\\d{2})-(?<closeHour>\\d{2}):(?<closeMin>\\d{2})",
options: .caseInsensitive)
Here is a function that takes your input "Mon: 12:00-18:00", you can select the right now based on the correct day, or move the day checking into the function too.
func isOfficeOpenNow(input: String) -> Bool {
let range = NSRange(location: 0, length: input.utf8.count)
guard let match = regex.firstMatch(in: input, options: [], range: range) else {
assert(false, "Epic Fail!")
}
guard let dayRange = Range(match.range(withName: "day"), in: input),
let openHourRange = Range(match.range(withName: "openHour"), in: input),
let openMinRange = Range(match.range(withName: "openMin"), in: input),
let closeHourRange = Range(match.range(withName: "closeHour"), in: input),
let closeMinRange = Range(match.range(withName: "closeMin"), in: input) else {
assert(false, "Did not find the named groups")
}
let day = String(input[dayRange])
guard let openHour = Int(input[openHourRange]),
let openMin = Int(input[openMinRange]),
let closeHour = Int(input[closeHourRange]),
let closeMin = Int(input[closeMinRange]) else {
assert(false, "Failed to convert to ints")
}
print("day: \(day) Opens at: \(openHour):\(openMin) and closes at \(closeHour):\(closeMin)")
// Lets check if its now open (not checking the day....sorry)
let tz = NSTimeZone.default
let now = NSCalendar.current.dateComponents(in: tz, from: Date())
guard let hour = now.hour,
let minute = now.minute else {
assert(false, "this should never happen")
}
let rightNowInMinutes = hour * 60 + minute
let opensAt = openHour * 60 + openMin
let closesAt = closeHour * 60 + closeMin
assert(opensAt < closesAt, "Opening after closing does not make sense")
return rightNowInMinutes > opensAt &&
rightNowInMinutes < closesAt
}
Here is how you could use it
if isOfficeOpenNow(input: "Mon: 12:00-18:00") {
print("Store open")
} else {
print("Store closed")
}