DateFormatter: ключові поняття, форматування дати та локалізація

Автор: IT Sectr Опубліковано: 2026-07-12 Час читання: 7 хв

DateFormatter — це клас Foundation, призначений для двоспрямованого перетворення між об'єктами Date та їх рядковим представленням. Клас враховує локаль, часовий пояс і календар користувача, забезпечуючи коректне відображення дат у будь-якому регіоні світу. За даними Apple Developer Documentation (2025), DateFormatter підтримує чотири встановлених стилі дати та часу, а також повністю кастомні формати через рядок шаблону. Без DateFormatter неможливо коректно відобразити дату користувачеві в інтернаціоналізованому застосунку.

Головне

  • DateFormatter — клас для перетворення Date в рядок і назад з урахуванням локалі та часового поясу.
  • dateStyle і timeStyle — встановлені стилі (.short, .medium, .long, .full) для швидкого форматування.
  • dateFormat — рядок шаблону для кастомного формату, що задається через символи специфікації Unicode LDML.
  • Locale і TimeZone — властивості форматера, що визначають регіональне відображення та часовий пояс.
  • ISO8601DateFormatter — швидша альтернатива для формату ISO 8601 при серіалізації в API.

Що таке 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 SDK

DateFormatter застосовується в багатьох системних компонентах iOS. UIDatePicker використовує DateFormatter всередині себе для відображення дат у режимі countDownTimer. TextField з форматером на вході може автоматично валідувати введену користувачем дату. Core Data підтримує атрибути типу Date, але їх рядкове відображення завжди виконується через DateFormatter.

Thread Safety — DateFormatter не є потокобезпечним. Модифікація властивостей форматера з різних потоків призводить до невизначеної поведінки. Для багатопотокового використання створюйте окремі екземпляри форматера для кожного потоку або використовуйте синхронізацію через NSLock або serial queue.

Стилі форматування DateFormatter

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)
.short21.07.20267/21/26
.medium21 лип. 2026Jul 21, 2026
.long21 липня 2026July 21, 2026
.fullвівторок, 21 липня 2026Tuesday, July 21, 2026

При комбінуванні стилів DateFormatter автоматично вибирає роздільник: для .short.date + .short.time результат може бути «21.07.2026, 14:30». Для .full.date + .full.time — «вівторок, 21 липня 2026, 14:30:00 MSK». Роздільником керує локаль, а не розробник — це гарантує відповідність регіональним очікуванням користувача.

Кастомні формати через dateFormat

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») для фіксованого міжрегіонального формату, особливо при парсингу дат із серверних відповідей.

swift
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 працюють по-різному в різних локалях.

Локалізація та TimeZone

Locale визначає, як відображаються назви місяців, днів тижня та які роздільники використовуються. DateFormatter за замовчуванням використовує Locale.current, але в деяких сценаріях потрібно вказати конкретну локаль: для фіксованого формату в логах використовуйте en_US_POSIX, для серверних дат — локаль ідентичну серверній.

Властивість TimeZone визначає часовий пояс для відображення. За замовчуванням використовується системний часовий пояс, але для застосунків з міжнародною аудиторією часто потрібно відображати дати в часовому поясі користувача або в UTC. Зміна timeZone впливає лише на відображення — значення Date залишається незмінним.

Важлива особливість: якщо DateFormatter використовується для парсингу рядка, а в рядку міститься вказівка часового поясу (наприклад, «2026-07-21T14:30:00Z» з Z для UTC), властивість timeZone ігнорується — форматер використовує часовий пояс з рядка. Якщо часовий пояс у рядку відсутній, застосовується timeZone форматера.

swift
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 та альтернативи

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.

swift
// 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 години, для месенджерів — тиждень.

swift
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. Такий підхід гарантує, що файл відкриється коректно в будь-якій країні незалежно від регіональних налаштувань системи.

СценарійФорматерКлючове налаштування
Новинна стрічкаRelativeDateFormatterunitsStyle = .full
Введення датиDateFormatterdateFormat + fallback
API серіалізаціяISO8601DateFormatterwithInternetDateTime
Експорт звітуDateFormatteren_US_POSIX + UTC

Часто задавані питання

Чому DateFormatter повертає nil для коректного рядка?

Найчастіша причина — неспівпадіння dateFormat з форматом рядка. Наприклад, формат «dd.MM.yyyy» не розпарсить рядок «2026-07-21». Друга причина — невідповідність локалі: рядок «July 21, 2026» не розпарситься з локалью ru_RU. Третя — помилки в символах LDML: використовуйте yyyy, а не YYYY (різний сенс).

Чи потрібно створювати новий DateFormatter для кожного виклику?

Ні. DateFormatter — важкий об'єкт, його ініціалізація включає завантаження даних локалі. Створюйте один екземпляр на тип форматування та перевикористовуйте. У багатопотоковому середовищі використовуйте Thread-local storage або пул форматерів з serial queue для синхронізації.

Чим DateFormatter відрізняється від RelativeDateFormatter?

DateFormatter виводить абсолютну дату (21 липня 2026), а RelativeDateFormatter — відносну (сьогодні, вчора, через 3 дні). RelativeDateFormatter з'явився в iOS 15+ та використовує той самий LDML-шаблон, але автоматично підбирає відносне відображення.

Як обробити дати без часового поясу з API?

Встановіть timeZone форматера в UTC перед парсингом. Якщо сервер віддає дату в локальному часі без зазначення таймзони, уточніть специфікацію API — найімовірніше, мається на увазі UTC. Для ISO 8601 з Z на кінці timeZone не потрібен — форматер парсить зміщення з рядка.

Як зробити DateFormatter thread-safe?

Не використовуйте один екземпляр з різних потоків без синхронізації. Створюйте новий екземпляр у кожному потоці або використовуйте Thread.current.threadDictionary для зберігання. Альтернатива — NSLock з блокуванням на час string(from:) та date(from:).

Підсумки

  • DateFormatter — клас Foundation для перетворення Date в рядок і назад з урахуванням локалі, часового поясу та календаря користувача.
  • Встановлені стилі dateStyle та timeStyle з варіантами .short, .medium, .long, .full покривають більшість UI-сценаріїв.
  • Кастомний dateFormat через символи LDML дає повний контроль над форматом, але потребує обережності з локалізацією.
  • Locale та TimeZone — обов'язкові властивості для коректного відображення: для UI — системна локаль, для API — en_US_POSIX та UTC.
  • ISO8601DateFormatter — переважний вибір для серіалізації дат в API завдяки швидкості та стабільності.
  • DateFormatter не потокобезпечний — використовуйте окремі екземпляри для кожного потоку або синхронізуйте доступ.
  • RelativeDateFormatter (iOS 15+) — оптимальне рішення для відображення відносних дат у новинних стрічках та месенджерах.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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