Датата и часът в мобилната разработка — една от ключовите теми в мобилното инженерство. iOS използва DateFormatter и ISO8601DateFormatter, Android — DateTimeFormatter и LocalDate от java.time. Според Android Developer Docs, правилният избор на мобилно API за форматиране на дата и час пряко влияе върху производителността и коректното показване в различни часови зони.
Основни неща
На iOS датата и часът традиционно се форматират чрез DateFormatter от Foundation. Този клас преобразува Date в низ и обратно, използвайки зададен шаблон, локал и часова зона. Основният проблем на DateFormatter — не е нишково безопасен и се създава бавно, затова в мобилния код инстанциите задължително се кешират. Датата в мобилната разработка на iOS изисква правилен форматиращ елемент.
Създаването на DateFormatter отнема около 1 милисекунда поради парсване на шаблона dateFormat, зареждане на локала и определяне на часовата зона. При форматиране на списък от стотици дати многократното създаване води до забележими забавяния в UI. Решението — създайте форматиращия елемент веднъж за целия екран и го използвайте повторно. За многонишков достъп използвайте синхронизация чрез DispatchQueue.
import Foundation
// Кеширан DateFormatter — създава се веднъж
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 — лека алтернатива
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"
За формат ISO 8601 използвайте ISO8601DateFormatter — той не изисква посочване на шаблон и работи по-бързо от DateFormatter. На iOS 10+ този клас е за предпочитане за работа със сървърни дати. За стари формати оставете DateFormatter, но задължително кеширайте инстанцията като статично свойство или сингълтън.
На Android датата и часът се обработват чрез пакета java.time, достъпен от API 26. Основни класове: LocalDate (само дата), LocalTime (само час), LocalDateTime (дата и час без часова зона) и ZonedDateTime (с часова зона). DateTimeFormatter — нишково безопасен форматиращ елемент за мобилната платформа Android. Датата в мобилните приложения се форматира чрез DateTimeFormatter и LocalDate без загуба на производителност.
LocalDate съхранява дата без време — идеално за рождени дни. ZonedDateTime съхранява пълна информация с часова зона. DateTimeFormatter поддържа предефинирани шаблони (ISO_LOCAL_DATE, ISO_DATE_TIME) и персонализирани чрез ofPattern. Всички класове на java.time са неизменяеми и нишково безопасни, което елиминира race conditions при работа в няколко нишки.
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.ZoneId
// Създаване и форматиране на текущата дата
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatted = today.format(formatter)
// Работа с часови зони
val utcTime = ZonedDateTime.now(ZoneId.of("UTC"))
val moscowTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Moscow"))
DateTimeFormatter е значително по-производителен от SimpleDateFormat, тъй като не парсва шаблона при всяко извикване. Препоръчва се да създавате константи на форматиращите елементи на ниво клас или компаньон обект. За конвертиране на Unix Timestamp използвайте Instant.ofEpochSecond, след това преобразувайте в LocalDateTime или ZonedDateTime чрез часова зона.
Коректната дата и час на екрана на потребителя зависят от правилното обработване на часовите зони. Датата и часът в мобилната разработка за Android изисква отчитане на часовата зона на устройството. Златното правило: съхранявайте времето в UTC, конвертирайте в локална часова зона само за показване. iOS предоставя TimeZone от Foundation, Android — ZoneId от java.time. И двете API за мобилни платформи поддържат автоматично определяне на текущата часова зона на устройството. Датата в мобилното приложение зависи от ZoneId и TimeZone.
Лятното часово време създава нееднозначност: един момент от времето може да бъде представен от два различни местни часа. java.time и Foundation отчитат DST автоматично. Използвайте IANA идентификатори (Europe/Moscow, America/New_York), а не съкращения (MSK, EST) — съкращенията са нееднозначни и игнорират прехода към лятно часово време.
import Foundation
// Конвертиране от UTC в локално време на 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)
}
// Определяне на часовата зона на устройството
let localTimeZone = TimeZone.current
let isDST = localTimeZone.isDaylightSavingTime()
let secondsFromGMT = localTimeZone.secondsFromGMT()
На Android използвайте ZoneId.systemDefault() за получаване на часовата зона на устройството. При конвертиране от UTC в локално време създавайте ZonedDateTime с ZoneOffset.UTC и приложете withZoneSameInstant. За стари API без java.time използвайте TimeZone.getDefault() и Calendar — но по-добре веднага подключете ThreeTenABP.
import java.time.Instant
import java.time.ZonedDateTime
import java.time.ZoneId
// Конвертиране на Unix Timestamp в локално време
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 — библиотека-адаптер, пренасяща java.time на Android API под 26. Тя предоставя същите класове: LocalDate, LocalTime, ZonedDateTime, DateTimeFormatter, Instant. Ако приложението поддържа Android 5 или 6 (API 21-25), ThreeTenABP е единственият начин да използвате модерно мобилно API без преминаване към остарелия Calendar. Датата и часът в мобилната разработка за стари устройства са реализирани чрез тази библиотека. За стари устройства датата и часът са реализирани чрез ThreeTenABP.
Библиотеката изисква инициализация в метода Application.onCreate() с извикване на AndroidThreeTen.init(). Това зарежда данните за часовите зони от assets. След инициализация API е напълно идентичен с java.time — кодът за API 26+ и за API 21+ ще бъде еднакъв. Размерът на библиотеката е около 400 KB, което е приемливо за повечето мобилни приложения.
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
// Инициализация в Application
class App : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
}
}
// API е идентичен с java.time
val today = LocalDate.now()
val formatted = today.format(DateTimeFormatter.ISO_LOCAL_DATE)
Производителността на ThreeTenABP е сравнима с оригиналния java.time на съвременни устройства. На стари устройства е възможно забавяне при първото извикване поради зареждане на данните за часовите зони. Мигрирайте съществуващия код от Calendar към ThreeTenABP постепенно: заменяйте Date и Calendar с Instant и LocalDate в нови модули, оставяйки стария код без промени.
Unix Timestamp — броят секунди, изминали от 1 януари 1970 г. 00:00 UTC. Това е универсален формат за съхранение и обмен на време между сървър и клиент. Времето на устройството обаче може да се различава от реалното поради настройките на потребителя. За синхронизация се използва NTP — протокол за точно време чрез UDP. Unix Timestamp е форматът, в който се съхраняват датата и часът на сървъра.
Date().timeIntervalSince1970 на iOS връща секунди от 1970 г. На Android System.currentTimeMillis() връща милисекунди. За точност използвайте NTP: библиотеката TrueTime за iOS и AndroidNtp за Android изпращат заявка към NTP сървър и изчисляват времевото отместване. Точността на NTP е 1-10 ms в локална мрежа и 10-100 ms през интернет.
import Foundation
// Получаване на Unix Timestamp на iOS
let seconds = Date().timeIntervalSince1970
let milliseconds = Int64(seconds * 1000)
// NTP синхронизация с TrueTime
TrueTime.shared.start { result in
switch result {
case .success:
let ntpTime = TrueTime.shared.now()
print("Точно време: \(ntpTime)")
case .failure(let error):
print("NTP грешка: \(error)")
}
}
Без синхронизация в мобилното приложение са възможни грешки: неправилно отбелязване на събития, десинхронизация със сървъра. Времето в мобилната разработка се синхронизира чрез NTP еднократно при стартиране на приложението, след което се използва изчисленото отместване. За критични към точността мобилни приложения (финанси, логистика) протоколът NTP е задължителен преди изпращане на данни към сървъра.
Често задавани въпроси
DateFormatter парсва шаблона за форматиране, зарежда локала и определя часовата зона при всяко създаване на инстанция. Кеширайте форматиращия елемент като статична константа или използвайте ISO8601DateFormatter за ISO 8601 — той е по-лек и по-бърз.
DateTimeFormatter от пакета java.time — нишково безопасен и производителен. Достъпен от API 26. За стари версии използвайте ThreeTenABP, който предоставя идентично API.
Подключете ThreeTenABP — backport на java.time за Android API 21+. Инициализация чрез AndroidThreeTen.init() в Application.onCreate(). API е напълно идентичен с оригиналния java.time.
Да, съхранявайте времето в UTC на сървъра и в локалната база данни. Конвертирайте в локалната часова зона на потребителя само на етапа на показване. Това изключва грешки при смяна на часовата зона или пътувания.
Date().timeIntervalSince1970 връща броя секунди от 1 януари 1970 г. За милисекунди умножете по 1000 и приведете към Int64. За точна синхронизация използвайте библиотеката TrueTime с NTP сървър.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.