Дата и время в мобильной разработке — одна из ключевых тем в мобильной инженерии. 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 КБ, что приемлемо для большинства мобильных приложений.
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 мс в локальной сети и 10-100 мс через интернет.
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 — бэкпорт 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 года. Мы проконсультируем вас и предложим наилучшее решение.