DateFormatter returns nil 与 TimeZone 和 Locale 的特定组合

DateFormatter returns nil with specific combination of TimeZone and Locale

在我们的应用程序中,从字符串创建日期时存在问题,但只能通过非常特定的组合重现。不幸的是,没有办法从遇到问题的用户那里得到它,所以我决定尝试所有可能的组合:

import Foundation

var dateOnlyDateFormatter: (String, String) -> DateFormatter = { timeZoneS, localeS in
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    formatter.timeZone = TimeZone(identifier: timeZoneS)
    formatter.locale = Locale(identifier: localeS)
    return formatter
}

let date = "2022-05-27"
let time = "06:15"

for timeZone in TimeZone.knownTimeZoneIdentifiers {
    for locale in Locale.availableIdentifiers {
        let dateFormatter = dateOnlyDateFormatter(timeZone, locale)
        let printDate = dateFormatter.date(from: date)
        if printDate == nil {
            print("TimeZone: \(timeZone), Locale: \(locale)")
        }
    }
}

结果:

TimeZone: America/Asuncion, Locale: ar_SA
TimeZone: America/Asuncion, Locale: en_SA

我不太确定处理此问题的最佳方法是什么。显然我们的 BE 可以 return 使用一个特定的 Locale 约会,比如 en_US_POSIX,但我对此几乎没有控制权,因为我是一个更大的旧系统的一部分。有人遇到过这样的问题吗?

所以如果你像这样使用'time',就不会有nil值:

let dateOnlyDateFormatter: (String, String) -> DateFormatter = { timeZoneS, localeS in
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm"
            formatter.timeZone = TimeZone(identifier: timeZoneS)
            formatter.locale = Locale(identifier: localeS)
            return formatter
        }

let date = "2022-05-27 06:15"
//let time = "06:15"

for timeZone in TimeZone.knownTimeZoneIdentifiers {
    for locale in Locale.availableIdentifiers {
        let dateFormatter = dateOnlyDateFormatter(timeZone, locale)
        let printDate = dateFormatter.date(from: date)
        if printDate == nil {
           print(">>>>>>>> TimeZone: \(timeZone), Locale: \(locale)")
        } else {
           print("..... \(printDate)")
        }
    }
}

如果您阅读 DateFormatter 文档的“Working With Fixed Format Date Representations”部分,您会发现:

For most fixed formats, you should also set the locale property to a POSIX locale ("en_US_POSIX"), and set the timeZone property to UTC.

您可能应该只听从这里的建议...但这可能是为什么南非和巴拉圭时区产生零的原因。

在该部分的下方,有一个 link 到 technical Q&A,其中对此进行了更详细的解释。与您的问题最相关的部分是:

A user can change their calendar (using System Preferences > Language & Region > Calendar on OS X, or Settings > General > International > Calendar on iOS). In that case NSDateFormatter will treat the numbers in the string you parse as if they were in the user's chosen calendar. For example, if the user selects the Buddhist calendar, parsing the year "2010" will yield an NSDate in 1467, because the year 2010 on the Buddhist calendar was the year 1467 on the (Gregorian) calendar that we use day-to-day.

在区域设置 SA 中,日期字符串的数字似乎是使用 Islamic Calendar 解释的。查看使用 en_SA 和 America/New_York.

格式化的今天日期
let dateFormatter = dateOnlyDateFormatter("America/New_York", "en_SA")
let printDate = dateFormatter.string(from: .init())
print(printDate)
// 1443-10-26

另请查看由 en_SA 和 America/New_York

解析的 non-nil 日期
let dateFormatter = dateOnlyDateFormatter("America/New_York", "en_SA")
let printDate = dateFormatter.date(from: date)
print(printDate)
// 2583-10-05 04:00:00 +0000

注意 10-05 是 2583 年的第一个星期日(请参阅 this calendar). If Paraguay still uses the same DST rules,因为它现在是 2583 年,那么这意味着在 2583-10-05 有一个 DST 间隙过渡 00:00:00,开始夏令时。从 00:00:00 开始的小时将被跳过,因此 00:00:00 将不存在。

仅解析日期时,DateFormatter 会尝试将时间组件设置为格式化程序时区中的 00:00:00,但 00:00:00 不存在,因此解析失败。


在任何情况下,只需将 locale 设置为 posix 并将 timeZone 设置为 UTC 当您设置了 dateFormat.