【问题标题】:iOS 11.2.6 DateFormatter.date returns nil for cities that observe Brasília Summer Time [duplicate]对于遵守巴西利亚夏令时的城市,iOS 11.2.6 DateFormatter.date 返回 nil [重复]
【发布时间】:2018-03-22 21:29:08
【问题描述】:

我从头开始创建了一个新的 Single View App 项目,并且只将以下代码添加到 ViewController 的 viewDidLoad 方法中:

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yy"
if let date = dateFormatter.date(from: "11/20") {
    print("got the date: \(date)")
} else {
    print("failed getting the date")
}

上面的代码可以在世界任何地方除了观察Brasília Summer Time (BRST)的城市。

我测试了here 列出的所有 38 个时区的城市,方法是在设置 > 常规 > 日期和时间中更改设备上的时区。我还确认 BRST 城市在 iOS 11.0 设备上运行良好,但 不是 iOS 11.2.6。

还要注意,即使在 iOS 11.2.6 上,我尝试过的几乎每隔一个月/一年的组合都可以正常工作。 只有“11/20”和“11/26”似乎失败了。

为什么对于在 iOS 11.2.6 上遵守 BRST 的城市,此代码返回 nil?

【问题讨论】:

  • 您是否能够通过在dateFormatter 上设置TimeZone 而不仅仅是使用设备设置来重现此问题?您在日期和时间设置中选择什么时区?
  • 你是对的 - 如果我设置 dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 它不再为“11/20”返回 nil。
  • @shim 我使用圣保罗作为 UTC−02:00 时区。
  • 但是seconds from GMT: 0 和UTC-2 不是同一个时区……
  • @shim,对 - 这就是为什么它在将 dateFormatter 的时区更改为非中断时区时起作用的原因。我只是说如果我将设备上的时区保持为 UTC-02:00 并手动设置 dateFormatter 的时区,这是一个潜在的修复/解决方法。

标签: ios nsdateformatter


【解决方案1】:

shim 的回答很到位,但我想提供一些更明确的细节。

2017 年 12 月 15 日,巴西总统 Michel Temer 签署了decree,将夏令时 (DST) 的开始时间更改为 11 月的第一个星期日,从 2018 年开始。看起来 iOS 11.2.6 是第一个版本的 iOS 来接受这个变化,这就是为什么这个问题在以前的 iOS 版本中没有出现。

时间变化移动到 11 月的第一个星期日的问题是 11 月的第一个星期日最终可能是(在 2020 年和 的情况下2026 年)11 月 1 日。为什么这会出现问题?恰巧巴西在夏令时开始时将他们的时钟“向前跳”at midnight(从晚上 11:59 到凌晨 1:00)。

为什么 11 月 1 日午夜的日期会出现问题?好吧,当我们要求 iOS 在用户输入时仅基于月/年(例如 11/20)给我们一个 Date 对象时,Date 对象默认为该月的第一天午夜。这是有问题的,因为在巴西从 11 月 1 日午夜开始夏令时的年份,从技术上讲,那个时间根本不存在。因此,当我们要求 iOS 为我们提供 11/20 的 Date 对象时,iOS 会尝试返回 11/01/2020 @ 00:00,但它返回 nil,因为该时间不存在。

因此,从技术上讲,这个问题可能发生在 任何 遵守 DST 的时区,该时区可以在每月 1 日午夜开始。在浏览了一些国家并检查了他们的 DST 规则后,我发现巴西只允许在每月 1 日午夜开始 DST。

为了解决这个问题,我有效地执行了以下操作:

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/yy"
let dateFormatterWithTime = DateFormatter()
dateFormatterWithTime.dateFormat = "MM/yy HH:mm"
if let date = dateFormatter.date(from: "11/20") ??
    dateFormatterWithTime.date(from: "11/20 03:00") {
    print("got the date: \(date)")
} else {
    print("failed getting the date")
}

03:00 是未来的任意时间量,应该安全地考虑从午夜开始的 DST 变化。

【讨论】:

  • 这太棒了!
  • 请注意,您永远不应依赖特定数量的夏令时时间,因为它可能随时发生变化。请注意,自选举实际总统博尔索纳罗撤销 DST 以来,巴西不再有 DST。无需设置具体时间。您需要设置日期格式化程序的日历属性。
  • 顺便说一句,现在巴西不再有 DST,它不再在该日期返回 nil。
【解决方案2】:

它不适用于该时区(圣保罗)的原因是该时区特有的考虑因素,即夏令时。巴西的夏令时从 first Sunday in November 开始,恰好是 2020 年 11 月 1 日。

它默认的时间不存在,所以它返回nil。

您可以通过更改格式化程序以包含时间并准确查看日期格式化程序何时开始返回 nil 来玩弄它。

以前也遇到过类似的困惑;日期和时区很棘手。

如果您使用日期格式化程序向用户显示日期,通常不应覆盖他们的设备设置,并且您可能希望避免使用固定日期格式,并且应该注意日期格式化程序可能会返回一个 nil 值(用于救援的 Swift 选项)。但是如果你将它用于内部日期,例如你的 API,你应该考虑在你的日期格式化程序上设置一个明确的语言环境/时区,这样这些事情就不会发生,for example:

dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

【讨论】:

    【解决方案3】:

    使其工作的另一种方法是将DateFormatter 时区设置为UTC。所以你的代码看起来像:

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "MM/yy"
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    
    if let date = dateFormatter.date(from: "11/20") {
        print("got the date: \(date)")
    } else {
        print("failed getting the date")
    }
    

    通过这样做,代码将打印以下内容:

    得到日期:2020-11-01 00:00:00 +0000

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-13
      • 2011-07-26
      • 1970-01-01
      • 2016-12-13
      • 1970-01-01
      • 2019-10-25
      • 1970-01-01
      • 2015-05-22
      相关资源
      最近更新 更多