Instant — незмінний клас із пакету java.time, що представляє точку на часовій шкалі в UTC з точністю до наносекунди. На відміну від LocalDateTime, Instant не містить дати та часу в людиночитаному форматі — це машинне представлення моменту. За даними специфікації Oracle Java 17 (2024), Instant спроєктований для машинного обміну позначками часу та є аналогом System.currentTimeMillis(), але з наносекундною точністю.
Головне
Instant — це клас, що моделює одиночну точку на часовій шкалі. Його внутрішнє представлення складається з двох полів: long seconds (кількість секунд від 1970-01-01T00:00:00Z) та int nanos (наносекунди в межах поточної секунди, від 0 до 999999999).
Діапазон значень Instant — від -31557014167219200 до 31556889864403199 секунд від епохи, що покриває приблизно 292 мільйони років в обидві сторони. Цього достатньо для будь-яких практичних завдань, включаючи астрономічні розрахунки.
За даними Baeldung (2024), Instant — це міст між людиночитаними типами (LocalDateTime, ZonedDateTime) та машинними форматами (timestamp у мілісекундах). Instant використовується для логування, кешування, синхронізації та всіх завдань, де важливий абсолютний момент часу.
Клас реалізує інтерфейси Comparable (для порівняння моментів) та Temporal (для використання в загальному API java.time). Instant незмінний — всі методи повертають новий об'єкт.
До Java 8 для роботи з моментами часу використовувалися java.util.Date та System.currentTimeMillis(). Обидва підходи мають недоліки. Date змінний, не thread-safe, зберігає час у мілісекундах від епохи, але назви методів застарілі (getYear() повертає 116 для 2016).
Long (простий timestamp) швидкий та компактний, але не має вбудованої підтримки наносекунд, не відображається в читаному вигляді та потребує ручного парсингу при налагодженні. Long-підхід також не розрізняє тип даних — розробник може передати невірне значення.
Instant вирішує всі ці проблеми. Він незмінний, містить явну інформацію про точність (секунди + наносекунди), серіалізується в ISO-8601 формат «2026-07-21T15:00:00Z» та має багатий API для перетворень. За даними SonarSource (2024), Instant є рекомендованою заміною Date у всіх нових проєктах.
Поточний момент отримується через Instant.now(). На відміну від LocalDateTime.now(), Instant.now() завжди повертає час в UTC, ігноруючи часовий пояс пристрою. Це робить його ідеальним для серверних позначок часу.
З існуючих значень: Instant.ofEpochSecond(long epochSecond) — із секунд від епохи, Instant.ofEpochMilli(long epochMilli) — із мілісекунд, Instant.parse(CharSequence) — із рядка ISO-8601 («2026-07-21T15:00:00Z»).
Для читання: getEpochSecond() — кількість секунд від епохи, toEpochMilli() — кількість мілісекунд, getNano() — наносекунди. Метод toString() повертає рядок у форматі ISO-8601.
val now = Instant.now()
val fromSeconds = Instant.ofEpochSecond(1784700000)
val fromMillis = Instant.ofEpochMilli(1784700000000)
val parsed = Instant.parse("2026-07-21T15:00:00Z")
val epochSecond = now.getEpochSecond()
val epochMilli = now.toEpochMilli()
val nanos = now.getNano()
Instant перетворюється на ZonedDateTime через atZone(ZoneId). Наприклад, Instant.now().atZone(ZoneId.of(«Europe/Moscow»)) поверне ZonedDateTime для Москви. Без зони перетворення неможливе — Instant не містить календарної інформації.
У LocalDateTime Instant перетворюється через atZone(ZoneId).toLocalDateTime(). Цей спосіб явний і не втрачає інформацію. Зворотне перетворення — LocalDateTime.atZone(ZoneId).toInstant().
Для сумісності з java.util.Date: Date.from(instant) та date.toInstant(). Це двоспрямоване перетворення, що зберігає точність до мілісекунд (Date не підтримує наносекунди). Для роботи з java.sql.Timestamp використовується Timestamp.from(instant) з підтримкою наносекунд.
val instant = Instant.now()
val zoned = instant.atZone(ZoneId.of("Europe/Moscow"))
val localDateTime = instant
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
val oldDate = Date.from(instant)
val backToInstant = oldDate.toInstant()
Ключова особливість Instant — він повністю незалежний від часових поясів. Instant.now() повертає однаковий результат на будь-якому пристрої в будь-якій точці світу. Це досягається за рахунок фіксації часу в UTC.
Часовий пояс потрібен тільки для відображення Instant людині. Для цього використовується atZone(ZoneId). ZoneId.systemDefault() повертає часовий пояс пристрою, встановлений в операційній системі. ZoneOffset.UTC — константа для UTC.
У розподілених системах рекомендується зберігати та передавати всі позначки часу в Instant (або OffsetDateTime з ZoneOffset.UTC). Перетворення в локальний час виконується тільки на клієнті перед відображенням користувачеві. Це запобігає плутанині з часовими поясами.
У розподілених Android-додатках синхронізація часу критична для коректної роботи кешування, сповіщень та спільного редагування. Instant — природний вибір для цього завдання завдяки прив'язці до UTC.
При порівнянні позначок часу від різних пристроїв потрібно враховувати, що системні годинники можуть розходитися. Рекомендується використовувати серверний час як еталон. Сервер повертає Instant в UTC, клієнт порівнює з локальним Instant тільки для відносних розрахунків.
Для розрахунку різниці між двома моментами використовується Duration.between(Instant start, Instant end). Цей метод повертає Duration — тривалість, яку можна конвертувати в години, хвилини, секунди. Методи isAfter() та isBefore() дозволяють порівнювати моменти.
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
Перший приклад — логування подій з позначкою часу. Instant зберігається в базу даних Room і передається на сервер. Позначка часу логується в UTC для однозначної інтерпретації.
data class EventLog(
val id: Long = 0,
val eventName: String,
val timestamp: Instant
)
class Converters {
@TypeConverter
fun fromInstant(value: Instant?): Long? {
return value?.toEpochMilli()
}
@TypeConverter
fun toInstant(value: Long?): Instant? {
return value?.let { Instant.ofEpochMilli(it) }
}
}
Другий приклад — визначення часу, що минув з моменту події. Використовуємо Duration.between для відображення «5 хвилин тому», «2 години тому» — формату, поширеного в месенджерах та соцмережах.
fun timeAgo(instant: Instant): String {
val duration = Duration.between(instant, Instant.now())
return when {
duration.toMinutes() < 1 -> "just now"
duration.toHours() < 1 -> "${duration.toMinutes()} min ago"
duration.toDays() < 1 -> "${duration.toHours()} h ago"
else -> "${duration.toDays()} d ago"
}
}
Третій приклад — синхронізація даних між сервером та клієнтом. Використовуємо Instant для відстеження часу останнього оновлення.
class SyncManager {
private var lastSyncAt: Instant? = null
fun sync() {
val syncStart = Instant.now()
// server request with lastSyncAt
lastSyncAt = syncStart
}
fun shouldSync(intervalMinutes: Long): Boolean {
val last = lastSyncAt ?: return true
return Duration.between(last, Instant.now())
.toMinutes() >= intervalMinutes
}
}
Перша помилка — використання Instant.now().toString() для відображення користувачеві. Instant виводиться в UTC-форматі «2026-07-21T15:00:00Z», який нечитабельний для людини. Завжди перетворюйте Instant через atZone() в локальний часовий пояс перед відображенням.
Друга помилка — втрата наносекунд при перетворенні в java.util.Date. Date підтримує тільки мілісекунди. Якщо Instant має наносекунди, вони будуть відкинуті при Date.from(instant). Використовуйте Instant.truncatedTo(ChronoUnit.MILLIS) для явного зазначення точності.
Третя помилка — плутанина між toEpochMilli() та getEpochSecond(). toEpochMilli() повертає кількість мілісекунд від епохи (long), а getEpochSecond() — кількість секунд (long). Переплутавши ці методи, можна отримати помилку в 1000 разів.
Четверта помилка — припущення, що Instant.now() на всіх пристроях синхронізований. Системні годинники можуть відрізнятися на хвилини і навіть години. Для критичних до часу операцій (автентифікація, платежі) використовуйте серверний Instant як джерело істини.
Часті запитання
System.currentTimeMillis() повертає long — кількість мілісекунд від епохи без прив'язки до часового поясу. Instant надає той самий функціонал, але з наносекундною точністю та багатим API для перетворень, порівнянь і сумісності з java.time.
Room не підтримує Instant напряму. Використовуйте TypeConverter, який перетворює Instant в Long (toEpochMilli) і назад (Instant.ofEpochMilli). Для наносекундної точності зберігайте два поля: епоха-секунди та наносекунди.
Так, Instant незмінний і коректно реалізує equals() та hashCode(). Два Instant з однаковим значенням будуть рівні. Це робить його надійним ключем для HashMap та інших колекцій, на відміну від змінного java.util.Date.
Використовуйте Duration.between(start, end) для отримання Duration або ChronoUnit.SECONDS.between(start, end) для різниці в секундах (long). Duration надає методи toMinutes(), toHours(), toDays() та toNanos().
Instant спроєктований як абсолютна точка на шкалі часу. Без зазначення часового поясу або UTC парсинг неможливий, оскільки Instant не містить календарної інформації. Суфікс «Z» позначає нульове зміщення (UTC) і обов'язковий для ISO-8601 формату.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також