Question

I have a method which extracts the hour and second components form a NSDate by breaking it down into its NSDateComponents. My code is as follows...

unsigned hourAndMinuteFlags = NSHourCalendarUnit | NSMinuteCalendarUnit;
NSCalendar* calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
NSDateComponents* travelDateTimeComponents = [calendar components:hourAndMinuteFlags fromDate:travelDate];
NSString* hours = [NSString stringWithFormat:@"%02i", [travelDateTimeComponents hour]];
NSString* minutes = [NSString stringWithFormat:@"%02i", [travelDateTimeComponents minute]];

My problem is that I'm losing an hour in the conversion. I have a suspicion that it's due to timezones but as far as I can see both the date being passed in and the calendar being used are both GMT.

For example if I pass in the following NSDate object (this is a log of [NSDate description])...

2010-08-02 08:00:00 +0100

I expect that I get 8 for hours and 0 for minutes, but I actually get back 7 for my hours.

My system time is GMT, hence the NSDate above is currently +0100 for British Summer Time.

Was it helpful?

Solution

The thing is, GMT is not equal to British Summer Time. The date 2010-08-02 08:00:00 +0100 is equal to 2010-08-02 07:00:00 GMT so 7 hours is the result you should expect.

OTHER TIPS

Also keep in mind that the NSDate object always shows time according to GMT, whereas the components you're pulling from that date are altered by the timeZoneWithName part.

Example:

NSDate *today = [NSDate date];
NSLog(@"Today's date: %@",today);

unsigned hourAndMinuteFlags = NSHourCalendarUnit | NSMinuteCalendarUnit;
NSCalendar* calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
NSDateComponents* travelDateTimeComponents = [calendar components:hourAndMinuteFlags fromDate:today];
NSString* hours = [NSString stringWithFormat:@"%02i", [travelDateTimeComponents hour]];
NSString* minutes = [NSString stringWithFormat:@"%02i", [travelDateTimeComponents minute]];

NSLog(@"Calendar: %@",calendar);
NSLog(@"Travel Components: %@",travelDateTimeComponents);
NSLog(@"Hours: %@",hours);
NSLog(@"Minutes: %@",minutes);

Console Output:

[Session started at 2011-01-23 12:59:17 -0700.]
2011-01-23 12:59:19.755 testttt[5697:207] Today's date: 2011-01-23 19:59:19 +0000
2011-01-23 12:59:19.756 testttt[5697:207] Calendar: <__NSCFCalendar: 0x4b2ca70>
2011-01-23 12:59:19.757 testttt[5697:207] Travel Components: <NSDateComponents: 0x4b2de20>
2011-01-23 12:59:19.757 testttt[5697:207] Hours: 19
2011-01-23 12:59:19.758 testttt[5697:207] Minutes: 59

Now if we change the Time Zone...

NSDate *today = [NSDate date];
NSLog(@"Today's date: %@",today);

unsigned hourAndMinuteFlags = NSHourCalendarUnit | NSMinuteCalendarUnit;
NSCalendar* calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithName:@"MST"]];
NSDateComponents* travelDateTimeComponents = [calendar components:hourAndMinuteFlags fromDate:today];
NSString* hours = [NSString stringWithFormat:@"%02i", [travelDateTimeComponents hour]];
NSString* minutes = [NSString stringWithFormat:@"%02i", [travelDateTimeComponents minute]];

NSLog(@"Calendar: %@",calendar);
NSLog(@"Travel Components: %@",travelDateTimeComponents);
NSLog(@"Hours: %@",hours);
NSLog(@"Minutes: %@",minutes);

Output:

2011-01-23 13:05:29.896 testttt[5723:207] Today's date: 2011-01-23 20:05:29 +0000
2011-01-23 13:05:29.897 testttt[5723:207] Calendar: <__NSCFCalendar: 0x4e10020>
2011-01-23 13:05:29.897 testttt[5723:207] Travel Components: <NSDateComponents: 0x4e0f6d0>
2011-01-23 13:05:29.898 testttt[5723:207] Hours: 13
2011-01-23 13:05:29.898 testttt[5723:207] Minutes: 05

Notice how the local hours and minutes change, but the "Today's date:" part still reflects GMT. A bit misleading to programmers, if you ask me.

I agree with aqua. It is very misleading and has led me to days hitting my head against the screen. It works like this: [NSDate date] always returns current date. So in the example above it will be 2010-08-02 08:00:00. When we convert into NSDateComponents, the actual time is lost and converted into a set of integers (hour, year etc.) which no longer hold the Time Zone.

When converting back to a NSDate object it will take the values of year, month, day etc. and give it to you relative to GMT, hence the hour lost. It's just the way it was decided in the framework. What I do is the following:

+(NSDateComponents *)getComponentFromDate:(NSDate *)date {

    NSCalendar * gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

    unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSWeekdayCalendarUnit | NSDayCalendarUnit;
    NSDateComponents* components = [gregorian components:unitFlags fromDate:date];

    NSTimeZone* timeZone = [NSTimeZone localTimeZone];
    if(timeZone.isDaylightSavingTime) components.hour = ((int)timeZone.daylightSavingTimeOffset/3600);

    [gregorian release];

    return components;
}

That will give me a 'corrected' version of the components rounded to the current day which when passed to the calendar to retrieve a date will always be set to 00:00:00 on the day of the date passed as argument.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top