NSDate 值总是在我的时区返回

NSDate value is always returned in my time zone

TL/DR:我需要一个 NSDate 具体表示 1970-01-01 00:00:00 +0000。使用 [NSDate dateWithTimeIntervalSince1970:0] returns 根据我的时区调整日期。我不知道如何解决这个问题。

我正在尝试编写围绕 NSDates 的 Objective-C iOS 测试。问题是,我尝试创建的任何 NSDate 对象,无论是直的,还是使用 NSDateFormatter[NSDateComponents dateFromComponents],似乎总是会针对我的 Mac 当前进行调整时区。也就是说,测试将使用 NSDate 的时区调整值,它也会以这种方式出现在 Xcode 调试器中。但是,如果我在 NSDateComponentsNSDateFormatter 中明确将时区设置为 GMT,当我使用 po 检查控制台中的值时,它将 return [=44] =] 像我设置的那样在格林威治标准时间。

例如,[NSDate dateWithTimeIntervalSince1970:0] return 是日期为 1969-12-31 19:00:00 -0500 的 NSDate。 [NSDate date] returns 当前日期和时间,但根据我的时区:2015-03-03 05:49:32 -0500。我想不通为什么我不能简单地将 1970-01-01 00:00:00 +0000 作为 NSDate。

有什么我想念的吗?谁能帮我解决这个问题?

谢谢!

编辑:这是我尝试使用 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 在控制台中正确显示(当我键入 po componentDate 时),但在调试 window 中显示为时区调整。此外,这个时区调整后的值是我测试中使用的值,因此它对我编写可靠测试的能力有一定影响。

TLDR:[NSDate dateWithTimeIntervalSince1970:0] 表示您要求的日期。它是将 NSDate 转换为涉及您的时区的字符串。


作为人类,我们有很多方式来表示一个瞬间。例如,“January 1, 2001 00:00:00 GMT”表示一个特定的时间点。同一时刻的另一个名称是“2000 年 12 月 31 日 18:00:00 America/Chicago”。同一时刻的第三个名称是“2001 年 1 月 1 日 03:00:00 Europe/Moscow”。所有这些名称(字符串)以及更多名称都代表同一时刻。作为人类,我们认识到所有这些标签都代表同一时刻。

NSDate 以完全不同的方式表示时间瞬间:NSDate 存储相对于一个预定参考瞬间的偏移量(以秒为单位)。 NSDate 不存储任何时区信息。它只存储一个双精度数。对于每个瞬间,只有一个 NSDate 代表它。1

注意人类表征和 NSDate 表征之间的区别:一个瞬间有许多人类表征,但只有一个 NSDate 表征。

当您在 NSLog 中使用 %@ 或在调试器中使用 po 打印日期时,它会向该日期发送 description 消息。 description 方法的工作是为 NSDate 表示的唯一时刻生成一些人类表示。但是有很多可能的人类表示,description 方法只能使用一种。该方法的实施者决定使用基于您当地时区的表示。

如果您不喜欢使用当地时区,请不要使用 description(直接或间接)。创建一个 NSDateFormatter,设置它的 timeZone,根据需要设置它的其他属性,并用它来格式化您的日期。您已经注意到这是可行的。它是您问题的正确解决方案。

如果需要,您可以向 NSDate 添加一个类别,定义 gmtDescription 方法,returns 表示 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

脚注 1。这不完全正确,因为 double 有两个零:+0 和 -0。两者都代表同一时刻(参考时刻本身)。此外,由于 NSDate 将其偏移量存储为 double,并且双精度数的数量有限,因此它不能唯一地表示每个瞬间。许多时刻映射到相同的 double 值。