iOS 11.2.6 DateFormatter.date возвращает ноль для городов, которые наблюдают за бразилийским летним временем

Я создал новый проект приложения с одним viewDidLoad и добавил только следующий код в метод 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")
}

Вышеприведенный код работает повсюду в мире, кроме городов, которые наблюдают за бразилийским летним временем (BRST).

Я проверил города во всех 38 часовых поясах, перечисленных здесь, изменив часовой пояс на устройстве в разделе "Настройки"> "Основные"> "Дата и время". Я также подтвердил, что города BRST отлично работают на устройстве iOS 11.0, но не iOS 11.2.6.

Также обратите внимание, что даже на iOS 11.2.6 почти каждая другая комбинация месяца/года, которую я пробовал, работает нормально. Только "11/20" и "11/26", похоже, терпят неудачу.

Почему этот код возвращает ноль для городов, которые наблюдают BRST на iOS 11.2.6?

Ответ 1

Причина, по которой он не работает в течение одного часового пояса (Сан-Паулу), объясняется соображениями, специфичными для этого часового пояса, а именно летней экономией. Переход на летнее время в Бразилии начинается в первое воскресенье ноября, которое, как ожидается, будет 1 ноября в 2020 году.

Время, в течение которого оно по умолчанию не существует, поэтому оно возвращает nil.

Вы можете поиграть с ним, изменив форматтер, чтобы включить время и посмотреть, когда начинается форматирование даты.

Раньше сталкивались с подобной путаницей; Даты и часовые пояса сложны.

Если вы используете формат даты для отображения дат для пользователя, вы, как правило, не должны переопределять их настройки устройства, и вы, вероятно, хотите избежать использования фиксированного формата даты и должны знать о возможности того, чтобы форматировщик даты возвращал нуль value (Быстрые опции для спасения). Но если вы используете его для внутренних дат, например, для вашего API, вам следует рассмотреть возможность установки явного языка в вашем форматировании даты, чтобы такого рода вещи не происходили, например:

dateFormatter.locale = Locale(identifier: "en_US_POSIX")

Ответ 2

Ответ на прокладку был на месте, но я хотел бы предоставить еще несколько уточняющих деталей.

15 декабря 2017 года президент Бразилии Мишель Темер подписал указ об изменении начала летнего времени (DST) до первого воскресенья ноября, начиная с 2018 года. Похоже, что iOS 11.2.6 является первой версией iOS, чтобы забрать это изменение, поэтому этот вопрос не был замечен в предыдущих версиях iOS.

Проблема с переходом времени на первое воскресенье ноября - это то, что первое воскресенье ноября может оказаться (и в случае с 2020 и 2026 годами) 1 ноября. Почему это создает проблему? Хорошо, так получилось, что в Бразилии также есть свои часы "весной вперед" в полночь (с 23:59 до 1:00), когда начинается DST.

Почему в день 1 ноября в полночь возникает проблема? Хорошо, когда мы запрашиваем у iOS объект Date на основе только месяца/года, когда пользователь вводит (например, 11/20), объект Date по умолчанию помещается в первый день месяца в полночь. Это проблематично, потому что в годы, когда DST начинается 1 ноября в полночь в Бразилии, этого времени технически никогда не существует. Поэтому, когда мы запрашиваем у iOS объект Date для 11/20, iOS пытается вернуть 11/01/2020 @00:00, но он возвращает нуль, поскольку это время никогда не существует.

Таким образом, эта проблема может быть технически осуществлена в любой временной зоне, которая наблюдает DST, которая может начинаться 1-го числа в полночь. Пройдя через некоторые страны и проверив свои правила DST, я нашел Бразилию, чтобы позволить DST начинаться 1-го числа в полночь.

Чтобы исправить эту проблему, я сделал следующее:

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 - это произвольное количество времени в будущем, которое должно безопасно учитывать изменения ДСТ с полуночи.