Swift NSDateFormatter timezone not working [duplicate] - ios

I have a NSString (ex. "2011-04-12 19:23:39"), and what I did to format it to a NSDate was the following:
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *date = [inputFormatter dateFromString:newDateString];
but what it outputs when I nslog the date is this:
2011-04-12 23:23:39 +0000
which is about 4 hours off. Is there something I missed? Possibly a time zone problem?

The answer in short, is the Date is being returned GMT unless specified otherwise. You can set your timezone to get the correct date. If you plan on using the date in the app to set anything ( like localNotification time or Event ) you will need to do something special with the date because if you set the date in the iPhone it will be set as GMT time and will be off by a few hours. ( in your case 4 hours ). I do this exact thing I just described in one of my apps.
I made a mess of trying to get this to work correctly without having the hours be off. It was a huge PITA to figure out but its working now. I have copied, pasted, and edited my code to share. Again, its messy but it works! The pickerChanged is getting its info from a UIDatePicker
Using the code below. To answer your question, you can stop at "destinationDate". That will return to you the corrected time for your current time zone. I just provided the extra incase you were trying to use the date in the Phone somewhere.
NOTE: for a quick example i put the Event reminder in the same function as the datepicker, you will NOT want to do that otherwise you will have alot of reminders set everytime the wheel scrolls in the datepicker.
The code is below.
- (void)pickerChanged:(id)sender
{
NSLog(#"value: %#",[sender date]);
NSDate* date= [sender date];
NSDateFormatter *formatter=[[[NSDateFormatter alloc]init]autorelease];
[formatter setDateFormat:#"MM/dd/yyyy hh:mm:ss a"];
[formatter setTimeZone:[NSTimeZone systemTimeZone]];
[formatter setTimeStyle:NSDateFormatterLongStyle];
NSString *dateSelected =[formatter stringFromDate:date];
NSString *timeZone = [dateSelected substringFromIndex:12];
NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];
//here we have to get the time difference between GMT and the current users Date (its in seconds)
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:date];
//need to reverse offset so its correct when we put it in the calendar
correctedTimeForCalendarEvent = destinationGMTOffset + (2*(-1*destinationGMTOffset));
//date to enter into calendar (we will use the correctedTimeForCalendarEvent to correct the time otherwise it will be off by a few hours )
NSDate * destinationDate = [[[NSDate alloc] initWithTimeInterval:destinationGMTOffset sinceDate:date] autorelease];
NSDate * dateForReminder = destinationDate;
// return destinationDate;
NSLog(#"value: %# - %#",destinationDate,dateForReminder);
//DO NOT put this code in this same function this is for a quick example only on StackOverflow
//otherwise you will have reminders set everytime the users scrolled to a different time
//set event reminder
//make sure to import EventKit framework
EKEventStore *eventDB = [[[EKEventStore alloc] init]autorelease];
EKEvent *myEvent = [EKEvent eventWithEventStore:eventDB];
NSString * eventTitle = [NSString stringWithFormat:#"%# - %#",app.dealerBusinessName,serviceOrComments.text];
myEvent.title = eventTitle;
//double check date one more time
NSLog(#"value: %#",destinationDate);
//set event time frame (1 hour) the "initWithTimeInterval" is where we account for the users timezone by adding the correctedTime from GMT to the calendar time ( so its not off by hours when entering into calendar)
myEvent.startDate = [[[NSDate alloc] initWithTimeInterval:correctedTimeForCalendarEvent sinceDate:destinationDate ]autorelease];
myEvent.endDate = [[[NSDate alloc] initWithTimeInterval:3600 sinceDate:myEvent.startDate]autorelease];
myEvent.allDay = NO;
//set event reminders 1 day and 1 hour before
myAlarmsArray = [[[NSMutableArray alloc] init] autorelease];
EKAlarm *alarm1 = [EKAlarm alarmWithRelativeOffset:-3600]; // 1 Hour
EKAlarm *alarm2 = [EKAlarm alarmWithRelativeOffset:-86400]; // 1 Day
[myAlarmsArray addObject:alarm1];
[myAlarmsArray addObject:alarm2];
myEvent.alarms = myAlarmsArray;
[myEvent setCalendar:[eventDB defaultCalendarForNewEvents]];
NSError *err;
[eventDB saveEvent:myEvent span:EKSpanThisEvent error:&err];
if (err == noErr) {
//no error, but do not show alert because we do that below.
}
}

NSDateFormatter use the current device timezone when it created the NSDate object. NSDate stores the date/time in GMT. Therefore by default NSLog will output the date/time in GMT+0. So, there's nothing wrong with your code. Now if you want to output the NSDate to your current timezone, your will have to use a NSDateFormatter object.

Your data and date formatter omit the TimeZone specifier. So something like this:
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ssZ"];
Would work - the Z is the timezone specifier and will parse both numeric offsets and timezone codes. Although in your case as your input date has no TimeZone information it won't work.
Your correct Time string should be like "2011-04-12 19:23:39 -0400" or "2011-04-12 19:23:39 EST "
Depending on where you get your date from, you should fix that to produce a fully qualified date if you can't do that, you will have to agree timezone offsets with the server or simply 'hard code' a timezone offset and add that number of seconds to your NSDate.

The date is being logged as a UTC date can be seen by the +0000 at the end. The date format you are using to parse the string assumes your local time zone which is presumably 4 hours behind UTC with daylight savings and the standard -5 hours.

Use -[NSDateFormatter setTimeZone:] to provide the date formatter with timezone information. You can use the local time zone, or if you have a fixed time zone associated with the date information, I recommend creating the timezone with the name (such as "America/East") rather than the abbreviation (such as "EST" or "EDT"), since the name does not force daylight savings into effect, but uses the correct daylight savings offset for that date in that timezone.

Related

Unexpected behavior when iOS device switched from 12 hour to 24 hour time setting

I have an app that is formatted with military time (HH:mm). When the device is in 12 hour time setting the time field in the app is correct. For example, 5:00 PM shows up as 17:00. When I change the device setting to 24 hour time, the time field shows up as null. I decided to change the formatting in the app to regular time (hh:mm a). When the device is set to 12 hour mode, the time field shows up correctly as, for example, 5:00 PM. When I switch the device to 24 hour mode, the time field again shows as null. So it seems that no matter if the time is formatted as 12 hour time (hh:mm a) or military time (HH:mm), when the device is in 24 hour mode both show as null. The JSON data source is "functionStartTime":"05:00 PM" which is stored in Core Data with an Attribute name startTime and Type Date. The code is below.
NSDate * sTime = [object valueForKey:#"startTime"];
NSDateFormatter *sdf = [[NSDateFormatter alloc]init];
NSLocale *slocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[sdf setLocale:slocale];
[sdf setDateFormat:#"HH:mm"];
[sdf setTimeZone:[NSTimeZone localTimeZone]];
NSString *sTimeStr = [sdf stringFromDate:sTime];
NSLog(#"Start %#", sTimeStr);
try replacing your code snippet with the following -
NSDate * sTime = [object valueForKey:#"startTime"];
// get the locale
[NSLocale autoupdatingCurrentLocale];
NSLocale *theLocale = [NSLocale currentLocale];
// set the formatter like this
NSDateFormatter *sdf = [[NSDateFormatter alloc] init];
[sdf setLocale:theLocale];
[sdf setDateStyle:NSDateFormatterNoStyle];
[sdf setTimeStyle:NSDateFormatterShortStyle];
NSString *sTimeStr = [sdf stringFromDate:sTime];
NSLog(#"Start %#", sTimeStr);
You'll also need some mechanism to check this whenever the app returns from the background, in case the user changes the time format 12/24hr in settings.

What is the correct way of showing a timestamp in device timezone?

I've this timestamp : 1439353372
This was generated when one of our user (from Canada) sent me a message (in India).
Inside our app – He is seeing following time for that message : 2015-08-11 / 22:22pm and I'm seeing this 2015-08-12 / 09:52am. He's seeing this in his iPhone6 and I'm on iPhone6+ simulator.
I think this time is wrong – I'm having so much confusion regarding this timestamp/timezone and dates conversion.
Below is our code to convert timestamp to date and showing it in above format.
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"yyyy-MM-dd"];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:1439353372];
NSString *strDate = [df stringFromDate:date];
[df setDateFormat:#"HH:mm a"];
NSString *strTime = [df stringFromDate:date];
NSLog(#"%#",[NSString stringWithFormat:#"%# / %#", strDate, strTime]);
I'm not sure this is the correct way of showing date for particular timestamp in user's region?
What should I add in above code such that it'll always show us proper time as per our device time zone?
When you create the timestamp, add the local time zone:-
NSTimeZone *localTimeZone = [NSTimeZone systemTimeZone];
[df setTimezone: localTimeZone];
While retrieving the timestamp, do the same approach so that you get the proper date and time according to your time zone.
EDITED:-
This is how you add time zone to your datestamp, just found it from this Source as I don't have the time right now to write the code
NSDate* referenceDate = [NSDate dateWithTimeIntervalSince1970: 0];
NSTimeZone* timeZone = [NSTimeZone systemTimeZone];
int offset = [timeZone secondsFromGMTForDate: referenceDate];
int current = unix_timestamp - offset;
Ok, I've found that, the above date and time is correct. In GMT, if you convert above timestamp to NSDate you'll get following result: 2015-08-12 / 04:22pm. So for Canada timezone is GMT-6 (6 hours back from 2015-08-12 / 04:22pm time) and India is GMT+5.30 (5.30 hours ahead from 2015-08-12 / 04:22pm time). So the result in the question I asked is seems to be pure :)

SWIFT: Timezone issue when converting string to NSDate object [duplicate]

I have a NSString (ex. "2011-04-12 19:23:39"), and what I did to format it to a NSDate was the following:
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *date = [inputFormatter dateFromString:newDateString];
but what it outputs when I nslog the date is this:
2011-04-12 23:23:39 +0000
which is about 4 hours off. Is there something I missed? Possibly a time zone problem?
The answer in short, is the Date is being returned GMT unless specified otherwise. You can set your timezone to get the correct date. If you plan on using the date in the app to set anything ( like localNotification time or Event ) you will need to do something special with the date because if you set the date in the iPhone it will be set as GMT time and will be off by a few hours. ( in your case 4 hours ). I do this exact thing I just described in one of my apps.
I made a mess of trying to get this to work correctly without having the hours be off. It was a huge PITA to figure out but its working now. I have copied, pasted, and edited my code to share. Again, its messy but it works! The pickerChanged is getting its info from a UIDatePicker
Using the code below. To answer your question, you can stop at "destinationDate". That will return to you the corrected time for your current time zone. I just provided the extra incase you were trying to use the date in the Phone somewhere.
NOTE: for a quick example i put the Event reminder in the same function as the datepicker, you will NOT want to do that otherwise you will have alot of reminders set everytime the wheel scrolls in the datepicker.
The code is below.
- (void)pickerChanged:(id)sender
{
NSLog(#"value: %#",[sender date]);
NSDate* date= [sender date];
NSDateFormatter *formatter=[[[NSDateFormatter alloc]init]autorelease];
[formatter setDateFormat:#"MM/dd/yyyy hh:mm:ss a"];
[formatter setTimeZone:[NSTimeZone systemTimeZone]];
[formatter setTimeStyle:NSDateFormatterLongStyle];
NSString *dateSelected =[formatter stringFromDate:date];
NSString *timeZone = [dateSelected substringFromIndex:12];
NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];
//here we have to get the time difference between GMT and the current users Date (its in seconds)
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:date];
//need to reverse offset so its correct when we put it in the calendar
correctedTimeForCalendarEvent = destinationGMTOffset + (2*(-1*destinationGMTOffset));
//date to enter into calendar (we will use the correctedTimeForCalendarEvent to correct the time otherwise it will be off by a few hours )
NSDate * destinationDate = [[[NSDate alloc] initWithTimeInterval:destinationGMTOffset sinceDate:date] autorelease];
NSDate * dateForReminder = destinationDate;
// return destinationDate;
NSLog(#"value: %# - %#",destinationDate,dateForReminder);
//DO NOT put this code in this same function this is for a quick example only on StackOverflow
//otherwise you will have reminders set everytime the users scrolled to a different time
//set event reminder
//make sure to import EventKit framework
EKEventStore *eventDB = [[[EKEventStore alloc] init]autorelease];
EKEvent *myEvent = [EKEvent eventWithEventStore:eventDB];
NSString * eventTitle = [NSString stringWithFormat:#"%# - %#",app.dealerBusinessName,serviceOrComments.text];
myEvent.title = eventTitle;
//double check date one more time
NSLog(#"value: %#",destinationDate);
//set event time frame (1 hour) the "initWithTimeInterval" is where we account for the users timezone by adding the correctedTime from GMT to the calendar time ( so its not off by hours when entering into calendar)
myEvent.startDate = [[[NSDate alloc] initWithTimeInterval:correctedTimeForCalendarEvent sinceDate:destinationDate ]autorelease];
myEvent.endDate = [[[NSDate alloc] initWithTimeInterval:3600 sinceDate:myEvent.startDate]autorelease];
myEvent.allDay = NO;
//set event reminders 1 day and 1 hour before
myAlarmsArray = [[[NSMutableArray alloc] init] autorelease];
EKAlarm *alarm1 = [EKAlarm alarmWithRelativeOffset:-3600]; // 1 Hour
EKAlarm *alarm2 = [EKAlarm alarmWithRelativeOffset:-86400]; // 1 Day
[myAlarmsArray addObject:alarm1];
[myAlarmsArray addObject:alarm2];
myEvent.alarms = myAlarmsArray;
[myEvent setCalendar:[eventDB defaultCalendarForNewEvents]];
NSError *err;
[eventDB saveEvent:myEvent span:EKSpanThisEvent error:&err];
if (err == noErr) {
//no error, but do not show alert because we do that below.
}
}
NSDateFormatter use the current device timezone when it created the NSDate object. NSDate stores the date/time in GMT. Therefore by default NSLog will output the date/time in GMT+0. So, there's nothing wrong with your code. Now if you want to output the NSDate to your current timezone, your will have to use a NSDateFormatter object.
Your data and date formatter omit the TimeZone specifier. So something like this:
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ssZ"];
Would work - the Z is the timezone specifier and will parse both numeric offsets and timezone codes. Although in your case as your input date has no TimeZone information it won't work.
Your correct Time string should be like "2011-04-12 19:23:39 -0400" or "2011-04-12 19:23:39 EST "
Depending on where you get your date from, you should fix that to produce a fully qualified date if you can't do that, you will have to agree timezone offsets with the server or simply 'hard code' a timezone offset and add that number of seconds to your NSDate.
The date is being logged as a UTC date can be seen by the +0000 at the end. The date format you are using to parse the string assumes your local time zone which is presumably 4 hours behind UTC with daylight savings and the standard -5 hours.
Use -[NSDateFormatter setTimeZone:] to provide the date formatter with timezone information. You can use the local time zone, or if you have a fixed time zone associated with the date information, I recommend creating the timezone with the name (such as "America/East") rather than the abbreviation (such as "EST" or "EDT"), since the name does not force daylight savings into effect, but uses the correct daylight savings offset for that date in that timezone.

iOS 7: Convert NSDate to string with custom timezone and get back new NSDate

I wish to modify current system time (set a custom NSTimeZone) and get back a new NSDate object.
The code I've made
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss Z"];
NSLog(#"System time: %#", [dateFormatter stringFromDate:[NSDate date]]);
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"Asia/Aqtobe"]];
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
NSLog(#"Aqtobe time: %#", dateString);
The output
System time: 2014-02-05 10:00:46 +0000
Aqtobe time: 2014-02-05 15:00:46 +0500
But if I try to get new NSDate object from Aqtobe time:
NSLog(#"New NSDate: %#", [dateFormatter dateFromString:dateString]);
I get
New NSDate: 2014-02-05 10:02:40 +0000
Where I was wrong? Thanks in advance
NSDate always returns date in GMT +0:00.
So, it (NSDate object) always have correct converted value but in GMT +0:00.
So for using it as text you will always have to use same date formatter with same zone.
If you want to use date as string from date in other places (out of dateformatter object scope), it is better to make special method for conversion.
It is explained clearly below with example:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss Z";
NSLog(#"System time: %#", [dateFormatter stringFromDate:[NSDate date]]);
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"Asia/Aqtobe"]];
NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
NSLog(#"Aqtobe time: %#", dateString);
// date will always contain value in GMT +0:00
NSDate *date = [dateFormatter dateFromString:dateString];
NSLog(#"New NSDate (NSDate): %#", [dateFormatter stringFromDate:date]);
// converts date into string
NSLog(#"New NSDate (NSString): %#", [dateFormatter stringFromDate:date]);
For detailed explaination you can refer this question: Does [NSDate date] return the local date and time?
NSDate has many methods for comparing date such as isEqualToDate: (for a more detailed of the methods and usage you can see this SO question).
This method, that I took from here, will let you determine if date is between firstDate and lastDate.
- (BOOL)isDate:(NSDate *)date inRangeFirstDate:(NSDate *)firstDate lastDate:(NSDate *)lastDate {
return [date compare:firstDate] == NSOrderedDescending &&
[date compare:lastDate] == NSOrderedAscending;
}
I hope this is what you meant. If not please explain exactly what you're trying to do.
You can use NSDateComponents and NSCalendar to create a new date. From the Date and Time Programming Guide
Creating Dates with Time Zones
Time zones play an important part in determining when dates take
place. Consider a simple calendar application that keeps track of
appointments. For example, say you live in Chicago and you have a
dentist appointment coming up at 10:00 AM on Tuesday. You will be in
New York for Sunday and Monday, however. When you created that
appointment it was done with the mindset of an absolute time. That
time is 10:00 AM Central Time; when you go to New York, the time
should be presented as 11:00 AM because you are in a different time
zone, but it is the same absolute time. On the other hand, if you
create an appointment to wake up and exercise every morning at 7:00
AM, you do not want your alarm to go off at 1:00 PM simply because you
are on a business trip to Dublin—or at 5:00 AM because you are in Los
Angeles.
NSDate objects store dates in absolute time. For example, the date
object created in Listing 16 represents 4:00 PM CDT, 5:00 EDT, and so
on.
Listing 16 Creating a date from components using a specific time zone
NSCalendar *gregorian=[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"CDT"]];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init]; [timeZoneComps setHour:16];
//specify whatever day, month, and year is appropriate
NSDate *date=[gregorian dateFromComponents:timeZoneComps];
If you need to create a date that is independent of timezone, you can store the
date as an NSDateComponents object—as long as you store some reference
to the corresponding calendar.
In iOS, NSDateComponents objects can contain a calendar, a timezone,
and a date object. You can therefore store the calendar along with the
components. If you use the date method of the NSDateComponents class
to access the date, make sure that the associated timezone is
up-to-date.
Anyway, keep in mind that a date is a unique point in time. What you display to the user is different based on their locale and time zone.

UIDatePicker returns wrong date (-1 day to the real date)

I have a UIDatePicker mm/dd/yy. It works fine, but there is one problem: I set minimum and maximum date to it, and when user tries to choose the forbidden day, month or year, the [datePicker date] property begins working wrong. It returns you the current day - 1 or current month - 1 or current year - 1. I added some pictures, so you can see the situation.
This is correct
This is wrong (After choosing the forbidden date)
Does somebody know, how can I fix this ? Thanks !
UPD:
Code
[self.myDatePicker setMinimumDate:[NSDate date]];
[self.myDatePicker setMaximumDate:[[NSDate date] addTimeInterval:2 * 365.25 * 24 * 60 * 60]]; // to get upto 5 years
NSDate * now = [[NSDate alloc] init];
[self.myDatePicker setDate: now animated: YES];
self.myDatePicker.timeZone = [NSTimeZone localTimeZone];
self.myDatePicker.calendar = [NSCalendar currentCalendar];
Just add one line of code for setting your timezone.
self.datePicker.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
0 is for GMT 00 . Add according to your time zone.
My solution was to set the returned date to 12:00 AM as NSDates work in UTC
NSDate * adjustedDate = [[NSCalendar currentCalendar] dateBySettingHour:12 minute:0 second:0 ofDate:sender.date options:0];
Also for date calculations you shoud use NSCalender methods and not addTimeInterval
It is deffinately something with the timezones and/or Daylight Saving Times. But it must be very subtle, as the code looks fine (beside the interval). Now to my question about if you are in russia:
This year the Kremlin did several back and forth swings on keeping daylight saving times forever. Actually I am not sure, what they decided at last. But maybe it isnt reflected correctly in Cocoa. The the video WWDC 2011 Video "Session 117 - Performing Calendar Calculations" , the presenter even mentions that things like that can happen.
Please try to work with dates with manually set times to noon, as this would keep you out of such mess.
The world just saw a similar misbehavior in iOS 6: the DND-Always-Active bug. I bet this was for a wrong date format (YYYY instead of yyyy)
Also try to set the timezone property on the picker at the very first thing and assign a manually instantiated Gregorian calendar to it.
Check if you use the wrong formatting symbols with big letters: "YYYY".
Replace them with "yyyy".
I ran into the same trouble and this is what i derived:
Don't use [date description] to check NSDate if you want correct representation for your system. Use NSDateFormatter, because it shows date based on your system preferences (in simulator it will be simulators preferences).
For example:
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateStyle:NSDateFormatterShortStyle];
[df setTimeStyle:NSDateFormatterNoStyle];
NSLog(#"date for locale is %#", [df stringFromDate:date]);
Try this,
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.string(from: yourPicker.date)
Might be due to TimeZone...
Set your time zone.
For me this problem is resolved by adding this line
datePicker.date.addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT()))
This line adds up the seconds in dateTime of UIDatePicker and dateTime will be equal to current dateTime
Boolean futureevent;
- (void)viewDidLoad
{
futureevent = false;
}
int intervall = (int) [currentdate timeIntervalSinceDate: datePicker.date] / 60;
if (intervall < 1200 && intervall > 0)
{
futureevent = true;
NSDate *newDate1 = [datePicker.date dateByAddingTimeInterval:60*60*24*1];
birthdate = [newDate1.description substringToIndex:spaceRange.location];
}
else
{
if (futureevent)
{
NSDate *newDate1 = [datePicker.date dateByAddingTimeInterval:60*60*24*1];
birthdate = [newDate1.description substringToIndex:spaceRange.location];
}
else
{
birthdate = [datePicker.date.description substringToIndex:spaceRange.location];
}
}
It does not return wrong date. What actually happens is, when you select a so called forbidden date, the date picker gets reset to maximum or minimum allowed date with first moment of the day i.e 12:00AM.
So if you are at a place where time zone is for example, 2 hours ahead of GMT, the date returned by date picker will be yesterday's 10:00PM GMT. So here, you might think it is returning yesterday's date but if you convert it to your time zone, you will get today's date only but time component will be 12:00AM.

Resources