I have a function which produces the current date depending on the users location. this provides the correct date and time even after daylight saving changes here in the UK.
However when i try to create a string with the date it seems to add an extra hour on for me...
E.g date stored in Coredata is 1/3/2013 12:00 however when converted to a string it appears as 1/3/2013 13:00. What ever i change with locale and timezones don't seem to make a difference.
Any ideas how i can fix this? It might be worth noting that the "Local Date" function i have never creates the date time with the correct timezone.
Code:
+(NSDate*)localDate{
NSDate* date = [NSDate date];
NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
NSTimeZone* destinationTimeZone = [NSTimeZone localTimeZone];
NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:date];
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:date];
NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;
return [NSDate dateWithTimeInterval:interval sinceDate:date];
}
Formatted Code:
-(NSString*)dateWithTime{
NSDateFormatter*formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:#"dd"];
NSInteger day = [[formatter stringFromDate:self]integerValue];
NSInteger fullday = day;
NSString* strDay = #"";
if(day>29){
day = day - 30;
}
else if(day>19){
day = day - 20;
}
else if(day>9){
day = day - 10;
}
if(fullday ==11){
strDay = #"th";
}
else if(fullday ==12){
strDay = #"th";
}
else if(fullday ==13){
strDay = #"th";
}
else if(day == 1){
strDay = #"st";
}
else if(day ==2){
strDay = #"nd";
}
else if(day ==3){
strDay = #"rd";
}
else{
strDay = #"th";
}
[formatter setDateFormat:#"MMMM"];
NSString* month = [formatter stringFromDate:self];
[formatter setDateFormat:#"HH:mm"];
NSString* time = [formatter stringFromDate:self];
return [NSString stringWithFormat:#"%u%# %# %# - %#",fullday,strDay,month,[self yearString],time];
}
EDIT: Even NSDateComponants returns the incorrect time..
NSCalendar *gregorian = [NSCalendar currentCalendar];
NSDateComponents *dateComponents = [gregorian components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:self];
Please note that printing NSDate's in the console will always print the date in UTC.
Simply store the [NSDate date] in your database. When the user never changed it's time zone, reading the date from your database and format it will return the correct result.
NSDate objects in Core Data are stored as UNIX timestamp with time zone UTC. When reading and formatting the date object the current time zone of the user is applied.
When storing dates with Core Data and having dependences to different time zones the only way to format dates correctly is to store the appropriate time zone (name of time zone as string) as well. When reading and formatting the date object you have to apply the stored time zone to the dateFormatter. Then you'll get the correct results.
NSDate doesn't know about timezones it's only when converted that the timezone counts, so your localDate method is probably fudging things in a way you don't intend. What you probably need to do is set the timezone for the formatter or calendar to the local zone, so:
[formatter setTimeZone:localTimeZone];
or
[gregorian setTimeZone:localTimeZone];
and leave the value returned by [NSDate date] alone.
Set the locale of dateformatter;
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
Depending on your needs after the initial date/time is set, you may want to be notified if the time zone (including DST) has changed. If so, applicationSignificantTimeChange: would be your friend.
Related
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.
This question already has an answer here:
sethours in NSDateComponents
(1 answer)
Closed 9 years ago.
I am trying to create a date with the time set to 12:00:01 am. Seconds and minutes set to their values correctly but the hour value always goes to whatever i set the value, + 4. Why 4? What is so special about that value? The minute and second values set to what I want correctly but it appears the hours value simply adds rather than replaces.
here is the code,
NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:now];
[components setHour:0];
[components setMinute:0];
[components setSecond:1];
NSDate *compareTime = [calendar dateFromComponents:components];
NSLog(#"compareTime: %#", compareTime);
NSLog(#"currentTime: %#", now);
output is :
compareTime: 2013-05-17 04:00:01 +0000
currentTime: 2013-05-17 15:00:37 +0000
EST is 4 hours ahead of GMT, hence your offset. Here's some code we use to create dates plus and minus the current time in the local time zone.
- (NSDate *) getDateWithHoursOffset : (int) aHourInt
{
CFGregorianDate gregorianStartDate, gregorianEndDate;
CFGregorianUnits startUnits = { 0,0,0, aHourInt,0,0 }; //2 hours before
CFGregorianUnits endUnits = { 0,0,0, 8,0,0 }; //5 hours ahead
CFTimeZoneRef timeZone = CFTimeZoneCopySystem();
gregorianStartDate = CFAbsoluteTimeGetGregorianDate(CFAbsoluteTimeAddGregorianUnits(CFAbsoluteTimeGetCurrent(), timeZone, startUnits),timeZone);
gregorianStartDate.minute = 0;
gregorianStartDate.second = 0;
gregorianEndDate = CFAbsoluteTimeGetGregorianDate(CFAbsoluteTimeAddGregorianUnits(CFAbsoluteTimeGetCurrent(), timeZone, endUnits),timeZone);
gregorianEndDate.minute = 0;
gregorianEndDate.second = 0;
NSDate* startDate = [NSDate dateWithTimeIntervalSinceReferenceDate:CFGregorianDateGetAbsoluteTime(gregorianStartDate, timeZone)];
CFRelease(timeZone);
return startDate;
}
NSDates exist independently of timezones. If you need the date to display 12:00:01 in the application, you should use a NSDateFormatter.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterFullStyle];
NSLog(#"formattedTime: %#", [dateFormatter stringFromDate:compareTime]);
This returns:
formattedTime: 12:00:01 AM Central Daylight Time
The problem is that your dates are correct but they're being logged in UTC relative to your device's time zone and that seems confusing. (It is, at first)
Your compare time is correct. It is set to midnight your time and outputted as UTC and if you're EST midnight would be 4am UTC:
compareTime: 2013-05-17 04:00:01 +0000
Current time is also correct, and again it is UTC time relative to your device's time zone:
currentTime: 2013-05-17 15:00:37 +0000
Your times are correct, its the output that is deceiving you.
This code (shamefully stolen from a thread listed below) should output compareTime's UTC date as 00:00:01 +0000. Though for date calculations UTC should be fine.
NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];
NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];
NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;
NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:compareTime];
Here are a few S.O. threads to help further explain:
Does [NSDate date] return the local date and time?
Why isn't my time zone being saved into my NSDate?
NSDate is not returning my local Time zone /default time zone of device
I am trying to get current date with adding device time zone but is show 1 hr late that original date. I thing , I am getting problem of DaylightSavingTime.
How to disable isDaylightSavingTime = FALSE .
here is the code, I have used..
NSDate *date = [NSDate Date];
NSTimeZone *currentTimeZon = [NSTimeZone localTimeZone];
if ([currentTimeZon isDaylightSavingTime])
{
NSLog(#"East coast is NOT on DT");
}
else
{
NSLog(#"East coast is on DT");
}
NSTimeInterval timeZoneOffset = [currentTimeZon secondsFromGMTForDate:currentDate];
NSTimeInterval gmtTimeInterval = [date timeIntervalSinceReferenceDate] - timeZoneOffset;
NSDate *gmtDate = [NSDate dateWithTimeIntervalSinceReferenceDate:gmtTimeInterval];
NSLog(#"### Announcement gmtDate = %#",gmtDate);
I am getting time with 1 Hour difference, date is perfact.
Use NSCalendar, it understands time zones, daylight savings, etc. As #albertamg says, NSDate is just the time since a reference at UTC (GMT), it has no other concept.
I've searched and haven't found an exact question like mine, so here goes nothing:
I have a NSString containing a key that I pull from an XML feed. The key is a time in 24-hour format (e.g. 13:30 or 15:00.) I'd like to convert the NSString to an NSDate and have it converted to the appropriate timezone based on the device's set timezone. The key is Unicode HH:mm (24:00), so I'm curious why this does not work as it should.
I've already gotten a basic outline that should work, but alas does not. The 2nd NSLog (Got NS Date) returns null and the final log returns a strange number (1969-12--2147483629 -596:-31:-23 +0000 to be precise.) What am I doing wrong here?
Thanks in advance,
NSString *dateString = [dict objectForKey:#"24hrdate"];
NSLog(#"NSString Date: %#", dateString);
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"HH:mm"];
NSDate *sourceDate = [formatter dateFromString:dateString];
NSLog(#"Got NS Date: %#", sourceDate);
NSTimeZone *sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
NSTimeZone *destinationTimeZone = [NSTimeZone systemTimeZone];
NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];
NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;
NSDate* destinationDate = [[[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate] autorelease];
NSLog(#"Final Date: %#", destinationDate);
First of all understand that the date component will be 01-01-1970 because it isn't provided. I am assuming that you want the time to be 04:00 GMT if the input string is #"04:00". That you can achieve by setting the time zone of the formatter.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
[formatter setDateFormat:#"HH:mm"];
NSDate *sourceDate = [formatter dateFromString:dateString];
NSDate is used to represent a date and time. While you can represent just a date by sticking with midnight, you can't really represent just a time of day with it. You can sort of fake this on the Mac (it defaults to some reasonable day), but on iOS you'll get wildly inaccurate times instead. (At least, you do on certain versions. This may have been fixed.)
There's two approaches here:
You can build a NSDateComponents from your time of day and using dateByAddingComponents to add that to midnight on the date you want the time to appear on. This will fail to return the time you expect on a day where daylight savings begins or ends.
You can build a date/time string using the date you want (NSDate) and the time (likely, as a NSString).
- (NSDate *)timeInHours: (NSInteger)hours
minutes: (NSInteger)minutes
seconds: (NSInteger)seconds
onDate: (NSDate *)inDate;
{
id timeStr = [[NSMutableString alloc] initWithFormat: #"%02d:%02d:%02d", hours, minutes, seconds];
id dateStr = [dateWithoutTimeFormatter stringFromDate: inDate];
id dateTimeStr = [[NSString alloc] initWithFormat: #"%# %#", dateStr, timeStr];
[timeStr release];
id dateTime = [dateWithTimeFormatter dateFromString: dateTimeStr];
[dateTimeStr release];
return dateTime;
}
If you really want just the time of day, just keep it around as a string.
Just wanted to post back that I did manage to achieve what I initially set out to do without any issues. Basically, I had to convert the string to NSDate, run that NSDate through a NSDateFormatter (set to the time's original timezone--NSDateFormatter's setTimeZone was helpful), pull an NSDate out of that, and then run that through another NSDateFormatter for the device's timezone. I then converted the resulting NSDate back to NSString, and stuck it on a UILabel.
This solution seems to have worked quite well, as I've set my devices to various timezones, and the timezone change is still correct.
EDIT: this was important to included, too:
NSString *date…….
date = [date stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
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.