Data i czas w rozwoju mobilnym — jeden z kluczowych tematów w inżynierii mobilnej. iOS używa DateFormatter i ISO8601DateFormatter, Android używa DateTimeFormatter i LocalDate z java.time. Według Android Developer Docs, prawidłowy wybór mobilnego API do formatowania daty i czasu bezpośrednio wpływa na wydajność i poprawność wyświetlania w różnych strefach czasowych.
Najważniejsze
Na iOS data i czas są tradycyjnie formatowane przez DateFormatter z Foundation. Ta klasa konwertuje Date na ciąg znaków i z powrotem, używając określonego wzorca, ustawień regionalnych i strefy czasowej. Główny problem DateFormatter — nie jest bezpieczny wątkowo i jest wolny w tworzeniu, więc w kodzie mobilnym instancje muszą być buforowane. Data w rozwoju mobilnym na iOS wymaga odpowiedniego formatera.
Utworzenie DateFormatter zajmuje około 1 milisekundy z powodu parsowania wzorca dateFormat, ładowania ustawień regionalnych i określania strefy czasowej. Podczas formatowania listy setek dat wielokrotne tworzenie prowadzi do zauważalnych opóźnień w interfejsie użytkownika. Rozwiązanie — utwórz formater raz dla całego ekranu i używaj go ponownie. Do dostępu wielowątkowego używaj synchronizacji przez DispatchQueue.
import Foundation
// Zbuforowany DateFormatter — tworzony raz
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
return formatter
}()
// ISO8601DateFormatter — lekka alternatywa
private let isoFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()
let now = Date()
let formattedDate = dateFormatter.string(from: now)
// "01.07.2026 14:30"
let isoDate = isoFormatter.string(from: now)
// "2026-07-01T14:30:00.000Z"
Dla formatu ISO 8601 używaj ISO8601DateFormatter — nie wymaga określania wzorca i działa szybciej niż DateFormatter. Na iOS 10+ ta klasa jest preferowana do pracy z datami serwerowymi. Dla starych formatów pozostaw DateFormatter, ale pamiętaj o buforowaniu instancji jako statycznej właściwości lub singletona.
Na Androidzie data i czas są obsługiwane przez pakiet java.time, dostępny od API 26. Główne klasy: LocalDate (tylko data), LocalTime (tylko czas), LocalDateTime (data i czas bez strefy czasowej) i ZonedDateTime (ze strefą czasową). DateTimeFormatter — bezpieczny wątkowo formater dla platformy mobilnej Android. Data w aplikacjach mobilnych jest formatowana przez DateTimeFormatter i LocalDate bez utraty wydajności.
LocalDate przechowuje datę bez czasu — idealne do urodzin. ZonedDateTime przechowuje pełne informacje ze strefą czasową. DateTimeFormatter obsługuje predefiniowane wzorce (ISO_LOCAL_DATE, ISO_DATE_TIME) i niestandardowe przez ofPattern. Wszystkie klasy java.time są niezmienne i bezpieczne wątkowo, co eliminuje warunki wyścigu podczas pracy w wielu wątkach.
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.ZoneId
// Tworzenie i formatowanie bieżącej daty
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatted = today.format(formatter)
// Praca ze strefami czasowymi
val utcTime = ZonedDateTime.now(ZoneId.of("UTC"))
val moscowTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Moscow"))
DateTimeFormatter jest znacznie wydajniejszy niż SimpleDateFormat, ponieważ nie parsuje wzorca przy każdym wywołaniu. Zaleca się tworzenie stałych formatera na poziomie klasy lub obiektu towarzyszącego. Do konwersji Unix Timestamp używaj Instant.ofEpochSecond, a następnie konwertuj na LocalDateTime lub ZonedDateTime przez strefę czasową.
Prawidłowa data i czas na ekranie użytkownika zależą od poprawnego obsługiwania stref czasowych. Data i czas w rozwoju mobilnym dla Androida wymaga uwzględnienia strefy czasowej urządzenia. Złota zasada: przechowuj czas w UTC, konwertuj do lokalnej strefy czasowej tylko do wyświetlania. iOS udostępnia TimeZone z Foundation, Android udostępnia ZoneId z java.time. Oba API dla platform mobilnych obsługują automatyczne wykrywanie bieżącej strefy czasowej urządzenia. Data w aplikacji mobilnej zależy od ZoneId i TimeZone.
Czas letni tworzy niejednoznaczność: jeden moment w czasie może być reprezentowany przez dwa różne lokalne czasy. java.time i Foundation obsługują DST automatycznie. Używaj identyfikatorów IANA (Europe/Moscow, America/New_York), a nie skrótów (MSK, EST) — skróty są niejednoznaczne i ignorują przejście na czas letni.
import Foundation
// Konwersja UTC na czas lokalny na iOS
func formatToLocalTime(utcDate: Date, timeZoneId: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
formatter.timeZone = TimeZone(identifier: timeZoneId)
return formatter.string(from: utcDate)
}
// Określanie strefy czasowej urządzenia
let localTimeZone = TimeZone.current
let isDST = localTimeZone.isDaylightSavingTime()
let secondsFromGMT = localTimeZone.secondsFromGMT()
Na Androidzie używaj ZoneId.systemDefault() aby uzyskać strefę czasową urządzenia. Podczas konwersji UTC na czas lokalny twórz ZonedDateTime z ZoneOffset.UTC i stosuj withZoneSameInstant. Dla starych API bez java.time używaj TimeZone.getDefault() i Calendar — ale lepiej od razu dodać ThreeTenABP.
import java.time.Instant
import java.time.ZonedDateTime
import java.time.ZoneId
// Konwertuj Unix Timestamp na czas lokalny
fun toLocalDisplay(unixSeconds: Long): String {
val instant = Instant.ofEpochSecond(unixSeconds)
val local = instant.atZone(ZoneId.systemDefault())
return local.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
}
ThreeTenABP — biblioteka adaptera, która przenosi java.time na Android API poniżej 26. Zapewnia te same klasy: LocalDate, LocalTime, ZonedDateTime, DateTimeFormatter, Instant. Jeśli aplikacja obsługuje Androida 5 lub 6 (API 21-25), ThreeTenABP to jedyny sposób na używanie nowoczesnego mobilnego API bez przechodzenia na przestarzały Calendar. Data i czas w rozwoju mobilnym dla starych urządzeń są zaimplementowane przez tę bibliotekę. Dla starych urządzeń data i czas są zaimplementowane przez ThreeTenABP.
Biblioteka wymaga inicjalizacji w metodzie Application.onCreate() przez wywołanie AndroidThreeTen.init(). To ładuje dane stref czasowych z assets. Po inicjalizacji API jest w pełni identyczne z java.time — kod dla API 26+ i dla API 21+ będzie taki sam. Rozmiar biblioteki wynosi około 400 KB, co jest akceptowalne dla większości aplikacji mobilnych.
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
// Inicjalizacja w Application
class App : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
}
}
// API jest identyczne z java.time
val today = LocalDate.now()
val formatted = today.format(DateTimeFormatter.ISO_LOCAL_DATE)
Wydajność ThreeTenABP jest porównywalna z oryginalnym java.time na nowoczesnych urządzeniach. Na starych urządzeniach możliwe jest opóźnienie przy pierwszym wywołaniu z powodu ładowania danych stref czasowych. Migruj istniejący kod z Calendar do ThreeTenABP stopniowo: zastępuj Date i Calendar Instant i LocalDate w nowych modułach, pozostawiając stary kod bez zmian.
Unix Timestamp — liczba sekund, które upłynęły od 1 stycznia 1970 00:00 UTC. To uniwersalny format przechowywania i wymiany czasu między serwerem a klientem. Jednak czas na urządzeniu może różnić się od rzeczywistego z powodu ustawień użytkownika. Do synchronizacji używany jest NTP — protokół precyzyjnego czasu przez UDP. Unix Timestamp to format, w którym data i czas są przechowywane na serwerze.
Date().timeIntervalSince1970 na iOS zwraca sekundy od 1970 roku. Na Androidzie System.currentTimeMillis() zwraca milisekundy. Dla dokładności używaj NTP: biblioteka TrueTime dla iOS i AndroidNtp dla Androida wysyłają zapytanie do serwera NTP i obliczają przesunięcie czasu. Dokładność NTP wynosi 1-10 ms w sieci lokalnej i 10-100 ms przez internet.
import Foundation
// Pobieranie Unix Timestamp na iOS
let seconds = Date().timeIntervalSince1970
let milliseconds = Int64(seconds * 1000)
// Synchronizacja NTP z TrueTime
TrueTime.shared.start { result in
switch result {
case .success:
let ntpTime = TrueTime.shared.now()
print("Dokładny czas: \(ntpTime)")
case .failure(let error):
print("Błąd NTP: \(error)")
}
}
Bez synchronizacji w aplikacji mobilnej możliwe są błędy: nieprawidłowe znaczniki czasu zdarzeń, desynchronizacja z serwerem. Czas w rozwoju mobilnym jest synchronizowany przez NTP jednorazowo przy starcie aplikacji, po czym używane jest obliczone przesunięcie. Dla krytycznych pod względem dokładności aplikacji mobilnych (finanse, logistyka) protokół NTP jest obowiązkowy przed wysłaniem danych na serwer.
Często zadawane pytania
DateFormatter parsuje wzorzec formatowania, ładuje ustawienia regionalne i określa strefę czasową przy każdym tworzeniu instancji. Buforuj formater jako stałą statyczną lub używaj ISO8601DateFormatter dla ISO 8601 — jest lżejszy i szybszy.
DateTimeFormatter z pakietu java.time — bezpieczny wątkowo i wydajny. Dostępny od API 26. Dla starszych wersji używaj ThreeTenABP, który zapewnia identyczne API.
Dodaj ThreeTenABP — backport java.time dla Android API 21+. Inicjalizacja przez AndroidThreeTen.init() w Application.onCreate(). API jest w pełni identyczne z oryginalnym java.time.
Tak, przechowuj czas w UTC na serwerze i w lokalnej bazie danych. Konwertuj do lokalnej strefy czasowej użytkownika tylko na etapie wyświetlania. Eliminuje to błędy przy zmianie strefy czasowej lub podróżach.
Date().timeIntervalSince1970 zwraca liczbę sekund od 1 stycznia 1970 roku. Dla milisekund pomnóż przez 1000 i przekonwertuj na Int64. Do precyzyjnej synchronizacji używaj biblioteki TrueTime z serwerem NTP.
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.