Trouble Initializing TimeZone from TimeZone.abbreviation() - ios

I am storing user birth dates on my backend via storing a date component dictionary. It looks something like this:
{
"day": 1,
"month": 1,
"year": 1970,
"timeZone": "GMT"
}
To store this object, it grabs the user's birth day, month, and year from user input. The user time zone, however, is gathered via TimeZone.current.abbreviation().
Now, some of my user birthdate objects on my backend have their "timeZone" formatted as "CST", "BST", or "PDT". "timeZone"s that are formatted this way successfully initialize a TimeZone on the front end via let timeZone = TimeZone(abbreviation: "CST")!, let timeZone = TimeZone(abbreviation: "BST")!, or let timeZone = TimeZone(abbreviation: "PDT")!, respectively.
The problem is, other user birthdate objects on my backend have their "timeZone" formatted as "GMT+8". When trying to initialize "timeZone"s formatted like this via let timeZone = TimeZone(abbreviation: "GMT+8")!, the initialization returns nil. I also tried let timeZone = TimeZone(identifier: "GMT+8")!, but this returns nil as well.
Is there a way to initialize a TimeZone when it is formatted with respect to its offset to GMT as opposed to its unique abbreviation? I've seen a TimeZone initializer that is TimeZone(secondsFromGMT: Int). Could I simply take the 8 from "GMT+8" and multiply it by 3600 (the number of seconds in an hour) and pass this result to TimeZone(secondsFromGMT: Int)?

I ended up writing code that adapts my application to account for these unexpected fringe cases where a TimeZone's abbreviation is formatted like "GMT+8" rather than "SGT". I created an extension to TimeZone:
extension TimeZone {
static func timeZone(from string: String) -> TimeZone {
//The string format passed into this function should always be similar to "GMT+8" or "GMT-3:30"
if string.contains("±") {
//This case should always be "GMT±00:00", or simply GMT
return TimeZone(secondsFromGMT: 0)!
} else {
//If the string doesn't contain "±", then there should be some offset. We will split the string into timeZone components. "GMT+8" would split into ["GMT", "8"]. "GMT-3:30" would split int ["GMT","3","30"]
let timeZoneComponents = string.components(separatedBy: CharacterSet(charactersIn: "+-:"))
var isAheadOfGMT: Bool!
//Check if the string contains "+". This will dictate if we add or subtract seconds from GMT
if string.contains("+") {
isAheadOfGMT = true
} else {
isAheadOfGMT = false
}
//Grab the second element in timeZoneElements. This represents the offset in hours
let offsetInHours = Int(timeZoneComponents[1])!
//Convert these hours into seconds
var offsetInSeconds: Int!
if isAheadOfGMT {
offsetInSeconds = offsetInHours * 3600
} else {
offsetInSeconds = offsetInHours * -3600
}
//Check if there is a colon in the passed string. If it does, then there are additional minutes we need to account for
if string.contains(":") {
let additionalMinutes = Int(timeZoneComponents[2])!
let additionalSeconds = additionalMinutes * 60
offsetInSeconds += additionalSeconds
}
//Create a TimeZone from this calculated offset in seconds
let timeZoneFromOffset = TimeZone(secondsFromGMT: offsetInSeconds)!
//Return this value
return timeZoneFromOffset
}
}
}
It is used like so:
let json: [String:String] = ["timeZone":"GMT+8"]
let timeZone = json["timeZone"]
let birthDate: BirthDate!
if let timeZoneFromAbbrev = TimeZone(abbreviation: timeZone) {
birthDate = BirthDate(day: birthDay, month: birthMonth, year: birthYear, timeZone: timeZoneFromAbbrev)
} else {
let timeZoneFromOffset = TimeZone.timeZone(from: timeZone)
print(timeZoneFromOffset.abbreviation())
//Prints "GMT+8"
birthDate = BirthDate(day: birthDay, month: birthMonth, year: birthYear, timeZone: timeZoneFromOffset)
}
My BirthDate class for context:
class BirthDate {
var day: Int
var month: Int
var year: Int
var timeZone: TimeZone
init(day: Int, month: Int, year: Int, timeZone: TimeZone) {
self.day = day
self.month = month
self.year = year
self.timeZone = timeZone
}
}
Time zones are funny things to work with. If anybody sees issue with the TimeZone extension above, please let me know. I think I've accounted for all scenarios, but could be mistaken.

Related

Convert UTC time to PST in SWIFT

