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 | Повний формат (дата + час + пояс) | 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 | Автоматичний (через опції) | Ручний рядок формату |
| Дробові секунди | Будь-яка точність (.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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також