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

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

Analyzing speech and doing an action

I am trying to analyze a sentence and make an action depending on that.
Here are examples of sentences received from speech recognition. I wrote couple of sentences, because we actually don't know what user is going to say for sure, and if he at all says the right pattern.
var str = "20 minutes to take a shower"
var sentence = "seven minutes to make 10 last homeworks"
var sentence2 = "strum guitar for 15 minutes"
var plans = "launch with friend 12:15, then drawing lesson"
I want to extract "20 minutes" ; assign it for the timeValue and launch the timer.
Also, to assign a task to taskValue to represent the task that I am doing. (I thought getting task value by removing "20 minutes" from the initial sentence).
What do you think is the best way to work with the String that I need to analyze?
I thought of finding index ranges and then cutting/copying with the help of indexes, but
The format of the indexes it returns is like this: and I don't know how to extract the number of index. (In this case: 0 through 10)
<NSSimpleRegularExpressionCheckingResult: 0x6000027f0140>{0, 10}{<NSRegularExpression: 0x600003cfcb40> [0-9]{1,} minutes 0x1}
How to ignite the timer? We have to validate that there's the proper command given. And, when we get the timeValue and taskValue back, then how do we ignite the timer? (The whole process was: User pushes button -> user speaking -> speech recognized(and displayed on the screen label) -> sentence analyzed(?) -> timer starts(?) and task displayed in the label(?) )
What is your recommendation for architecture of speech analysis system. Maybe you know some articles on this topic?
Here's the logic for the speech detection.
var timeValue: Int = Int()
var taskValue: String = ""
func stringDeduction(of inputText: String) -> (Int?, String?) {
let pattern = "[0-9]{1,} minutes"
let regexOptions: NSRegularExpression.Options = [.caseInsensitive]
let matchingOptions: NSRegularExpression.MatchingOptions = [.reportCompletion]
// TODO - catch errors with regex
let regex = try! NSRegularExpression(pattern: pattern, options: regexOptions)
// } catch {
// print("error in regex")
// }
let range = NSRange(location: 0, length: inputText.utf8.count)
// \d - matches any digit
// Pattern for time format like this 00:00
//let patternForTime = "[0-9]{1,}:[0-9]{1,2}"
if let matchIndex = regex.firstMatch(in: inputText, options: matchingOptions, range: range) {
print(matchIndex)
} else {
print("No match.")
}
// check whether the string matches and print one of two messages
if let match = regex.firstMatch(in: inputText, range: NSRange(location: 0, length: inputText.utf8.count)) {
print("*: Match!")
} else {
print("*: No match.")
}
/* Question - how to use "mathces" properly?!
if let match = regex.matches(in: testString, options: .reportCompletion ,range: NSRange(location: 0, length: testString.utf8.count)) {
print("*: Match!")
print(match)
} else {
print("*: No match.")
}
*/
func matches(for regex: String, in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
return results.map {
String(text[Range($0.range, in: text)!])
}
} catch {
print("invalid regex")
return []
}
}
var task = regex.stringByReplacingMatches(in: inputText, options: .withoutAnchoringBounds, range: range, withTemplate: "")
//var taskMutableString = NSMutableString(string: str)
//regex.replaceMatches(in: taskMutableString, options: .withoutAnchoringBounds, range: range, withTemplate: "")
//taskMutableString
var timeStringArray = matches(for: pattern, in: inputText)
var timeString = timeStringArray[0]
let time = Int(timeString.replacingOccurrences(of: " minutes", with: ""))
timeValue = time ?? Int()
taskValue = task.replacingOccurrences(of: "to" , with: "", options: .caseInsensitive, range: task.startIndex..<task.index(task.startIndex, offsetBy: 4))
let taskReturn = taskValue
return (time, taskReturn)
// the regex ^[ \t]+|[ \t]+$ matches excess whitespace at the beginning or end of a line.
// what regex, or string method matches exess whitespace at the beginning of a line
}
Here's the extension to work with string like with array. Like this, - str[0..2]
extension String {
subscript (bounds: CountableClosedRange<Int>) -> String {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return String(self[start...end])
}
subscript (bounds: CountableRange<Int>) -> String {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
return String(self[start..<end])
}
}
Here's another approach I have tried: (Although it doesn't seem good)
let timeArray = ["one minute", "two minutes", "three minutes", "four minutes", "five minutes", "six minutes", "seven minutes", "eight minutes", "nine minutes"]
let timeValueCheck = "Answer: \(sentence.containsAny(of: timeArray) ?? "doesn't contain")"
//Dividing String into words
let abc: [String] = str.components(separatedBy: " ")
// Finding a number within an array of words
// There's a problem if there are couple of numbers in the sentence, it returns all of them and not only the needed time.
let numbers = abc.compactMap {
// convert each substring into an Int
return Int($0)
}
for i in 1...100 {
if str.contains(String(i) + " minutes") {
print(i)
}
}
Thank you for any of your help! I've just gone insane with this task for 3 months. Also, if something is unclear or a bit messy, please tell me! I'll try to correct.

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

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)

How to express current time in words

I am able to get current time, Now I want to output it in words.
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
print("hours = \(hour):\(minutes):\(seconds)")
Output
10:30
How to get this like -
It's half past ten
As #MwcsMac points out in his answer, the key to solving this is Formatter (once known as NSFormatter), particularly by setting the .numberStyle to .spellOut.
Although this will pick up the current locale (and thus language), the trouble is that many other languages than English do not use the same "half-past", "quarter-to" terminology - for example, in German 10:30 is "halb elf", literally "half (to) eleven".
Writing code that assumes that the locale is English/American is really bad practice and will probably get the app rejected if it is offered outside those areas, so the best one could really do is format "10:30" as "ten thirty", "zehn dreißig".
Code with apologies to #MwcsMac:
import Foundation
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minute = calendar.component(.minute, from: date)
func spell(_ number: Int, _ localeID: String) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
// Specify the locale or you will inherit the current default locale
formatter.locale = Locale(identifier: localeID)
if let s = formatter.string(from: NSNumber(value: number)) {
// AVOID forced unwrapping at all times!
return s
} else {
return "<Invalid>" // or make return optional and return `nil`
}
}
spell(hour, "EN") + " " + spell(minute, "EN") // "nineteen thirty-three"
spell(hour, "FR") + " " + spell(minute, "FR") // ""dix-neuf trente-trois"
spell(hour, "AR") + " " + spell(minute, "AR") // "تسعة عشر ثلاثة و ثلاثون"
This code will show you that it is possible to achieve the end goal. Keep in mind as stated above you will need create the logic for combinations that you want achieve.
Swift 3
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
func spell(_ number: Int) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut // This will convert the number to words.
return formatter.string(from: NSNumber(value: number))!
}
if minutes == 30 {
let hourString = spell(hour)
print("It's half past \(hourString)")
}
This could be a way that you would use a range for the time frames.
if case 1 ... 14 = minutes {
let hourString = spell(hour) // This will give your hour in word form
let minString = spell(minutes) // This will give your minutes in word form
print("It's \(minString) past \(hourString)")
}