Avatar ·

Как распарсить и создать дату/время с дробными секундами и UTC в формате ISO 8601 / RFC 3339 в Swift?

Всем привет! Я новичок в Swift и столкнулся с проблемой при работе с датами. Мне нужно отправлять на сервер и получать с сервера временные метки в формате ISO 8601 с дробными секундами и часовым поясом UTC. Пример такой строки: "2024-03-15T14:30:45.123Z". Проблема в том, что стандартный `DateFormatter` в Swift не всегда корректно обрабатывает дробные секунды. Я пробовал такой код: ```swift let dateString = "2024-03-15T14:30:45.123Z" let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" if let date = formatter.date(from: dateString) { print("Date: \(date)") } else { print("Failed to parse") } ``` Но это работает нестабильно. Иногда парсинг проходит, иногда нет. Особенно если количество цифр после точки меняется (например, ".12" или ".123456"). Ещё я пробовал использовать `ISO8601DateFormatter`, но там тоже возникают сложности с дробными секундами. Мне нужно: 1) Надёжно парсить строки с дробными секундами разной точности (от 1 до 6 знаков). 2) Создавать такие строки из `Date` для отправки на сервер. 3) Учитывать, что часовой пояс всегда UTC. Подскажите, как это правильно сделать в Swift? Есть ли встроенные средства или нужно использовать сторонние библиотеки? Заранее спасибо за помощь! <<>>

Решение с использованием ISO8601DateFormatter (iOS 10+)

Начиная с iOS 10, Apple представила ISO8601DateFormatter, который специально разработан для работы с форматом ISO 8601. Однако по умолчанию он не поддерживает дробные секунды. Но это можно легко исправить с помощью опции .withFractionalSeconds.

import Foundation

// Пример строки с дробными секундами
let dateString = "2024-03-15T14:30:45.123Z"

// Создаём форматтер
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

// Парсинг строки в Date
if let date = formatter.date(from: dateString) {
    print("Успешно распарсено: \(date)")
    
    // Обратное преобразование Date в строку
    let outputString = formatter.string(from: date)
    print("Строка для отправки: \(outputString)")
} else {
    print("Ошибка парсинга")
}

Поддержка разной точности дробных секунд

Этот код автоматически обрабатывает дробные секунды любой точности (от 1 до 6 знаков):

  • "2024-03-15T14:30:45.1Z" — 1 знак
  • "2024-03-15T14:30:45.12Z" — 2 знака
  • "2024-03-15T14:30:45.123456Z" — 6 знаков

Важные замечания

  • Опция .withFractionalSeconds доступна с iOS 11 / macOS 10.13 и новее
  • Форматтер автоматически использует часовой пояс UTC (буква Z в конце строки)
  • При обратном преобразовании в строку, дробные секунды будут содержать 3 знака (миллисекунды)
  • Если нужно другое количество знаков, используйте DateFormatter (см. альтернативный ответ)

Полный пример с обработкой ошибок

func parseISO8601Date(from string: String) -> Date? {
    let formatter = ISO8601DateFormatter()
    formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    return formatter.date(from: string)
}

func formatISO8601Date(_ date: Date) -> String {
    let formatter = ISO8601DateFormatter()
    formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    return formatter.string(from: date)
}

// Использование
if let date = parseISO8601Date(from: "2024-03-15T14:30:45.123456Z") {
    print(formatISO8601Date(date)) // "2024-03-15T14:30:45.123Z"
}

Почему это лучший подход?

  • Надёжность: Специализированный форматтер для ISO 8601
  • Простота: Минимум кода, нет необходимости вручную задавать маску
  • Совместимость: Поддержка различных вариантов ISO 8601 (с Z, с часовым поясом, без)
  • Производительность: Оптимизирован Apple для работы с датами
<<>>

Альтернативное решение с DateFormatter

Если вам нужен полный контроль над форматом (например, другое количество знаков после точки), используйте классический DateFormatter с правильной маской:

let dateString = "2024-03-15T14:30:45.123Z"
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

if let date = formatter.date(from: dateString) {
    print("Date: \(date)")
    
    // Обратное преобразование
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
    let output = formatter.string(from: date)
    print(output) // "2024-03-15T14:30:45.123Z"
}

Важные моменты

  • Всегда устанавливайте locale в en_US_POSIX для стабильной работы
  • Явно указывайте timeZone = UTC
  • Маска SSS — ровно 3 цифры миллисекунд. Для другого количества используйте соответствующее количество S
  • Этот метод не сможет обработать 6 знаков (микросекунды) — для этого нужен ISO8601DateFormatter

Когда использовать этот подход?

  • Когда нужно строго определённое количество знаков после точки
  • Для совместимости со старыми версиями iOS (до iOS 10)
  • Когда нужен нестандартный формат (например, без буквы Z, а с +00:00)