Argus9
Argus9

Reputation: 1945

NSDate value is always returned in my time zone

TL/DR: I need an NSDate representing 1970-01-01 00:00:00 +0000 specifically. Using [NSDate dateWithTimeIntervalSince1970:0] returns the date adjusted for my time zone. I can't figure out how to fix this.

I am attempting to write an Objective-C iOS test that revolves around NSDates. The trouble is, any NSDate object I try to create, whether it's straight, or using NSDateFormatter or [NSDateComponents dateFromComponents], will always seem to be adjusted for my Mac's current time zone. That is, tests will use the timezone-adjusted value of the NSDate, and it will also appear that way in the Xcode debugger. However, if I explicitly set the time zone to GMT in NSDateComponents or NSDateFormatter, when I check the value in the console using po, it will return the date/time in GMT like I set.

For instance, [NSDate dateWithTimeIntervalSince1970:0] returns an NSDate with the date 1969-12-31 19:00:00 -0500. [NSDate date] returns the current date and time, but per my time zone: 2015-03-03 05:49:32 -0500. I can't figure out for the life of me why I can't simply get 1970-01-01 00:00:00 +0000 as an NSDate.

Is there something I'm missing? Can anyone help me out with this?

Thank you!

EDIT: Here's my attempt at using NSDateComponents:

NSCalendar *calendar = [[NSCalendar alloc]
                            initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *components = [[NSDateComponents alloc] init];
    [components setYear:1970];
    [components setMonth:1];
    [components setDay:1];
    [components setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    NSDate *componentDate = [calendar dateFromComponents:components];

componentDate displays properly in the console (when I type po componentDate), but displays in the debug window as being timezone-adjusted. Also, this timezone-adjusted value is what's being used in my tests, so it has a definite impact on my ability to write reliable tests.

Upvotes: 0

Views: 779

Answers (1)

rob mayoff
rob mayoff

Reputation: 385580

TLDR: [NSDate dateWithTimeIntervalSince1970:0] represents the date you're asking for. It is the conversion of that NSDate to a string that involves your time zone.


As humans, we have many ways to represent an instant in time. For example, “January 1, 2001 00:00:00 GMT” represents a particular instant in time. Another name for the same instant is “December 31, 2000 18:00:00 America/Chicago”. A third name for the same instant is “January 1, 2001 03:00:00 Europe/Moscow”. All these names (strings), and many more, represent the same instant in time. As humans, we recognize that all of these labels represent the same instant in time.

An NSDate represents an instant in time in a completely different way: an NSDate stores an offset, in seconds, relative to a one predetermined reference instant. An NSDate doesn't store any time zone information. It stores just one double-precision number. For every instant in time, there is just one NSDate that represents it.1

Note the difference between human representations and NSDate representations: an instant has many human representations, but only one NSDate representation.

When you print a date using %@ in NSLog, or using po in the debugger, it sends the description message to the date. The job of the description method is to produce some human representation for the unique instant represented by the NSDate. But there are many possible human representations, and the description method has to use just one. The implementors of the method decided to use the representation based on your local time zone.

If you don't like the use of the local time zone, don't use description (directly or indirectly). Create an NSDateFormatter, set its timeZone, set its other properties as desired, and use it to format your dates. You've already noted that this works. It is a correct solution to your problem.

If you want, you can add a category to NSDate defining a gmtDescription method that returns a representation of the date in GMT:

@interface NSDate (Argus9)
- (NSString *)gmtDescription;
@end

@implementation NSDate (Argus9)

- (NSString *)gmtDescription {
    static NSDateFormatter *df;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        df = [[NSDateFormatter alloc] init];
        df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
        df.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    });
    return [df stringFromDate:self];
}

@end

Footnote 1. This is not quite true, because double has two zeros: +0 and -0. Both represent the same instant in time (the reference instant itself). Also, since NSDate stores its offset as a double, and there are a finite number of doubles, it cannot represent every instant in time uniquely. Many instants in time map to the same double value.

Upvotes: 2

Related Questions