Одна из самых частых проблем при работе с API - даты. Казалось бы, что может пойти не так? Но на практике бэкенд может прислать
timestamp, ISO-строку, американский формат или вообще что-то свое.
Swift без правильной настройки просто упадет с ошибкой. Разбираемся, какие стратегии декодирования есть и когда что применять.
Что Swift делает по умолчанию:
Стандартная стратегия
.deferredToDate работает только в одном случае: если данные были закодированы самим
JSONEncoder в нативном для
Apple формате. В реальной жизни такое встречается редко. Чаще всего сервер присылает либо
Unix timestamp, либо строку в
ISO 8601, либо что-то кастомное. И здесь без явного указания стратегии не обойтись.
Timestamp - секунды или миллисекунды:
Если API отдает число - скорее всего, это
timestamp. Для секунд с 1970 года подходит
.secondsSince1970, для миллисекунд -
.millisecondsSince1970. Главное, чтобы бэкенд был последователен: если в одном поле миллисекунды, а в другом секунды - придется писать кастомную логику.
ISO 8601 - встроенная поддержка:
Для дат вроде "2025-11-01T12:00:00Z" есть готовая стратегия
.iso8601. Но у нее есть нюанс: она довольно строгая. Если сервер пришлет миллисекунды или немного другой формат часового пояса, декодирование упадет. В таких случаях помогает кастомный
ISO8601DateFormatter с ослабленными правилами.
Кастомный DateFormatter:
Когда формат не вписывается ни в одну стандартную стратегию, можно задать свой форматер:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(formatter)
Важно явно указывать локаль и часовой пояс. Иначе форматтер будет опираться на настройки устройства, и на телефоне с русской локалью дата может не распарситься.
Когда форматов несколько:
Бывает, что API в разных полях или даже в одном поле присылает даты в разных форматах. Например, новые данные в
ISO, старые - в
timestamp. Или просто разработчики бэкенда не смогли договориться.
В таких случаях выручает кастомная стратегия, где можно перебрать несколько форматов и вернуть первую успешно распарсенную дату:
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
let formatters: [DateFormatter] = [isoFormatter, legacyFormatter]
for formatter in formatters {
if let date = formatter.date(from: string) {
return date
}
}
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Неизвестный формат даты: \(string)"
)
}
Вся логика остается в одном месте, а не размазывается по моделям.
Вывод:
Даты - одно из тех мест, где декодинг может упасть в самый неожиданный момент. Хорошая новость:
Swift дает гибкие инструменты, чтобы разрулить почти любую ситуацию. Плохая: о них нужно знать и применять осознанно. Универсальной стратегии нет, но правильно настроенный декодер спасет от кучи багов на проде.