使用“localTimeZone”设置 NSCalendar 时区与使用“timeZoneWithAbbreviation”给出不同值之间的区别

Difference between setting NSCalendar's timezone using `localTimeZone` vs using `timeZoneWithAbbreviation` giving different values

我已将我的 phone(实际设备,而非模拟器)配置为位于格林威治标准时间的英国伦敦。

当我使用 localTimeZoneNSCalendar 上设置时区时,与使用 [NSTimeZone timeZoneWithAbbreviation:@"GMT"] 设置时相比,它让我的时间减少了 1 分 15 秒。如果我的时区已经设置为 GMT,两者不应该给我相同的结果吗?

我注意到的另一件事是,如果我设置断点,通过断点检查使用 localTimeZone 创建的 NSDate 会关闭 1 分 15 秒,但 NSLog 会打印正确的时间(断点显示:0000-12-30 09:01:15 UTC 但 NSLog 打印 Sat Dec 30 09:00:00 0000):

但是当我使用 [NSTimeZone timeZoneWithAbbreviation:@"GMT"] 时,断点显示正确的时间,但 NSLog 打印错误时间 1 分 15 秒(断点显示:0000-12-30 09:00:00 UTC 但 NSLog 打印 Sat Dec 30 08:58:45 0000):

这是我的代码以及旁边注释中的断点和 NSLog 输出:

NSLog(@"Timezone: %@",[NSTimeZone localTimeZone]);//Timezone: Local Time Zone (Europe/London (GMT) offset 0)

    NSLocale *locale = [NSLocale currentLocale];
    NSCalendar *myCalendar = [NSCalendar currentCalendar];
    [myCalendar setLocale:locale];
    [myCalendar setTimeZone:[NSTimeZone localTimeZone]];
    NSLog(@"TZ: %@",myCalendar.timeZone);//TZ: Local Time Zone (Europe/London (GMT) offset 0)

    NSDateFormatter *timeFormatter = [NSDateFormatter new];
    [timeFormatter setDateFormat:@"h:mm a"];
    [timeFormatter setLocale:locale];
    [timeFormatter setTimeZone:[NSTimeZone localTimeZone]];

    NSDateComponents* dateComps = [myCalendar components:NSCalendarUnitHour|NSCalendarUnitMinute fromDate:[timeFormatter dateFromString:@"9:00 AM"]];
    NSDate *myDate1 = [myCalendar dateFromComponents:dateComps];//Breakpoint shows: 0000-12-30 09:01:15 UTC


    NSLog(@"myDate1: %@",myDate1); // myDate1: Sat Dec 30 09:00:00 0000

    //----------------Explicitly specified timeZoneWithAbbreviation GMT:


    NSCalendar *myCalendarExplicitGMT = [NSCalendar currentCalendar];
    [myCalendarExplicitGMT setLocale:locale];
    [myCalendarExplicitGMT setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
    NSLog(@"TZ: %@",myCalendarExplicitGMT.timeZone);//TZ: GMT (GMT) offset 0

    NSDateComponents* dateCompsUsingExplicitGMT = [myCalendarExplicitGMT components:NSCalendarUnitHour|NSCalendarUnitMinute fromDate:[timeFormatter dateFromString:@"9:00 AM"]];
    NSDate *myDate2 = [myCalendarExplicitGMT dateFromComponents:dateCompsUsingExplicitGMT];//Breakpoint shows: 0000-12-30 09:00:00 UTC


    NSLog(@"myDate2: %@",myDate2); // myDate2: Sat Dec 30 08:58:45 0000

在计算过程中,您将获得一个只有小时和分钟值的 NSDateComponents。你在这一点上很好。但是,要将 NSDateFormatter 与它一起使用,您将转换为 NSDate(因为这是 NSDateFormatter 所需要的)。 NSDate 是一个绝对的即时时间,所以它需要一个完全指定的日期来转换。它没有返回 nil,而是对 NSDateComponents 中缺失的任何字段使用默认(零)值。因此,它使用的是第 1 年(实际上将 wards 调整回 1 BC)。执行此操作时,您会看到年份打印为“0000”。

当您使用明确的 GMT 时区时,您就是在明确指定偏移量,并且它基本上按预期工作。然而,当 NSDate 使用本地时区格式化时,即 Europe/London 对你来说,它将应用在该时区发生的任何历史变化,因为那是 "Europe/London" 说要做的。 NSTimeZone 使用的 IANA time zone system 具有相当完整的所有时区更改历史记录,可以追溯到时区系统本身的创建。他们忽略了 1970 年左右之前出现和消失的时区,但他们试图追踪他们确实拥有的时区的历史,回到他们的起源。对于伦敦,他们似乎在 1847 年首次使用 GMT 作为标准时间(在创建完整时区系统之前)。不过在此之前,他们(和其他所有人)使用的是 "Local Mean Time",这是该城镇所在位置的自然时间——因此对于经度相距一定度数的城镇,他们的时间将相差四分钟(1440 分钟在一个天除以地球经度的 360 度,每度得到四分钟)。因此,在 1847 年之前,他们使用 "London Mean Time",这与 "Greenwich Mean Time" 略有不同。查看 tzdata "europe" 源文件,他们给出了他们在 1994 Independent 文章中找到的描述:

'An old stone obelisk marking a forgotten terrestrial meridian stands
beside the river at Kew. In the 18th century, before time and longitude
was standardised by the Royal Observatory in Greenwich, scholars observed
this stone and the movement of stars from Kew Observatory nearby. They
made their calculations and set the time for the Horse Guards and Parliament,
but now the stone is obscured by scrubwood and can only be seen by walking
along the towpath within a few yards of it.'

I have a one inch to one mile map of London and my estimate of the stone's
position is 51° 28' 30" N, 0° 18' 45" W. The longitude should
be within about ±2". The Ordnance Survey grid reference is TQ172761.

[This yields GMTOFF = -0:01:15 for London LMT in the 18th century.]

因此,他们计算出在 1847 年之前,伦敦(因此 "Europe/London" 时区)与格林威治标准时间相差一分十五秒,它会相应地调整您的时间。由于您的 NSDate 是公元前 1 年,它肯定早于 1847 年,因此您可以进行调整。当我以同样的方式尝试 America/New_York 时,它与纯粹的 GMT-5 3:58 不同。

所以简短的回答是——当您使用现代时区进行日期转换时,最好让其余的日期组件也保持现代。例如,如果你让年份回溯到二战,你将开始应用伦敦在 war 期间的双倍夏令时,并且从 1800 年代和更早的时间开始,你开始进入让铁路公司疯狂的时间系统;-).如您所见,您的另一个选择是使用非历史但具有可预测偏移量的 NSTimeZones。 NSLog() 虽然使用 NSDate -description 方法,该方法将依次使用当前(历史)时区,并且总是会获得那些相当任意的古代偏移量。