【问题标题】:Difference between setting NSCalendar's timezone using `localTimeZone` vs using `timeZoneWithAbbreviation` giving different values使用 `localTimeZone` 设置 NSCalendar 的时区与使用 `timeZoneWithAbbreviation` 给出不同值的区别
【发布时间】:2019-05-16 04:25:34
【问题描述】:

我已将我的手机(实际设备,而非模拟器)配置为位于英国伦敦,即 GMT。

当我使用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):

这是我的代码以及旁边的 cmets 中的断点和 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

【问题讨论】:

  • 如果您使用 @"UTC" 时区而不是 @"GMT" 会发生什么?除了闰秒之外,它们应该是相同的,我认为它被忽略了。我认为到目前为止已经有 37 个闰秒,这大约是你的差异的一半,所以除非它被调整两次(并且一次太多),否则不确定这是否能解释它。但这似乎很弱。
  • @CarlLindberg 刚刚测试了 UTC,它打印的内容与 GMT (08:58:45) 相同。用于打印时区的 NSLog 打印“GMT”而不是 UTC。我相信在幕后 UTC 和 GMT 是一样的。
  • 它们在技术上是不同的,但它们应该导致显示相同的时间。只是在确定。另一方面,它似乎显示的是零年(公元前 1 年)。可能会有一些奇怪的地方。也许尝试将 NSDateComponents 中的年份设置为更现代一点?
  • @CarlLindberg 如果我将组件设置为NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:[timeFormatter dateFromString:@"01-01-2000 9:00 AM"] 并将格式设置为@"MM-dd-yyyy h:mm a",那么它似乎确实匹配。如果我最初要求日期组件只使用小时和分钟,为什么有必要?
  • 它正在转换为 NSDate,这是一个完全指定的时刻,因此您需要提供年/月/日值等以从组件值中获取 NSDate - 即它总是需要完整的日历转换来创建 NSDate,如果没有指定,它将使用默认值(否则必须返回 nil)。而且我想一旦你到了 BC 年,时区的实现就会有细微的差别。不好的部分是 NSDateFormatter 不能直接在 NSDateComponents 上操作,需要 NSDate 步骤,你最好使用当前年份。

标签: ios nsdate nsdateformatter nscalendar nsdatecomponents


【解决方案1】:

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

当您使用明确的 GMT 时区时,您是在明确指定偏移量,它基本上可以按预期工作。但是,当 NSDate 使用本地时区(即您的欧洲/伦敦)格式化时,它将应用该时区发生的任何历史变化,因为这是“欧洲/伦敦”所说的。 NSTimeZone 使用的IANA time zone system 具有相当完整的所有时区更改历史,可以追溯到时区系统本身的创建。他们忽略了 1970 年左右之前来来去去的时区,但他们试图追踪他们确实拥有的时区的历史。对于伦敦来说,他们似乎在 1847 年首次使用 GMT 作为标准时间(在创建完整时区系统之前)。不过在此之前,他们(和其他所有人)使用的是“当地标准时间”,这是该城镇所在位置的自然时间——所以对于一个经度一定程度的城镇,他们的时间会相差四分钟(1440 分钟一天,除以地球经度的 360 度,每度得到四分钟)。因此,在 1847 年之前,他们使用的是“伦敦标准时间”,与“格林威治标准时间”略有不同。查看 tzdata“欧洲”源文件,他们给出了他们在 1994 年独立文章中找到的描述:

'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 年之前的伦敦(以及“欧洲/伦敦”时区)与 GMT 相差 1 分 15 秒,它会相应地调整您的时间。由于您的 NSDate 是公元前 1 年,因此它肯定早于 1847 年,因此您可以进行调整。当我以同样的方式尝试 America/New_York 时,它与纯 GMT-5 的时间相差 3:58。

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

【讨论】:

  • 对于我在 SO 上提出的问题,这是我见过的最令人惊奇和有趣的答案。非常感谢你,解释得很好,很有意义!只是好奇,您是如何调查此问题并遇到This yields GMTOFF = -0:01:15 for London LMT in the 18th century. 的?比如你的流程是什么让你看到那篇文章?再次感谢!
  • 当我在本地尝试时,看到 America/New_York 的不同(并且看似随机)的结果,我明白了发生了什么。之后,它正在下载 tzdata 文件(可在我在答案中提供的 IANA 链接中获得)以查看欧洲/伦敦的具体历史。 tzdata 源文件有大量的 cmets,并且会不断更新。阅读它们可能会很有趣,如果只是为了欣赏为了跟上世界各个角落的变化而付出的努力,以及政治家的一时兴起。
  • (一个古老的最爱是北美 tzdata 文件,当它试图追踪最近创建的加拿大领土 Nunavut 试图在 1999 年进入单一时区时发生了什么 - 有一些阻力那个,例如在 Pangnirtung 镇,导致无法在文件中指定的情况。在接下来的几年里,努纳武特回溯到我认为的单一时区,并回到三个。)
  • 谢谢你的解释,我有你+50赏金!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-24
  • 2010-12-10
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2015-12-03
相关资源
最近更新 更多