ISO8601DateFormatter: ключевые понятия и форматирование ISO 8601

Автор: IT Sectr Опубликовано: 2026-07-13 Время чтения: 9 мин

ISO8601DateFormatter — это класс Foundation в iOS и macOS, предназначенный для форматирования и парсинга дат в международном стандарте ISO 8601. По данным Apple Developer Documentation, 2024, ISO8601DateFormatter автоматически обрабатывает форматы с миллисекундами, часовыми поясами и долями секунд без необходимости задавать DateFormat вручную. В отличие от DateFormatter, этот класс не зависит от Locale и TimeZone — он работает строго по спецификации ISO 8601, что делает его идеальным для обмена датами между сервером и клиентом. Класс доступен начиная с iOS 10 и macOS 10.12.

Главное

  • ISO8601DateFormatter — класс Foundation для форматирования дат по стандарту ISO 8601
  • Не требует DateFormat — формат определяется автоматически по настройкам опций
  • Локально-независимый — работает одинаково на всех устройствах без настройки Locale
  • Поддержка миллисекунд — обрабатывает доли секунды любой точности (три, шесть и более знаков)
  • Опции форматирования — withFullDate, withTime, withMilliseconds, withTimeZone и другие управляют компонентами вывода

Что такое ISO8601DateFormatter?

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 в Foundation?

Принцип работы 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 поддерживает любую точность — от одной до девяти цифр после запятой.

Настройки формата ISO 8601

Опции формата ISO8601DateFormatter делятся на три группы: компоненты даты (withFullDate, withYear, withMonth, withDay, withWeekOfYear), компоненты времени (withTime, withHours, withMinutes, withSeconds) и дополнительные настройки (withMilliseconds, withFractionalSeconds, withTimeZone, withColonSeparatorInTimeZone, withDashSeparatorInDate, withFullTime). Комбинируя их, можно получить практически любой подформат ISO 8601.

Основные комбинации опций

  • .withFullDate — только дата: 2024-07-21. Для парсинга строк вида YYYY-MM-DD
  • .withFullDate + .withTime — дата и время без часового пояса: 2024-07-21T14:30:00
  • .withInternetDateTime — полный формат: 2024-07-21T14:30:00Z или 2024-07-21T14:30:00+03:00
  • .withInternetDateTime + .withFractionalSeconds — с долями секунд: 2024-07-21T14:30:00.123456+00:00
  • .withFullDate + .withTime + .withTimeZone — полный формат без колонов в tz: 2024-07-21T14:30:00+0300

Важный нюанс: .withFractionalSeconds и .withMilliseconds взаимоисключающие — если установлены обе, применяется .withFractionalSeconds. Для парсинга миллисекунд из серверных данных рекомендуется .withFractionalSeconds, так как многие серверы отправляют доли секунд с тремя, шестью или девятью знаками, и .withFractionalSeconds обрабатывает любую длину.

swift
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 в Swift: примеры кода

Базовое использование ISO8601DateFormatter сводится к созданию экземпляра, настройке timeZone (рекомендуется UTC для серверных данных) и formatOptions, после чего можно вызывать string(from:) для форматирования и date(from:) для парсинга. В отличие от DateFormatter, не нужно беспокоиться о Locale — класс игнорирует региональные настройки.

swift
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" — только трёхзначные миллисекунды.

swift
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

DateFormatter также может парсить ISO 8601, но требует ручной настройки dateFormat, locale и timeZone. Основная проблема — DateFormatter зависит от Locale, и если не установить en_US_POSIX, парсинг может сломаться у пользователей из регионов с нестандартными форматами дат. ISO8601DateFormatter решает эту проблему на уровне архитектуры: он не использует Locale.

ПараметрISO8601DateFormatterDateFormatter
Настройка 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.

Типовые ошибки при парсинге ISO 8601

Забытая настройка 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?

ISO8601DateFormatter — специализированный класс Foundation для форматирования и парсинга дат в формате ISO 8601, доступный с iOS 10. Автоматически обрабатывает стандартные форматы без ручного задания dateFormat.

Чем ISO8601DateFormatter отличается от DateFormatter?

ISO8601DateFormatter не зависит от Locale, использует опции вместо dateFormat и корректно обрабатывает доли секунд любой длины. DateFormatter универсален, но требует ручной настройки и подвержен багам, связанным с региональными настройками.

Как обрабатывать доли секунд переменной длины?

Используйте опцию .withFractionalSeconds — она поддерживает от 1 до 9 знаков после запятой. Не используйте .withMilliseconds, если точность может варьироваться. .withFractionalSeconds доступен с iOS 13.

Какой часовой пояс использует ISO8601DateFormatter?

По умолчанию UTC. Для смены установите свойство timeZone. Если timeZone = nil, используется локальное время устройства. При парсинге строки с явным часовым поясом в формате +HH:MM formatter учитывает его автоматически.

Почему парсинг даты без времени возвращает nil?

Потому что formatOptions по умолчанию = .withInternetDateTime, который ожидает дату + время + часовой пояс. Для парсинга только даты установите formatOptions = [.withFullDate]. Для поддержки обоих вариантов используйте fallback с разными опциями.

Итоги

  • ISO8601DateFormatter — специализированный класс для ISO 8601, безопаснее и проще DateFormatter
  • Опции формата заменяют ручной dateFormat — комбинируйте .withFullDate, .withTime, .withTimeZone
  • Не зависит от Locale — парсинг работает одинаково на всех устройствах без настройки локали
  • .withFractionalSeconds обрабатывает доли секунд любой точности (1–9 знаков)
  • DateFormatter проигрывает в производительности, безопасности и простоте для задач ISO 8601
  • Путаница опций — withMilliseconds и withFractionalSeconds невзаимозаменяемы
  • Формат без секунд (2024-07-21T14:30+03:00) не поддерживается — нужен DateFormatter

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также