I need to convert the UTC time to PST
From backed, I get UTC dates like "2021-06-25T07:00:00Z"
I need to show the dates in Hstack from Provided UTC date to the current date.
I write the following code.
Anyone help to me.
func datesRange(from:Date, to:Date)->[Date]{
if from > to {return [Date]()}
var tmpdate = from
var array:[Date] = []
while tmpdate <= to {
array.append(tmpdate)
tmpdate = Calendar.current.date(byAdding: .day,value: 1, to: tmpdate)!
}
return array
}
extension Date{
func convertTimezone(timezone:String)-> Date{
if let targettimeZone = TimeZone(abbreviation: timezone){
let delta = TimeInterval(targettimeZone.secondsFromGMT(for: self) - TimeZone.current.secondsFromGMT(for: self))
return addingTimeInterval(delta)
}else{
return self
}
}
}
I used as follows
func getrangeDays(){
let startday = "2021-06-25T07:00:00Z"
let dateformater = DateFormatter()
dateformater.locale = Locale(identifier: "en_US_POSIX")
dateformater.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
if let date = dateformater.date(from: startday){
let rangedays = datesRange(from:date.convertTimezone(timezone: "PST") , to: Date().convertTimezone(timezone: "PST"))
print(rangedays)
}
}
Your convertTimezone() function does not make sense. It is trying to convert a Date to a different time zone. A Date object does not have a time zone. It is an instant in time, anywhere on the planet. Time zones only make sense when you want to display a Date, or do time zone specific date calculations. (And in that case you want to create a Calendar object and set its time zone to the desired time zone, then use that Calendar for your date calculations.)
Get rid of that function.
Convert your input date string to a Date as you are doing now (although you might want to use an ISO8601DateFormatter rather than a regular date formatter, since those are specifically intended for handling ISO8601 dates.)
Build your date range using your datesRange() function.
Then use a second DateFormatter to display your dates in PST. (Not convert Dates to PST. That doesn't make sense.)

How to check if date is at least previous day?

Hi everyone I'm trying to get a positive answer when comparing a date earlier than today
To do this I am using a Date extension with this boolean
extension Date {
var isPreviousDate: Bool {
return self.timeIntervalSinceNow.sign == .minus
}
}
Here is my problem .. when I print the date to compare it tells me that today's date is earlier and I don't understand why
Since I'm having problems I tried to create a current date and compare it with today's date the answer is that today's date is earlier than today ... this is weird because it shouldn't tell me it's older
This is how I create the date to compare
var calendar = Calendar(identifier: Calendar.current.identifier)
calendar.timeZone = NSTimeZone(name: "UTC")! as TimeZone
guard let selectedDate = calendar.date(from: DateComponents(year: Date().currentYear, month: Date().currentMonth, day: Date().currentDate)) else { return }
print("isPrevious Date :",selectedDate.isPreviousDate)
print(selectedDate)
am I doing something wrong?
this is what I read in the console when I print the created date to compare
isPrevious Date: true
2021-02-11 00:00:00 +0000
timeIntervalSinceNow is a FloatingPoint value with a very high precision, that represents seconds passed since the Date.
To check if current date is at least on a previous day, you could do something like this:
extension Date {
var isAtLeastPreviousDay: Bool {
return isPast && !isToday
}
private var isPast: Bool {
return self < Date()
}
private var isToday: Bool {
return Calendar.current.isDateInToday(self)
}
}
The issue there is that you are using a custom calendar using UTC timezone. You should use the current calendar with the current timezone. Note that you can use Calendar method startOfDay to get the start of day of a specific date:
extension Date {
var isPreviousDate: Bool {
timeIntervalSinceNow.sign == .minus
}
var startOfDay: Date {
Calendar.current.startOfDay(for: self)
}
}
let now = Date()
let startOfDay = now.startOfDay
print("isPrevious Date:", startOfDay.isPreviousDate) // true
If you would like to check if a date is in yesterday all you need is to use calendar method isDateInYesterday:
extension Date {
var isDateInYesterday: Bool {
Calendar.current.isDateInYesterday(self)
}
}
Date().isDateInYesterday // false
Date(timeIntervalSinceNow: -3600*24).isDateInYesterday // true

Date from string is not coming properly and two dates difference also using Swift 3?

I have a date in string format, example:- "2017-07-31" or can be multiple dates (any) in string format. My requirement is to check this date to current date and if it is greater than 0 and less than 15, then that time I have to do another operation.
So first I am converting that date string to in date format. But it is giving one day ago date. Here is my code:
//Date from string
func dateFromString(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let currentDate = (dateFormatter.date(from: date))//(from: date))
return currentDate!
}
Ex. my date is "2017-08-30" and this function is returning 2017-08-29 18:30:00 +0000 in date format. It means 1 day ago. I am little bit confuse about dates operation. I read so many blogs also.
After that I have to check this date to current date if it is in between 0 < 15 than I will do other operation.
Comparing two dates:
extension Date {
func daysBetweenDate(toDate: Date) -> Int {
let components = Calendar.current.dateComponents([.day], from: self, to: toDate)
return components.day ?? 0
}
}
If my date is today date and comparing to tomorrow date then also it is giving 0 days difference. Why?
If – for example – the current date is 2017-07-31 at 11AM then the
difference to 2017-08-01 (midnight) is 0 days and 13 hours, and that's
why you get "0 days difference" as result.
What you probably want is to compare the difference between the start
of the current day and the other date in days:
extension Date {
func daysBetween(toDate: Date) -> Int {
let cal = Calendar.current
let startOfToday = cal.startOfDay(for: self)
let startOfOtherDay = cal.startOfDay(for: toDate)
return cal.dateComponents([.day], from: startOfToday, to: startOfOtherDay).day!
}
}
Try this method for convert string to date:
func dateFromString(date : String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
dateFormatter.timeZone = TimeZone.init(abbreviation: "UTC")
let currentDate = (dateFormatter.date(from: date))//(from: date))
return currentDate!
}
Try this to compare the time between two dates in seconds :
var seconds = Calendar.current.dateComponents([.second], from: date1!, to: date2!).second ?? 0
seconds = abs(seconds)
let min = seconds/60 // this gives you the number of minutes between two dates
let hours = seconds/3600 // this gives you the number of hours between two dates
let days = seconds/3600*24 // this gives you the number of days between two dates

How to add an Int to a NSDate?

I'm currently getting JSON data for a date in this format:
Date = "2016-07-21T18:32:24.347Z"
I need to be able to add an Int, or float that represents minutes (60 min total)
How can I do this?
So there are like a million different ways that you could do this but that is probably not what you are looking for here. The best way to play with these things would be in a playground that way you can see how the changes to your code effect the end result.
So first you need a function that can take a string as a parameter.
func dateFromJSON(dateString: String?) -> NSDate? {}
This Function will take in our string and return us a NSDate
Then we need a date formatter, There are many different ways you can format a date and you can check out the documentation on them here NSDateFormatter Documentation
so here is an example of a formatter "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" you can see pretty quickly that different parts refer to differnt peices of a date. IE the yyyy is for the year in this case will give us something like 2016. If it were yy we would only get 16.
Initialize the dateFormatter then apply our format.
You should also throw in some checks to make your code safe. So it should look like this.
func dateFromWebJSON(dateString: String?) -> NSDate? {
guard let dateString = dateString else {
return nil
}
let formatter = NSDateFormatter()
let date = ["yyyy-MM-dd'T'HH:mm:ss.SSSZZZ"].flatMap { (dateFormat: String) -> NSDate? in
formatter.dateFormat = dateFormat
return formatter.dateFromString(dateString)
}.first
assert(date != nil, "Failed to convert date")
return date
}
so now we can call
let time = dateFromWebAPIString(date)
print(time) // Jul 21, 2016, 12:32 PM
Now to add time you have to remember that 1 NSTimeInterval is 1 second. So do some basic math
let min = 60
let hr = 60 * min
then we can add as much time as you want
let newTime = time?.dateByAddingTimeInterval(NSTimeInterval(20 * min))
print(newTime) // "Jul 21, 2016, 12:52 PM"
20 min later. Hope this helps 🖖🏽

check value existence by NSDate as key in dictionary

I have a dictionary like this:
var dic = [NSDate: Int]()
it is used in my iOS to-do app to get the number of finished tasks of a particular date. I only care about the year, month and day sections in NSDate and also want to be able to get the number of tasks in a particular date using this dictionary, how can I do that? thanks.
Instead of storing your date as NSDate in your dictionary you can save it as String so that comparison will be easier. Use following code to store it as a string
func dateFromString(date : NSDate) -> String {
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.stringFromDate(date)
}
You can pass NSDate() to above function and it will give you string containing only year, month and date. For retrieving your data from dictionary use following.
func dateFrom(year:Int, month:Int, day:Int) -> String {
let components = NSDateComponents()
components.year = year
components.month = month
components.day = day
let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let date = gregorian!.dateFromComponents(components)
return dateFromString(date!)
}
You can pass year, month and date to above function and it will return corresponding date in string format. So your dictionary operations will look like
dict[dateFromString(NSDate())] = 1 //for insertion or updation
let numOfTasks = dict[dateFrom(2016, month: 1, day: 15)] //to get task for any particular day
EDIT
If you want to proceed with NSDate as key for your dictionary then you'll have to modify above code as follows. dateFrom will return date with year,month and date of your choice, and time will be some constant value. Time will be set to midnight in your current time zone if you don't set it.
func dateFrom(year:Int, month:Int, day:Int) -> NSDate {
let components = NSDateComponents()
components.year = year
components.month = month
components.day = day
let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let date = gregorian!.dateFromComponents(components)
return date!
}
And for getting current date use following so that you store date object with current year, date, month and time to some constant value.
func getCurrentDate()->NSDate {
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day , .Month , .Year], fromDate: date)
return dateFrom(components.year, month: components.month, day: components.day)
}
Usage will be as follows
dict[getCurrentDate()] = i //for insertion or updation
let numOfTasks = dict[dateFrom(2016, month: 1, day: 15)] //to get task for any particular day

Resources