DateFormatter — to klasa Foundation przeznaczona do dwukierunkowej konwersji między obiektami Date a ich reprezentacją tekstową. Klasa uwzględnia ustawienia regionalne, strefę czasową i kalendarz użytkownika, zapewniając poprawne wyświetlanie dat w dowolnym regionie świata. Według Apple Developer Documentation (2025), DateFormatter obsługuje cztery predefiniowane style daty i czasu, a także w pełni niestandardowe formaty poprzez ciąg wzorca. Bez DateFormatter nie jest możliwie poprawne wyświetlenie daty użytkownikowi w zinternacjonalizowanej aplikacji.
Najważniejsze
DateFormatter — to klasa z frameworka Foundation, implementująca dwukierunkową konwersję między Date a tekstem. Pojawiła się w OpenStep jako NSDateFormatter i od tego czasu pozostaje głównym narzędziem formatowania dat na wszystkich platformach Apple. Klasa dziedziczy po Formatter i udostępnia wygodne API do zlokalizowanego wyświetlania dat.
Zasada działania DateFormatter opiera się na wzorcach Unicode LDML — tych samych, które są używane w ICU (International Components for Unicode). Wzorzec jest określany przez właściwość dateFormat, gdzie symbole y, M, d, H, m, s odpowiadają odpowiednio roku, miesiącowi, dniowi, godzinom, minutom i sekundom. Powtórzenie symbolu określa format: „y“ — dwucyfrowy rok, „yyyy“ — czterocyfrowy.
Tworzenie DateFormatter — to kosztowna operacja, ponieważ podczas inicjalizacji ładowane są dane ustawień regionalnych i kalendarza. Apple zaleca tworzenie formattera raz dla każdego typu formatowania i ponowne jego używanie. W SwiftUI i UIKit formattery są często buforowane we właściwościach statycznych lub tworzone leniwie przy pierwszym dostępie.
DateFormatter jest używany w wielu systemowych komponentach iOS. UIDatePicker wewnętrznie korzysta z DateFormatter do wyświetlania dat w trybie countDownTimer. TextField z formatterem na wejściu może automatycznie walidować datę wprowadzaną przez użytkownika. Core Data obsługuje atrybuty typu Date, ale ich tekstowe wyświetlanie zawsze odbywa się przez DateFormatter.
Thread Safety — DateFormatter nie jest bezpieczny wątkowo. Modyfikacja właściwości formattera z różnych wątków prowadzi do nieokreślonego zachowania. Do użytku wielowątkowego twórz osobne instancje formattera dla każdego wątku lub używaj synchronizacji przez NSLock lub serial queue.
dateStyle i timeStyle — to najprostsze sposoby konfiguracji wyświetlania daty. Każdy styl ma cztery warianty: .short, .medium, .long, .full. Kombinacja dateStyle i timeStyle pozwala niezależnie skonfigurować format daty i czasu, a właściwość .none wyłącza odpowiednią część.
Dla ustawień regionalnych USA styl .short formatuje datę jako „7/21/26“, a dla polskich — jako „21.07.2026“. Styl .long dla polskich ustawień regionalnych wyświetla „21 lipca 2026“, a .full — „wtorek, 21 lipca 2026“ z podaniem dnia tygodnia. Wszystkie cztery style automatycznie dostosowują się do standardów regionalnych, w tym kolejności składników i separatorów.
RelativeDateFormatter w iOS 15+ oferuje alternatywne podejście przez RelativeDateFormatter i DateIntervalFormatter. RelativeDateFormatter wyświetla „dzisiaj“, „wczoraj“, „za 3 dni“ dla bieżącego kontekstu. DateIntervalFormatter pokazuje zakresy dat: “21–25 lipca 2026” — dla rezerwacji i planowania.
| Styl | Przykład (pl_PL) | Przykład (en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 21 lip 2026 | Jul 21, 2026 |
| .long | 21 lipca 2026 | July 21, 2026 |
| .full | wtorek, 21 lipca 2026 | Tuesday, July 21, 2026 |
Przy łączeniu stylów DateFormatter automatycznie wybiera separator: dla .short.date + .short.time wynik może brzmieć „21.07.2026, 14:30“. Dla .full.date + .full.time — „wtorek, 21 lipca 2026, 14:30:00 MSK“. Separator jest kontrolowany przez ustawienia regionalne, a nie programistę — gwarantuje to zgodność z oczekiwaniami regionalnymi użytkownika.
dateFormat pozwala określić dowolny wzorzec formatowania przy użyciu symboli specyfikacji Unicode LDML. Daje to pełną kontrolę nad wyświetlaniem: można wyświetlić tylko rok i miesiąc, dzień tygodnia bez liczby, lub czas bez sekund. Niestandardowy format jest niezastąpiony przy specyficznych wymaganiach projektowych.
Podstawowe symbole — yyyy (rok: 2026), MM (miesiąc: 07), dd (dzień: 21), HH (godziny: 14), mm (minuty: 30), ss (sekundy: 00). Do pełnej nazwy miesiąca używaj MMMM (lipiec), do skróconej — MMM (lip). Dzień tygodnia — EEEE (wtorek), skrócony — E (wt).
Przy użyciu dateFormat ważne jest ustawienie locale formattera. Jeśli locale nie jest ustawione, formatter używa systemowych ustawień regionalnych, co może być niepożądane dla stałego formatu w API. Apple zaleca ustawienie locale = Locale(identifier: “en_US_POSIX”) dla stałego międzyregionalnego formatu, szczególnie przy parsowaniu dat z odpowiedzi serwerowych.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// Parsowanie niestandardowego ciągu znaków
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
Błąd w dateFormat — jedna z częstych przyczyn awarii aplikacji. Jeśli format nie pasuje do ciągu, metoda date(from:) zwraca nil. Używaj guard let lub ?? do bezpiecznego wyodrębnienia opcjonalnej wartości. Do walidacji formatu testuj na wszystkich obsługiwanych językach — niektóre symbole LDML działają inaczej w różnych ustawieniach regionalnych.
Locale określa, jak wyświetlane są nazwy miesięcy, dni tygodnia i jakie separatory są używane. DateFormatter domyślnie używa Locale.current, ale w niektórych scenariuszach wymagane jest wskazanie konkretnych ustawień regionalnych: dla stałego formatu w logach używaj en_US_POSIX, dla dat serwerowych — ustawień regionalnych identycznych z serwerem.
Właściwość TimeZone określa strefę czasową dla wyświetlania. Domyślnie używana jest systemowa strefa czasowa, ale w aplikacjach z międzynarodową publicznością często wymagane jest wyświetlanie dat w strefie czasowej użytkownika lub w UTC. Zmiana timeZone wpływa tylko na wyświetlanie — wartość Date pozostaje niezmieniona.
Ważna cecha: jeśli DateFormatter jest używany do parsowania ciągu, a ciąg zawiera oznaczenie strefy czasowej (np. “2026-07-21T14:30:00Z” z Z dla UTC), właściwość timeZone jest ignorowana — formatter używa strefy czasowej z ciągu. Jeśli strefa czasowa w ciągu jest nieobecna, stosowana jest timeZone formattera.
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"
// Parsowanie bez strefy czasowej w ciągu znaków
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — specjalny typ ustawień regionalnych, które automatycznie aktualizują się przy zmianie ustawień systemowych użytkownika. DateFormatter domyślnie go obsługuje. Jeśli aplikacja działa w tle i użytkownik zmieni język systemu, formatter utworzony przed zmianą będzie nadal używać starych ustawień — do aktualizacji należy utworzyć nową instancję.
ISO8601DateFormatter — wyspecjalizowany formatter do pracy z datami w formacie ISO 8601. Ten format jest standardem de facto dla REST API, JSON i wymiany danych. ISO8601DateFormatter działa znacznie szybciej niż DateFormatter, ponieważ nie zależy od ustawień regionalnych i używa stałej gramatyki parsowania.
Podstawowe opcje formattera — .withInternetDateTime (2026-07-21T14:30:00Z), .withFractionalSeconds (dodaje milisekundy), .withTimeZone (zawiera przesunięcie strefy czasowej). Łącząc opcje, można uzyskać dowolny wariant ISO 8601: z milisekundami, ze strefą czasową, z datą bez czasu.
JSONEncoder.DateEncodingStrategy pozwala globalnie skonfigurować kodowanie dat dla wszystkich modeli Codable. Warianty — .iso8601 (używa ISO8601DateFormatter), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970. Wybór strategii wpływa na cały cykl życia serializacji i powinien być jednolity dla wszystkich endpointów API.
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// JSONEncoder z ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// Alternatywa: JSONEncoder z niestandardowym formaterem
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter vs ISO8601DateFormatter — wybieraj ISO8601DateFormatter do serializacji i parsowania dat w API, ponieważ działa 5-10 razy szybciej niż DateFormatter i nie podlega błędom lokalizacji. DateFormatter pozostaw do interfejsu użytkownika, gdzie wymagane jest zlokalizowane wyświetlanie z nazwami miesięcy i dni tygodnia w języku ojczystym użytkownika.
Rozważmy rzeczywiste scenariusze użycia DateFormatter w aplikacji iOS: wyświetlanie na liście wiadomości, wprowadzanie daty urodzenia i eksport raportu z datami w różnych strefach czasowych.
RelativeDateFormatter jest optymalny dla kanałów informacyjnych. Wyświetla „przed chwilą“, „5 minut temu“, „wczoraj“ dla świeżych wiadomości i przełącza się na pełną datę dla starszych. Próg przełączenia jest konfigurowany przez calendar: dla wiadomości użyj progu 24 godzin, dla komunikatorów — tygodnia.
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)
}
Wprowadzanie daty urodzenia — kolejny częsty scenariusz. DateFormatter jest konfigurowany z konkretnym dateFormat „dd.MM.yyyy“ i ustawieniami regionalnymi „pl_PL“. Przy parsowaniu wprowadzonego ciągu ważne jest obsłużenie możliwych błędów: formatter zwraca nil dla nieprawidłowego ciągu. Po pomyślnym parsowaniu data jest sprawdzana pod kątem mieszczenia się w dozwolonym zakresie — nie wcześniej niż 1900 rok, nie później niż dzisiaj.
Eksport raportu z datami wymaga stałego formatu niezależnego od ustawień regionalnych użytkownika. Używaj dateFormat „yyyy-MM-dd HH:mm:ss“ z ustawieniami regionalnymi en_US_POSIX i strefą czasową UTC. Takie podejście gwarantuje, że plik otworzy się poprawnie w każdym kraju niezależnie od regionalnych ustawień systemu.
| Scenariusz | Format | Kluczowe ustawienie |
|---|---|---|
| Kanał informacyjny | RelativeDateFormatter | unitsStyle = .full |
| Wprowadzanie daty | DateFormatter | dateFormat + fallback |
| Serializacja API | ISO8601DateFormatter | withInternetDateTime |
| Eksport raportu | DateFormatter | en_US_POSIX + UTC |
Często zadawane pytania
Najczęstsza przyczyna — niezgodność dateFormat z formatem ciągu. Na przykład format „dd.MM.yyyy“ nie sparsuje ciągu „2026-07-21“. Drugi powód — niezgodność ustawień regionalnych: ciąg „July 21, 2026“ nie sparsuje się z ustawieniami pl_PL. Trzeci — literówki w symbolach LDML: używaj yyyy, a nie YYYY (różne znaczenie).
Nie. DateFormatter — ciężki obiekt, jego inicjalizacja obejmuje ładowanie danych ustawień regionalnych. Twórz jedną instancję na typ formatowania i używaj jej wielokrotnie. W środowisku wielowątkowym używaj Thread-local storage lub puli formatterów z serial queue do synchronizacji.
DateFormatter wyświetla datę bezwzględną (21 lipca 2026), a RelativeDateFormatter — względną (dzisiaj, wczoraj, za 3 dni). RelativeDateFormatter pojawił się w iOS 15+ i używa tego samego wzorca LDML, ale automatycznie dobiera wyświetlanie względne.
Ustaw timeZone formattera na UTC przed parsowaniem. Jeśli serwer zwraca datę w czasie lokalnym bez oznaczenia strefy, sprawdź specyfikację API — najprawdopodobniej chodzi o UTC. Dla ISO 8601 z Z na końcu timeZone nie jest potrzebny — formatter parsuje przesunięcie z ciągu.
Nie używaj jednej instancji z różnych wątków bez synchronizacji. Twórz nową instancję w każdym wątku lub używaj Thread.current.threadDictionary do przechowywania. Alternatywa — NSLock z blokadą na czas string(from:) i date(from:).
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również