Create TimeZone object from timeZoneOffset string? - swift

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

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

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)

Swift - Retrieve timezone from ISO8601 date string

I have dates saved in a database in this format - "yyyy-MM-dd'T'HH:mm:ssZ". For example, "2018-05-17T11:15:00+0330". The time zone varies, it is in whatever user's local time zone is.
I want to retrieve and display the date like "May 17, 2018 11.15AM", keeping the date/time in the same time zone as it was saved. How can I do that?
I tried this.
let dateString = "2018-05-17T11:15:00+0330"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
//formatter.timeZone = ???
print(formatter.date(from: dateString))
But I get the Date object in local timezone and converting that to date string displays the date in my local timezone as well.
Is there anything like this, https://stackoverflow.com/a/46216251/1373592, in Swift?
Thanks.
What you are really trying to do, is to ignore the time zone.
You can just strip off the last 5 characters from the date string, parse it in one format, and format it in another format:
let dateString = "2018-05-17T11:15:00+0330"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = formatter.date(from: String(dateString.dropLast(5)))! // note the stripping off here
formatter.dateFormat = "MMMM d, yyyy hh.mma"
print(formatter.string(from: date))
Note that you can't compare this date with other dates because you've stripped its time zone off.
Also, SwiftDate is a similar library to JodaTime.
Here's a little extension to TimeZone I just whipped up. You can use it to create a TimeZone from an ISO8601 date string. It will handle timezones in the string in the following formats:
Z (for UTC time)
+/-HH
+/-HHmm
+/-HH:mm
Here is the extension:
extension TimeZone {
init?(iso8601: String) {
let tz = iso8601.dropFirst(19) // remove yyyy-MM-ddTHH:mm:ss part
if tz == "Z" {
self.init(secondsFromGMT: 0)
} else if tz.count == 3 { // assume +/-HH
if let hour = Int(tz) {
self.init(secondsFromGMT: hour * 3600)
return
}
} else if tz.count == 5 { // assume +/-HHMM
if let hour = Int(tz.dropLast(2)), let min = Int(tz.dropFirst(3)) {
self.init(secondsFromGMT: (hour * 60 + min) * 60)
return
}
} else if tz.count == 6 { // assime +/-HH:MM
let parts = tz.components(separatedBy: ":")
if parts.count == 2 {
if let hour = Int(parts[0]), let min = Int(parts[1]) {
self.init(secondsFromGMT: (hour * 60 + min) * 60)
return
}
}
}
return nil
}
}
And a test:
print(TimeZone(iso8601: "2018-05-17T11:15:00+0330"))
You can combine this parsing and formatting so the final result is in the original timezone.
let dateString = "2018-05-17T11:15:00+0330"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
if let date = formatter.date(from: dateString), let tz = TimeZone(iso8601: dateString) {
let newFmt = DateFormatter()
newFmt.dateStyle = .medium
newFmt.timeStyle = .short
newFmt.timeZone = tz
let newString = newFmt.string(from: date)
print(newString)
}
Quick fix to #rmaddy answer as his answer has a problem with negative offset specially minute component as #jjoelson mentioned
extension TimeZone {
init?(iso8601: String) {
let tz = iso8601.dropFirst(19) // remove yyyy-MM-ddTHH:mm:ss part
if tz == "Z" {
self.init(secondsFromGMT: 0)
} else if tz.count == 3 { // assume +/-HH
if let hour = Int(tz) {
self.init(secondsFromGMT: hour * 3600)
return
}
} else if tz.count == 5 { // assume +/-HHMM
if let hour = Int(tz.dropLast(2)), var min = Int(tz.dropFirst(3)) {
min = (hour < 0) ? -1 * min : min
self.init(secondsFromGMT: (hour * 60 + min) * 60)
return
}
} else if tz.count == 6 { // assime +/-HH:MM
let parts = tz.components(separatedBy: ":")
if parts.count == 2 {
if let hour = Int(parts[0]), var min = Int(parts[1]) {
min = (hour < 0) ? -1 * min : min
self.init(secondsFromGMT: (hour * 60 + min) * 60)
return
}
}
}
return nil
}
}

How to split date from string in Swift?

How can I split only "2017-09-10" from strings like"\u200c2017-09-10".
days = "\u200c2017-09-10"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
date = formatter.date(from: days)
date return nil when it is not split.
Try this:
days = "\u200c2017-09-10"
let last10 = String(days.characters.suffix(10))
Output: 2017-09-10
Basically you are just taking the last 10 characters of the string days.
You used string seprated function like this:-
func SplitDatefromString() -> String {
let getString = self.components(separatedBy: "-")
guard getString.count != 0 else {
return "0"
}
return getString[0]
}

Comparing Time Strings

I have 2 times that create a time range that a map annotation is visible(a start time and an end time). Whenever the current time is greater than the end time, the annotation is removed. The user inputs the times as a string then I add that time string to the current date string, then I convert the date as "h:mm a, M/dd/yyyy" into an actual date. The problem I am having is that if someone says the event starts at night (PM) then ends in the early morning or something (AM) then the event annotation is instantly removed because it creates the end time as AM time of the day that is about to end instead of the new day. Here is my current method for getting the time strings in code:
let SpotStartTime = StartTimeTextField.text;
let SpotEndTime = EndTimeTextField.text;
let date = NSDate();
let formatter = NSDateFormatter();
formatter.dateFormat = "M/dd/yyyy";
let Date = formatter.stringFromDate(date)
let EndTime = SpotEndTime;
let RealEndTime = (EndTime! + ", " + Date);
print(RealEndTime);
then I convert this string into a date using this method:
let TimeStr = EndTime
let dateFormater : NSDateFormatter = NSDateFormatter()
dateFormater.dateFormat = "h:mm a, M/dd/yyyy
let EndDate = (dateFormater.dateFromString(TimeStr))
let realEnd = EndDate
How do I modify this so that I can compare the StartTimeTextField to the EndTimeTextField? I need something that basically compares the two and if the StartTimeTextField has "PM" in it and the EndTimeTextField has "AM" in it then the EndTime gets 24 hours added to it, but I don't know how to do this in code.
Can anyone help? Thanks
I hope that I understood what you want to do, I come up with this function:
func getTime(startTime:String, startM:String, endTiem: String, endM:String) {
let date = NSDate();
let formatter = NSDateFormatter();
formatter.dateFormat = "M/dd/yyyy";
let dateStrStart = formatter.stringFromDate(date)
var dateStrEnd = ""
if (startM == "PM" && endM == "AM") {
let dateEnd = NSDate(timeInterval: 86400, sinceDate: date)
let formatter = NSDateFormatter();
formatter.dateFormat = "M/dd/yyyy";
dateStrEnd = formatter.stringFromDate(dateEnd)
} else {
dateStrEnd = dateStrStart
}
let RealEndTime = (endTiem + ", " + dateStrEnd);
let RealStartTime = (startTime + ", " + dateStrStart);
}
you just have to grab the AM or PM part.
And if the startTime is PM and the endTime is AM then your dateEnd is the currentDate plus one day.