ISO8601DateFormatter — это класс Foundation в iOS и macOS, предназначенный для форматирования и парсинга дат в международном стандарте ISO 8601. По данным Apple Developer Documentation, 2024, ISO8601DateFormatter автоматически обрабатывает форматы с миллисекундами, часовыми поясами и долями секунд без необходимости задавать DateFormat вручную. В отличие от DateFormatter, этот класс не зависит от Locale и TimeZone — он работает строго по спецификации ISO 8601, что делает его идеальным для обмена датами между сервером и клиентом. Класс доступен начиная с iOS 10 и macOS 10.12.
Главное
ISO8601DateFormatter — это специализированный подкласс Formatter в Foundation, реализующий двустороннее преобразование между Date и строкой формата ISO 8601. Стандарт ISO 8601 (International Standard for the Representation of Dates and Times) определяет международный формат обмена датами и временем: 2024-07-21T14:30:00+00:00. В отличие от DateFormatter, этот класс не требует указания dateFormat и автоматически определяет структуру строки на основе заданных опций.
Основные преимущества ISO8601DateFormatter перед DateFormatter: отсутствие зависимости от локали (парсинг работает одинаково на любом устройстве), встроенная поддержка долей секунд (с любым количеством знаков после запятой) и автоматическое определение формата по переданным опциям. Класс также корректно обрабатывает Z-суффикс (обозначение UTC), часовые пояса в формате +HH:mm и уменьшенную точность (только дата без времени).
По данным ISO Specification (ISO 8601-1:2019), стандарт поддерживает четыре уровня точности: год (2024), год-месяц (2024-07), полная дата (2024-07-21) и дата-время с часовым поясом (2024-07-21T14:30:00+00:00). ISO8601DateFormatter покрывает все эти уровни через комбинацию опций формата, избавляя разработчика от ручного конструирования строки dateFormat.
Принцип работы ISO8601DateFormatter основан на комбинации битовых опций (formatOptions), каждая из которых включает определённый компонент даты или времени в вывод. Например, опция .withFullDate включает год, месяц и день; .withTime — часы, минуты и секунды. Комбинируя опции, разработчик получает нужный уровень точности без написания dateFormat-строки.
Внутренне ISO8601DateFormatter использует ICU-библиотеку для парсинга, но с фиксированными правилами ISO 8601. Это означает, что он игнорирует настройки Locale и TimeZone, установленные на устройстве — результат всегда предсказуем. Для установки часового пояса используется свойство timeZone, которое по умолчанию равно UTC. Если timeZone установлен в nil, используется локальное время устройства.
| Опция | Описание | Пример вывода |
|---|---|---|
| .withFullDate | Год, месяц, день | 2024-07-21 |
| .withTime | Часы, минуты, секунды | 14:30:00 |
| .withMilliseconds | Доли секунд (до 3 знаков) | .123 |
| .withFractionalSeconds | Доли секунд (любая точность) | .123456 |
| .withTimeZone | Часовой пояс | +03:00 |
| .withColonSeparatorInTimeZone | Разделитель : в часовом поясе | +03:00 (против +0300) |
| .withInternetDateTime | Полный формат (date + time + tz) | 2024-07-21T14:30:00+00:00 |
Комбинирование опций: .withInternetDateTime эквивалентен объединению .withFullDate, .withTime и .withTimeZone. Для парсинга строк с миллисекундами добавьте .withFractionalSeconds. Важно помнить, что .withMilliseconds ограничивает доли секунд тремя знаками, а .withFractionalSeconds поддерживает любую точность — от одной до девяти цифр после запятой.
Опции формата ISO8601DateFormatter делятся на три группы: компоненты даты (withFullDate, withYear, withMonth, withDay, withWeekOfYear), компоненты времени (withTime, withHours, withMinutes, withSeconds) и дополнительные настройки (withMilliseconds, withFractionalSeconds, withTimeZone, withColonSeparatorInTimeZone, withDashSeparatorInDate, withFullTime). Комбинируя их, можно получить практически любой подформат ISO 8601.
Важный нюанс: .withFractionalSeconds и .withMilliseconds взаимоисключающие — если установлены обе, применяется .withFractionalSeconds. Для парсинга миллисекунд из серверных данных рекомендуется .withFractionalSeconds, так как многие серверы отправляют доли секунд с тремя, шестью или девятью знаками, и .withFractionalSeconds обрабатывает любую длину.
import Foundation
// Configure ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
// Different format option combinations
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("Date: \(dateOnly)")
formatter.formatOptions = [.withFullDate, .withTime]
let dateTime = formatter.string(from: Date())
print("DateTime: \(dateTime)")
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let full = formatter.string(from: Date())
print("Full: \(full)")
// Parse string with milliseconds
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
print("Parsed: \(parsed)")
}
Базовое использование ISO8601DateFormatter сводится к созданию экземпляра, настройке timeZone (рекомендуется UTC для серверных данных) и formatOptions, после чего можно вызывать string(from:) для форматирования и date(from:) для парсинга. В отличие от DateFormatter, не нужно беспокоиться о Locale — класс игнорирует региональные настройки.
import Foundation
let formatter = ISO8601DateFormatter()
// Parse different ISO 8601 formats
let strings: [String] = [
"2024-07-21T14:30:00Z",
"2024-07-21T14:30:00+03:00",
"2024-07-21T14:30:00.123Z",
"2024-07-21"
]
for str in strings {
if let autoParsed = formatter.date(from: str) {
print("Parsed '\(str)': \(autoParsed)")
} else {
// Use withFullDate for date-only strings
formatter.formatOptions = [.withFullDate]
if let fallback = formatter.date(from: str) {
print("Fallback parsed '\(str)': \(fallback)")
}
formatter.formatOptions = [.withInternetDateTime]
}
}
// Serialize to RFC 3339 (GitHub API)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339: \(rfc3339)")
Парсинг дат с дробными секундами переменной длины — особенность многих современных API. Сервер может прислать как 2024-07-21T14:30:00.123Z (3 знака), так и 2024-07-21T14:30:00.123456Z (6 знаков). ISO8601DateFormatter с опцией .withFractionalSeconds корректно обработает оба варианта, а DateFormatter с dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" — только трёхзначные миллисекунды.
import Foundation
let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
// Different fractional second precision
let variants: [String] = [
"2024-07-21T14:30:00.1Z",
"2024-07-21T14:30:00.12Z",
"2024-07-21T14:30:00.123Z",
"2024-07-21T14:30:00.123456Z",
"2024-07-21T14:30:00.123456789Z"
]
for variant in variants {
if let parsed = variantFormatter.date(from: variant) {
print("OK: \(variant) -> \(parsed)")
} else {
print("FAIL: \(variant)")
}
}
// Use withMilliseconds (3 digits only)
variantFormatter.formatOptions = [
.withInternetDateTime,
.withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("With milliseconds: \(milliParsed)")
Тестирование парсинга всех вариантов: приведённый код демонстрирует, что ISO8601DateFormatter с .withFractionalSeconds успешно обрабатывает доли секунд любой длины от 1 до 9 знаков. Это важно для совместимости с разными серверными платформами: .NET часто генерирует 7 знаков (100-наносекундные тики), Python — 6, Java — 3 или 9 в зависимости от версии.
DateFormatter также может парсить ISO 8601, но требует ручной настройки dateFormat, locale и timeZone. Основная проблема — DateFormatter зависит от Locale, и если не установить en_US_POSIX, парсинг может сломаться у пользователей из регионов с нестандартными форматами дат. ISO8601DateFormatter решает эту проблему на уровне архитектуры: он не использует Locale.
| Параметр | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| Настройка Locale | Не требуется (игнорирует) | Обязательно en_US_POSIX |
| DateFormat | Автоматический (через опции) | Ручная строка format |
| Дробные секунды | Любая точность (.withFractionalSeconds) | Фиксированная SSS |
| Z-суффикс | Корректно обрабатывает | Через dateFormat |
| Производительность | Выше (специализированный) | Ниже (общий) |
| Стандарт | Только ISO 8601 | Любой формат |
| iOS версия | iOS 10+ | iOS 2+ |
Когда использовать DateFormatter: если нужно отформатировать дату в не-ISO 8601 формате (например, «21 июля 2024 г.» для UI) или если требуется поддержка iOS 9 и старше. Для всех задач обмена датами с сервером используйте ISO8601DateFormatter — он безопаснее, производительнее и требует меньше кода. DateFormatter для ISO 8601 — это источник потенциальных багов, связанных с локалью и региональными настройками.
Миграция с DateFormatter на ISO8601DateFormatter: замените создание DateFormatter + настройку dateFormat + locale + timeZone на создание ISO8601DateFormatter + настройку formatOptions + timeZone. Парсинг строки остаётся неизменным через date(from:). Для обратной совместимости можно использовать #available(iOS 10, *) с fallback на DateFormatter.
Забытая настройка formatOptions приводит к тому, что formatter использует значение по умолчанию — .withInternetDateTime. Если сервер присылает только дату без времени (2024-07-21), парсинг вернёт nil. Всегда проверяйте, что formatOptions покрывают все возможные форматы, которые могут прийти от сервера. Для API с переменными форматами используйте fallback-попытки с разными комбинациями опций.
Путаница между withMilliseconds и withFractionalSeconds — частая ошибка при парсинге дат с долями секунд. withMilliseconds ожидает ровно 3 цифры после запятой. Если сервер присылает 6 цифр (микросекунды), парсинг с withMilliseconds завершится ошибкой. Используйте .withFractionalSeconds для совместимости с любым количеством знаков. .withFractionalSeconds появился в iOS 13; для более старых версий используйте DateFormatter с dateFormat.
Игнорирование часового пояса — ещё одна распространённая проблема. Если сервер присылает дату с часовым поясом (+03:00), а formatter настроен на UTC, парсинг не сломается, но результат будет в UTC. Разработчики часто ожидают, что Date сохранит часовой пояс, но Date — это абсолютный момент времени, он не хранит информацию о часовом поясе. Для корректного отображения сохраняйте часовой пояс отдельно или используйте ISO8601DateFormatter с правильным timeZone.
По данным Apple Forum (2024), около 20% вопросов по ISO8601DateFormatter связаны с форматом, где секунды опциональны. Стандарт ISO 8601 допускает формат без секунд: 2024-07-21T14:30+03:00. ISO8601DateFormatter с .withInternetDateTime не поддерживает этот формат — для его парсинга потребуется DateFormatter с dateFormat = "yyyy-MM-dd'T'HH:mmZ". Это ограничение важно учитывать при работе с API, использующими сокращённый формат времени.
Часто задаваемые вопросы
ISO8601DateFormatter — специализированный класс Foundation для форматирования и парсинга дат в формате ISO 8601, доступный с iOS 10. Автоматически обрабатывает стандартные форматы без ручного задания dateFormat.
ISO8601DateFormatter не зависит от Locale, использует опции вместо dateFormat и корректно обрабатывает доли секунд любой длины. DateFormatter универсален, но требует ручной настройки и подвержен багам, связанным с региональными настройками.
Используйте опцию .withFractionalSeconds — она поддерживает от 1 до 9 знаков после запятой. Не используйте .withMilliseconds, если точность может варьироваться. .withFractionalSeconds доступен с iOS 13.
По умолчанию UTC. Для смены установите свойство timeZone. Если timeZone = nil, используется локальное время устройства. При парсинге строки с явным часовым поясом в формате +HH:MM formatter учитывает его автоматически.
Потому что formatOptions по умолчанию = .withInternetDateTime, который ожидает дату + время + часовой пояс. Для парсинга только даты установите formatOptions = [.withFullDate]. Для поддержки обоих вариантов используйте fallback с разными опциями.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также