ISO8601DateFormatter — to klasa Foundation w iOS i macOS, przeznaczona do formatowania i parsowania dat w międzynarodowym standardzie ISO 8601. Według Apple Developer Documentation, 2024, ISO8601DateFormatter automatycznie obsługuje formaty z milisekundami, strefami czasowymi i ułamkami sekund bez konieczności ręcznego ustawiania DateFormat. W przeciwieństwie do DateFormatter, ta klasa nie zależy od Locale i TimeZone — działa ściśle według specyfikacji ISO 8601, co czyni ją idealną do wymiany dat między serwerem a klientem. Klasa jest dostępna od iOS 10 i macOS 10.12.
Najważniejsze
ISO8601DateFormatter — to wyspecjalizowana podklasa Formatter w Foundation, implementująca dwukierunkową konwersję między Date a ciągiem znaków w formacie ISO 8601. Standard ISO 8601 (International Standard for the Representation of Dates and Times) określa międzynarodowy format wymiany dat i czasu: 2024-07-21T14:30:00+00:00. W przeciwieństwie do DateFormatter, ta klasa nie wymaga podawania dateFormat i automatycznie określa strukturę ciągu na podstawie zadanych opcji.
Główne zalety ISO8601DateFormatter w porównaniu z DateFormatter: brak zależności od locale (parsowanie działa tak samo na każdym urządzeniu), wbudowana obsługa ułamków sekund (z dowolną liczbą znaków po przecinku) i automatyczne określanie formatu na podstawie przekazanych opcji. Klasa poprawnie obsługuje również sufiks Z (oznaczenie UTC), strefy czasowe w formacie +HH:mm oraz zmniejszoną precyzję (tylko data bez czasu).
Według ISO Specification (ISO 8601-1:2019), standard obsługuje cztery poziomy precyzji: rok (2024), rok-miesiąc (2024-07), pełna data (2024-07-21) i data-czas ze strefą czasową (2024-07-21T14:30:00+00:00). ISO8601DateFormatter pokrywa wszystkie te poziomy poprzez kombinację opcji formatu, zwalniając programistę z ręcznego konstruowania ciągu dateFormat.
Zasada działania ISO8601DateFormatter opiera się na kombinacji opcji bitowych (formatOptions), z których każda włącza określony komponent daty lub czasu w wyniku. Na przykład opcja .withFullDate włącza rok, miesiąc i dzień; .withTime — godziny, minuty i sekundy. Łącząc opcje, programista uzyskuje potrzebny poziom precyzji bez pisania ciągu dateFormat.
Wewnętrznie ISO8601DateFormatter używa biblioteki ICU do parsowania, ale ze stałymi regułami ISO 8601. Oznacza to, że ignoruje ustawienia Locale i TimeZone zainstalowane na urządzeniu — wynik jest zawsze przewidywalny. Do ustawienia strefy czasowej służy właściwość timeZone, która domyślnie równa się UTC. Jeśli timeZone jest ustawiony na nil, używany jest lokalny czas urządzenia.
| Opcja | Opis | Przykład wyniku |
|---|---|---|
| .withFullDate | Rok, miesiąc, dzień | 2024-07-21 |
| .withTime | Godziny, minuty, sekundy | 14:30:00 |
| .withMilliseconds | Ułamki sekund (do 3 znaków) | .123 |
| .withFractionalSeconds | Ułamki sekund (dowolna precyzja) | .123456 |
| .withTimeZone | Strefa czasowa | +03:00 |
| .withColonSeparatorInTimeZone | Separator : w strefie czasowej | +03:00 (zamiast +0300) |
| .withInternetDateTime | Pełny format (date + time + tz) | 2024-07-21T14:30:00+00:00 |
Łączenie opcji: .withInternetDateTime jest równoważny połączeniu .withFullDate, .withTime i .withTimeZone. Do parsowania ciągów z milisekundami dodaj .withFractionalSeconds. Ważne jest, aby pamiętać, że .withMilliseconds ogranicza ułamki sekund do trzech znaków, a .withFractionalSeconds obsługuje dowolną precyzję — od jednej do dziewięciu cyfr po przecinku.
Opcje formatu ISO8601DateFormatter dzielą się na trzy grupy: komponenty daty (withFullDate, withYear, withMonth, withDay, withWeekOfYear), komponenty czasu (withTime, withHours, withMinutes, withSeconds) i dodatkowe ustawienia (withMilliseconds, withFractionalSeconds, withTimeZone, withColonSeparatorInTimeZone, withDashSeparatorInDate, withFullTime). Łącząc je, można uzyskać praktycznie dowolny podformat ISO 8601.
Ważny niuans: .withFractionalSeconds i .withMilliseconds wzajemnie się wykluczają — jeśli ustawione są obie, stosowana jest .withFractionalSeconds. Do parsowania milisekund z danych serwerowych zaleca się .withFractionalSeconds, ponieważ wiele serwerów wysyła ułamki sekund z trzema, sześcioma lub dziewięcioma znakami, a .withFractionalSeconds obsługuje dowolną długość.
import Foundation
// Konfiguruj ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
// Różne kombinacje opcji formatu
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("Data: \(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("Pełny: \(full)")
// Parsuj string z milisekundami
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
print("Sparsowano: \(parsed)")
}
Podstawowe użycie ISO8601DateFormatter sprowadza się do utworzenia instancji, ustawienia timeZone (zaleca się UTC dla danych serwerowych) i formatOptions, po czym można wywołać string(from:) do formatowania i date(from:) do parsowania. W przeciwieństwie do DateFormatter, nie trzeba martwić się o Locale — klasa ignoruje ustawienia regionalne.
import Foundation
let formatter = ISO8601DateFormatter()
// Parsuj różne formaty ISO 8601
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("Sparsowano '\(str)': \(autoParsed)")
} else {
// Użyj withFullDate dla stringów tylko z datą
formatter.formatOptions = [.withFullDate]
if let fallback = formatter.date(from: str) {
print("Fallback sparsowano '\(str)': \(fallback)")
}
formatter.formatOptions = [.withInternetDateTime]
}
}
// Serializuj do RFC 3339 (API GitHub)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339: \(rfc3339)")
Parsowanie dat z ułamkowymi sekundami o zmiennej długości — cecha wielu nowoczesnych API. Serwer może przesłać zarówno 2024-07-21T14:30:00.123Z (3 znaki), jak i 2024-07-21T14:30:00.123456Z (6 znaków). ISO8601DateFormatter z opcją .withFractionalSeconds poprawnie obsłuży oba warianty, a DateFormatter z dateFormat = „yyyy-MM-dd’T’HH:mm:ss.SSSZ” — tylko trzycyfrowe milisekundy.
import Foundation
let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
// Różna precyzja ułamków sekund
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)")
}
}
// Użyj withMilliseconds (tylko 3 cyfry)
variantFormatter.formatOptions = [
.withInternetDateTime,
.withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("Z milisekundami: \(milliParsed)")
Testowanie parsowania wszystkich wariantów: powyższy kod demonstruje, że ISO8601DateFormatter z .withFractionalSeconds skutecznie przetwarza ułamki sekund o dowolnej długości od 1 do 9 znaków. Jest to ważne dla kompatybilności z różnymi platformami serwerowymi: .NET często generuje 7 znaków (tiki 100-nanosekundowe), Python — 6, Java — 3 lub 9 w zależności od wersji.
DateFormatter również może parsować ISO 8601, ale wymaga ręcznego ustawienia dateFormat, locale i timeZone. Główny problem polega na tym, że DateFormatter zależy od Locale, a jeśli nie ustawisz en_US_POSIX, parsowanie może się zepsuć u użytkowników z regionów o niestandardowych formatach dat. ISO8601DateFormatter rozwiązuje ten problem na poziomie architektury: nie używa Locale.
| Parametr | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| Konfiguracja Locale | Nie wymagana (ignoruje) | Wymagany en_US_POSIX |
| DateFormat | Automatyczny (przez opcje) | Ręczny ciąg format |
| Ułamkowe sekundy | Dowolna precyzja (.withFractionalSeconds) | Stała SSS |
| Sufiks Z | Poprawnie obsługuje | Przez dateFormat |
| Wydajność | Wyższa (wyspecjalizowany) | Niższa (ogólny) |
| Standard | Tylko ISO 8601 | Dowolny format |
| Wersja iOS | iOS 10+ | iOS 2+ |
Kiedy używać DateFormatter: jeśli potrzebujesz sformatować datę w formacie innym niż ISO 8601 (na przykład „21 lipca 2024 r.” dla UI) lub jeśli wymagane jest wsparcie dla iOS 9 i starszych. Do wszystkich zadań wymiany dat z serwerem używaj ISO8601DateFormatter — jest bezpieczniejszy, wydajniejszy i wymaga mniej kodu. DateFormatter dla ISO 8601 to źródło potencjalnych błędów związanych z locale i ustawieniami regionalnymi.
Migracja z DateFormatter na ISO8601DateFormatter: zastąp utworzenie DateFormatter + konfigurację dateFormat + locale + timeZone utworzeniem ISO8601DateFormatter + konfiguracją formatOptions + timeZone. Parsowanie ciągu pozostaje niezmienne przez date(from:). Dla kompatybilności wstecznej można użyć #available(iOS 10, *) z fallback na DateFormatter.
Zapomniane ustawienie formatOptions powoduje, że formatter używa wartości domyślnej — .withInternetDateTime. Jeśli serwer przysyła tylko datę bez czasu (2024-07-21), parsowanie zwróci nil. Zawsze sprawdzaj, czy formatOptions pokrywają wszystkie możliwe formaty, które mogą nadejść z serwera. Dla API ze zmiennymi formatami używaj prób fallback z różnymi kombinacjami opcji.
Mylenie withMilliseconds z withFractionalSeconds — częsty błąd przy parsowaniu dat z ułamkami sekund. withMilliseconds oczekuje dokładnie 3 cyfr po przecinku. Jeśli serwer przysyła 6 cyfr (mikrosekundy), parsowanie z withMilliseconds zakończy się błędem. Używaj .withFractionalSeconds dla kompatybilności z dowolną liczbą znaków. .withFractionalSeconds pojawił się w iOS 13; dla starszych wersji używaj DateFormatter z dateFormat.
Ignorowanie strefy czasowej — kolejny częsty problem. Jeśli serwer przysyła datę ze strefą czasową (+03:00), a formatter jest ustawiony na UTC, parsowanie nie ulegnie awarii, ale wynik będzie w UTC. Deweloperzy często oczekują, że Date zachowa strefę czasową, ale Date to absolutny moment w czasie, nie przechowuje informacji o strefie czasowej. Do poprawnego wyświetlania zapisuj strefę czasową osobno lub używaj ISO8601DateFormatter z prawidłowym timeZone.
Według Apple Forum (2024), około 20% pytań dotyczących ISO8601DateFormatter dotyczy formatu, w którym sekundy są opcjonalne. Standard ISO 8601 dopuszcza format bez sekund: 2024-07-21T14:30+03:00. ISO8601DateFormatter z .withInternetDateTime nie obsługuje tego formatu — do jego parsowania potrzebny będzie DateFormatter z dateFormat = „yyyy-MM-dd’T’HH:mmZ”. To ograniczenie należy uwzględnić podczas pracy z API używającymi skróconego formatu czasu.
Często zadawane pytania
ISO8601DateFormatter — wyspecjalizowana klasa Foundation do formatowania i parsowania dat w formacie ISO 8601, dostępna od iOS 10. Automatycznie obsługuje standardowe formaty bez ręcznego podawania dateFormat.
ISO8601DateFormatter nie zależy od Locale, używa opcji zamiast dateFormat i poprawnie obsługuje ułamki sekund o dowolnej długości. DateFormatter jest uniwersalny, ale wymaga ręcznej konfiguracji i jest podatny na błędy związane z ustawieniami regionalnymi.
Użyj opcji .withFractionalSeconds — obsługuje od 1 do 9 znaków po przecinku. Nie używaj .withMilliseconds, jeśli precyzja może się różnić. .withFractionalSeconds jest dostępny od iOS 13.
Domyślnie UTC. Aby ją zmienić, ustaw właściwość timeZone. Jeśli timeZone = nil, używany jest lokalny czas urządzenia. Podczas parsowania ciągu z jawną strefą czasową w formacie +HH:MM formatter uwzględnia ją automatycznie.
Ponieważ formatOptions domyślnie = .withInternetDateTime, który oczekuje daty + czasu + strefy czasowej. Do parsowania tylko daty ustaw formatOptions = [.withFullDate]. Aby obsłużyć oba warianty, użyj fallback z różnymi opcjami.
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ż