DateFormatter — це клас Foundation, призначений для двоспрямованого перетворення між об'єктами Date та їх рядковим представленням. Клас враховує локаль, часовий пояс і календар користувача, забезпечуючи коректне відображення дат у будь-якому регіоні світу. За даними Apple Developer Documentation (2025), DateFormatter підтримує чотири встановлених стилі дати та часу, а також повністю кастомні формати через рядок шаблону. Без DateFormatter неможливо коректно відобразити дату користувачеві в інтернаціоналізованому застосунку.
Головне
DateFormatter — це клас з фреймворку Foundation, що реалізує двоспрямоване перетворення між Date та рядком. Він з'явився в OpenStep як NSDateFormatter і з тих пір залишається основним інструментом форматування дат на всіх платформах Apple. Клас успадковується від Formatter і надає зручний API для локалізованого відображення дат.
Принцип роботи DateFormatter заснований на шаблонах Unicode LDML — тих самих, що використовуються в ICU (International Components for Unicode). Шаблон задається через властивість dateFormat, де символи y, M, d, H, m, s відповідають року, місяцю, дню, годинам, хвилинам, секундам. Повторення символу визначає формат: «y» — дворічний рік, «yyyy» — чотирирічний.
Створення DateFormatter — дорога операція, оскільки під час ініціалізації завантажуються дані локалі та календаря. Apple рекомендує створювати форматер один раз для кожного типу форматування та повторно використовувати його. У SwiftUI та UIKit форматери часто кешуються в статичних властивостях або створюються відкладено при першому зверненні.
DateFormatter застосовується в багатьох системних компонентах iOS. UIDatePicker використовує DateFormatter всередині себе для відображення дат у режимі countDownTimer. TextField з форматером на вході може автоматично валідувати введену користувачем дату. Core Data підтримує атрибути типу Date, але їх рядкове відображення завжди виконується через DateFormatter.
Thread Safety — DateFormatter не є потокобезпечним. Модифікація властивостей форматера з різних потоків призводить до невизначеної поведінки. Для багатопотокового використання створюйте окремі екземпляри форматера для кожного потоку або використовуйте синхронізацію через NSLock або serial queue.
dateStyle і timeStyle — найпростіші способи налаштувати відображення дати. Кожен стиль має чотири варіанти: .short, .medium, .long, .full. Комбінація dateStyle та timeStyle дозволяє незалежно налаштувати формат дати та часу, а властивість .none вимикає відповідну частину.
Для американської локалі .short форматує дату як «7/21/26», а для російської — як «21.07.2026». Стиль .long для російської локалі виводить «21 липня 2026», а .full — «вівторок, 21 липня 2026» із зазначенням дня тижня. Всі чотири стилі автоматично адаптуються під регіональні стандарти, включаючи порядок компонентів та роздільники.
SFDateFormatter в iOS 15+ надає альтернативний підхід через RelativeDateFormatter та DateIntervalFormatter. RelativeDateFormatter виводить «сьогодні», «вчора», «через 3 дні» для оперативного контексту. DateIntervalFormatter відображає діапазони дат: «21–25 липня 2026» — для бронювань та планування.
| Стиль | Приклад (ru_RU) | Приклад (en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 21 лип. 2026 | Jul 21, 2026 |
| .long | 21 липня 2026 | July 21, 2026 |
| .full | вівторок, 21 липня 2026 | Tuesday, July 21, 2026 |
При комбінуванні стилів DateFormatter автоматично вибирає роздільник: для .short.date + .short.time результат може бути «21.07.2026, 14:30». Для .full.date + .full.time — «вівторок, 21 липня 2026, 14:30:00 MSK». Роздільником керує локаль, а не розробник — це гарантує відповідність регіональним очікуванням користувача.
dateFormat дозволяє задати довільний шаблон форматування з використанням символів специфікації Unicode LDML. Це дає повний контроль над відображенням: можна вивести лише рік та місяць, або день тижня без числа, або час без секунд. Кастомний формат незамінний для специфічних вимог дизайну.
Основні символи — yyyy (рік: 2026), MM (місяць: 07), dd (день: 21), HH (години: 14), mm (хвилини: 30), ss (секунди: 00). Для повної назви місяця використовуйте MMMM (липень), для скороченої — MMM (лип.). День тижня — EEEE (вівторок), скорочений — E (вт).
При використанні dateFormat важливо встановити locale форматера. Якщо locale не задано, форматер використовує системну локаль, що може бути небажаним для фіксованого формату в API. Apple рекомендує встановлювати locale = Locale(identifier: «en_US_POSIX») для фіксованого міжрегіонального формату, особливо при парсингу дат із серверних відповідей.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// Парсинг кастомного рядка
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
Помилка в dateFormat — одна з частих причин крашу застосунку. Якщо формат не збігається з рядком, метод date(from:) повертає nil. Використовуйте guard let або ?? для безпечного вилучення опціонального значення. Для валідації формату тестуйте на всіх підтримуваних мовах — деякі символи LDML працюють по-різному в різних локалях.
Locale визначає, як відображаються назви місяців, днів тижня та які роздільники використовуються. DateFormatter за замовчуванням використовує Locale.current, але в деяких сценаріях потрібно вказати конкретну локаль: для фіксованого формату в логах використовуйте en_US_POSIX, для серверних дат — локаль ідентичну серверній.
Властивість TimeZone визначає часовий пояс для відображення. За замовчуванням використовується системний часовий пояс, але для застосунків з міжнародною аудиторією часто потрібно відображати дати в часовому поясі користувача або в UTC. Зміна timeZone впливає лише на відображення — значення Date залишається незмінним.
Важлива особливість: якщо DateFormatter використовується для парсингу рядка, а в рядку міститься вказівка часового поясу (наприклад, «2026-07-21T14:30:00Z» з Z для UTC), властивість timeZone ігнорується — форматер використовує часовий пояс з рядка. Якщо часовий пояс у рядку відсутній, застосовується timeZone форматера.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short
let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"
// Парсинг без часового поясу в рядку
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — спеціальний тип локалі, яка автоматично оновлюється при зміні системних налаштувань користувача. DateFormatter підтримує його за замовчуванням. Якщо застосунок працює у фоні та користувач змінює мову системи, форматер, створений до зміни, продовжить використовувати стару локаль — для оновлення потрібно створити новий екземпляр.
ISO8601DateFormatter — спеціалізований форматер для роботи з датами у форматі ISO 8601. Цей формат є стандартом де-факто для REST API, JSON та обміну даними. ISO8601DateFormatter працює значно швидше DateFormatter, оскільки не залежить від локалі та використовує фіксовану граматику парсингу.
Основні опції форматера — .withInternetDateTime (2026-07-21T14:30:00Z), .withFractionalSeconds (додає мілісекунди), .withTimeZone (включає зміщення часового поясу). Комбінуючи опції, можна отримати будь-який ISO 8601 варіант: з мілісекундами, з часовим поясом, з датою без часу.
JSONEncoder.DateEncodingStrategy дозволяє глобально налаштувати кодування дат для всіх Codable моделей. Варіанти — .iso8601 (використовує ISO8601DateFormatter), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970. Вибір стратегії впливає на весь lifecycle серіалізації та повинен бути єдиним для всіх ендпоінтів API.
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// JSONEncoder з ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// Альтернатива: JSONEncoder з кастомним форматером
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter vs ISO8601DateFormatter — вибирайте ISO8601DateFormatter для серіалізації та парсингу дат в API, оскільки він працює в 5-10 разів швидше DateFormatter та не піддається помилкам локалізації. DateFormatter залишайте для користувацького інтерфейсу, де потрібне локалізоване відображення з назвами місяців та днів тижня рідною мовою користувача.
Розглянемо реальні сценарії використання DateFormatter в iOS-застосунку: відображення в списку новин, введення дати народження та експорт звіту з датами в різних часових поясах.
RelativeDateFormatter оптимальний для новинних стрічок. Він виводить «щойно», «5 хвилин тому», «вчора» для свіжих новин та перемикається на повну дату для старих. Поріг перемикання налаштовується через calendar: для новин використовуйте поріг у 24 години, для месенджерів — тиждень.
func formatRelativeDate(_ date: Date) -> String {
let relative = RelativeDateFormatter()
relative.unitsStyle = .full
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let daysDiff = Calendar.current.dateComponents(
[.day], from: date, to: Date()
).day ?? 0
return daysDiff < 1
? relative.localizedString(for: date, relativeTo: Date())
: formatter.string(from: date)
}
Введення дати народження — ще один частий сценарій. DateFormatter налаштовується з конкретним dateFormat «dd.MM.yyyy» та locale «ru_RU». При парсингу введеного рядка важливо обробити можливі помилки: форматер повертає nil для некоректного рядка. Після успішного парсингу дата перевіряється на входження в допустимий діапазон — не раніше 1900 року, не пізніше сьогоднішнього дня.
Експорт звіту з датами потребує фіксованого формату, незалежного від локалі користувача. Використовуйте dateFormat «yyyy-MM-dd HH:mm:ss» з локалью en_US_POSIX та часовим поясом UTC. Такий підхід гарантує, що файл відкриється коректно в будь-якій країні незалежно від регіональних налаштувань системи.
| Сценарій | Форматер | Ключове налаштування |
|---|---|---|
| Новинна стрічка | RelativeDateFormatter | unitsStyle = .full |
| Введення дати | DateFormatter | dateFormat + fallback |
| API серіалізація | ISO8601DateFormatter | withInternetDateTime |
| Експорт звіту | DateFormatter | en_US_POSIX + UTC |
Часто задавані питання
Найчастіша причина — неспівпадіння dateFormat з форматом рядка. Наприклад, формат «dd.MM.yyyy» не розпарсить рядок «2026-07-21». Друга причина — невідповідність локалі: рядок «July 21, 2026» не розпарситься з локалью ru_RU. Третя — помилки в символах LDML: використовуйте yyyy, а не YYYY (різний сенс).
Ні. DateFormatter — важкий об'єкт, його ініціалізація включає завантаження даних локалі. Створюйте один екземпляр на тип форматування та перевикористовуйте. У багатопотоковому середовищі використовуйте Thread-local storage або пул форматерів з serial queue для синхронізації.
DateFormatter виводить абсолютну дату (21 липня 2026), а RelativeDateFormatter — відносну (сьогодні, вчора, через 3 дні). RelativeDateFormatter з'явився в iOS 15+ та використовує той самий LDML-шаблон, але автоматично підбирає відносне відображення.
Встановіть timeZone форматера в UTC перед парсингом. Якщо сервер віддає дату в локальному часі без зазначення таймзони, уточніть специфікацію API — найімовірніше, мається на увазі UTC. Для ISO 8601 з Z на кінці timeZone не потрібен — форматер парсить зміщення з рядка.
Не використовуйте один екземпляр з різних потоків без синхронізації. Створюйте новий екземпляр у кожному потоці або використовуйте Thread.current.threadDictionary для зберігання. Альтернатива — NSLock з блокуванням на час string(from:) та date(from:).
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також