Дата і час у мобільній розробці — одна з ключових тем у мобільній інженерії. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.