Класс java.time.Instant в Java: что это и как использовать
Строка DateTimeFormatter.ofPattern("dd.MM.yyyy").format(Instant.now()) компилируется, но при запуске падает с UnsupportedTemporalTypeException: Unsupported field: DayOfMonth. У Instant нет ни дня, ни месяца, ни года — только секунды от 1970 года. Чтобы получить «15.03.2024», сначала нужно сказать, в каком часовом поясе смотреть на этот момент: .withZone(ZoneId.of("Europe/Moscow")).
Instant в Java (java.time.Instant) — неизменяемый потокобезопасный класс, который описывает мгновение (instant, «инстант») — точку на временной шкале UTC. Внутри он хранит два числа: секунды от эпохи 1970-01-01T00:00:00Z (long) и наносекунды внутри секунды (int от 0 до 999 999 999). Например, 923 456 789 секунд и 186 054 812 наносекунд от эпохи — это момент 1999-04-07T03:46:29.186054812Z. Класс появился в Java 8; текущий момент возвращает Instant.now(). Используют Instant для меток времени: время создания записи в БД, события в логах, обмен датами между сервисами.
Как создать Instant: now(), ofEpochSecond(), ofEpochMilli(), parse()
Конструктора у Instant нет — объект создают статическими методами. Instant.now() возвращает текущий момент, ofEpochSecond(секунды[, наносекунды]) и ofEpochMilli(миллисекунды) задают момент относительно эпохи 1970-01-01T00:00:00Z, а parse() разбирает строку вида 2024-03-15T07:30:00Z. Отрицательные значения означают моменты до 1970 года.
import java.time.Instant;
import java.time.temporal.ChronoField;
public class InstantExample1 {
public static void main(String[] args) {
Instant now = Instant.now();
Instant instant = now.with(ChronoField.NANO_OF_SECOND, 10);
// Устанавливаем секунды
Instant fiveSecondsAfterEpoch = Instant.ofEpochSecond(5);
// Устанавливаем секунды и наносекунды (могут быть отрицательными)
Instant sixSecTwoNanBeforeEpoch = Instant.ofEpochSecond(-6, -2);
// Устанавливаем миллисекунды после (могут быть и до) эпохи
Instant fiftyMilliSecondsAfterEpoch = Instant.ofEpochMilli(50);
// Пример из определения
Instant y1999 = Instant.ofEpochSecond(923_456_789, 186_054_812);
Instant parsed = Instant.parse("2024-03-15T07:30:00Z");
System.out.println("now: " + now);
System.out.println("instant: " + instant);
System.out.println("fiveSecondsAfterEpoch: " + fiveSecondsAfterEpoch);
System.out.println("sixSecTwoNanBeforeEpoch: " + sixSecTwoNanBeforeEpoch);
System.out.println("fiftyMilliSecondsAfterEpoch: " + fiftyMilliSecondsAfterEpoch);
System.out.println("y1999: " + y1999);
System.out.println("parsed: " + parsed);
System.out.println("EPOCH: " + Instant.EPOCH);
}
} now: 2026-10-19T11:35:07.123456Z (зависит от момента запуска)
instant: 2026-10-19T11:35:07.000000010Z
fiveSecondsAfterEpoch: 1970-01-01T00:00:05Z
sixSecTwoNanBeforeEpoch: 1969-12-31T23:59:53.999999998Z
fiftyMilliSecondsAfterEpoch: 1970-01-01T00:00:00.050Z
y1999: 1999-04-07T03:46:29.186054812Z
parsed: 2024-03-15T07:30:00Z
EPOCH: 1970-01-01T00:00:00Z Посмотрите на четвёртую строку: ofEpochSecond(-6, -2) — это «6 секунд и 2 наносекунды до эпохи». Внутри Instant наносекунды всегда неотрицательны, поэтому значение нормализуется в минус 7 секунд плюс 999 999 998 наносекунд, отсюда 23:59:53.999999998. Доли секунды toString() печатает группами по три цифры: 50 миллисекунд выводятся как .050, а 10 наносекунд — как .000000010.
Допустимый диапазон — от Instant.MIN (-1000000000-01-01T00:00:00Z) до Instant.MAX (+1000000000-12-31T23:59:59.999999999Z). За его пределами ofEpochSecond() выбрасывает DateTimeException: Instant exceeds minimum or maximum instant.
Instant.now(): текущий момент в UTC
Метод Instant.now() возвращает текущий момент по системным часам в UTC и не зависит от часового пояса JVM: на сервере в Москве и в Лондоне он даст одно и то же значение. Поэтому Instant.now() — стандартный способ получить метку времени для логов, БД и обмена между сервисами.
import java.time.Instant;
import java.time.LocalDateTime;
public class InstantNowExample {
public static void main(String[] args) {
System.out.println("Instant.now(): " + Instant.now());
System.out.println("LocalDateTime.now(): " + LocalDateTime.now());
}
} Instant.now(): 2026-10-19T11:35:07.123456Z
LocalDateTime.now(): 2026-10-19T14:35:07.123512 (зона JVM Europe/Moscow) Суффикс Z (Zulu) означает UTC, поэтому Instant «отстаёт» от московского времени на 3 часа — это не ошибка, а другая запись того же момента. Точность зависит от версии Java и ОС: в Java 8 обычно миллисекунды, в Java 9+ — как правило микросекунды. Для тестов есть перегрузка Instant.now(Clock) — пример с фиксированными часами в уроке LocalTime.
Получение секунд, миллисекунд и наносекунд
Из Instant можно получить только величины, связанные с эпохой: getEpochSecond() возвращает секунды (long), getNano() — наносекунды внутри секунды (int), toEpochMilli() — миллисекунды от эпохи. Универсальные get() и getLong() поддерживают поля NANO_OF_SECOND, MICRO_OF_SECOND, MILLI_OF_SECOND, а getLong() — ещё и INSTANT_SECONDS.
import java.time.Instant;
import java.time.temporal.ChronoField;
public class InstantExample2 {
public static void main(String[] args) {
Instant instant = Instant.parse("2024-03-15T10:30:45.123456789Z");
long seconds = instant.getEpochSecond(); // Получить секунды
int nanos1 = instant.getNano(); // Получить наносекунды
// Получить значение как int
int millis = instant.get(ChronoField.MILLI_OF_SECOND);
int micros = instant.get(ChronoField.MICRO_OF_SECOND);
// Получить значение как long
long nanos2 = instant.getLong(ChronoField.NANO_OF_SECOND);
long epochMillis = instant.toEpochMilli();
System.out.println("Секунды: " + seconds);
System.out.println("Наносекунды: " + nanos1);
System.out.println("Миллисекунды: " + millis);
System.out.println("Микросекунды: " + micros);
System.out.println("Наносекунды: " + nanos2);
System.out.println("Миллисекунды от эпохи: " + epochMillis);
}
} Секунды: 1710498645
Наносекунды: 123456789
Миллисекунды: 123
Микросекунды: 123456
Наносекунды: 123456789
Миллисекунды от эпохи: 1710498645123 Обратите внимание: MILLI_OF_SECOND — это миллисекунды внутри текущей секунды (123), а не от эпохи; миллисекунды от эпохи даёт toEpochMilli(), причём наносекунды сверх миллисекунды при этом отбрасываются. Полей даты и времени суток у Instant нет: instant.get(ChronoField.HOUR_OF_DAY) выбросит UnsupportedTemporalTypeException: Unsupported field: HourOfDay. Чтобы узнать час, день или год, переведите момент в зону — см. раздел Конвертация.
Сравнение Instant: isAfter(), isBefore(), equals()
Два объекта Instant сравнивают методами isAfter(), isBefore() и equals(), которые возвращают boolean. Для сортировки используется compareTo(): класс реализует Comparable<Instant>. Сравнение идёт по секундам, а при равенстве — по наносекундам; часовой пояс не участвует, потому что его нет.
import java.time.Instant;
public class InstantExample3 {
public static void main(String[] args) {
Instant now = Instant.now();
Instant fiveSecondsAfterEpoch = Instant.ofEpochSecond(5);
System.out.println("now.isAfter(fiveSecondsAfterEpoch)? " + now.isAfter(fiveSecondsAfterEpoch));
System.out.println("now.isBefore(fiveSecondsAfterEpoch)? " + now.isBefore(fiveSecondsAfterEpoch));
System.out.println("now.equals(fiveSecondsAfterEpoch)? " + now.equals(fiveSecondsAfterEpoch));
System.out.println("now.compareTo(fiveSecondsAfterEpoch) > 0? " + (now.compareTo(fiveSecondsAfterEpoch) > 0));
}
} now.isAfter(fiveSecondsAfterEpoch)? true
now.isBefore(fiveSecondsAfterEpoch)? false
now.equals(fiveSecondsAfterEpoch)? false
now.compareTo(fiveSecondsAfterEpoch) > 0? true Результат compareTo() сравнивайте с нулём: если секунды совпадают, метод возвращает разницу наносекунд, а не строго -1 или 1. Метода isEqual(), как у LocalDate, у Instant нет — используйте equals(). Одинаковые моменты, записанные в разных зонах, например ZonedDateTime 10:30 по Москве и 07:30 по UTC, после toInstant() дают равные Instant.
Методы plus() и minus()
Чтобы сдвинуть Instant, используют plusSeconds(), plusMillis(), plusNanos(), парные minus...() и универсальные plus(количество, ChronoUnit) / minus(...) или plus(Duration). Поддерживаются единицы от NANOS до DAYS (день считается ровно 86 400 секундами); месяцев, недель и лет нет. Каждый вызов возвращает новый объект.
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class InstantExample4 {
public static void main(String[] args) {
Instant epoch = Instant.ofEpochSecond(0);
// Добавление
Instant instant1 = epoch.plusSeconds(400);
Instant instant2 = epoch.plusMillis(98622200);
Instant instant3 = epoch.plusNanos(300013890);
Instant instant4 = epoch.plus(2, ChronoUnit.MINUTES);
// Вычитание
Instant instant5 = epoch.minusSeconds(2);
Instant instant6 = epoch.minusMillis(1);
Instant instant7 = epoch.minusNanos(1);
Instant instant8 = epoch.minus(10, ChronoUnit.SECONDS);
System.out.println(epoch);
System.out.println(instant1);
System.out.println(instant2);
System.out.println(instant3);
System.out.println(instant4);
System.out.println(instant5);
System.out.println(instant6);
System.out.println(instant7);
System.out.println(instant8);
System.out.println(epoch.plus(1, ChronoUnit.DAYS));
}
} 1970-01-01T00:00:00Z
1970-01-01T00:06:40Z
1970-01-02T03:23:42.200Z
1970-01-01T00:00:00.300013890Z
1970-01-01T00:02:00Z
1969-12-31T23:59:58Z
1969-12-31T23:59:59.999Z
1969-12-31T23:59:59.999999999Z
1969-12-31T23:59:50Z
1970-01-02T00:00:00Z Что выведет Instant.now().plus(1, ChronoUnit.MONTHS)? Ничего: будет выброшено UnsupportedTemporalTypeException: Unsupported unit: Months. Длина месяца зависит от календаря и часового пояса, а Instant не знает ни того, ни другого. Месяц прибавляют через зону:
Instant start = Instant.parse("2024-01-31T09:00:00Z");
ZoneId zone = ZoneId.of("Europe/Moscow");
Instant nextMonth = start.atZone(zone).plusMonths(1).toInstant();
System.out.println(nextMonth); // 2024-02-29T09:00:00Z
System.out.println(start.plusSeconds(1800)); // 2024-01-31T09:30:00Z
System.out.println(start.plusSeconds(1800).truncatedTo(ChronoUnit.HOURS)); // 2024-01-31T09:00:00Z Метод truncatedTo() отбрасывает всё, что меньше указанной единицы. Учтите, что truncatedTo(ChronoUnit.DAYS) даёт полночь по UTC, а не по местному времени, — для Москвы это 03:00.
Разница между двумя Instant: Duration.between
Разницу между двумя моментами возвращает Duration.between(start, end) — объект с часами, минутами, секундами и наносекундами. Если нужно число в конкретных единицах, используйте ChronoUnit.X.between(start, end) или start.until(end, unit): они возвращают long и отбрасывают неполные единицы.
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class InstantBetweenExample {
public static void main(String[] args) {
Instant start = Instant.parse("2024-03-15T07:30:00Z");
Instant end = Instant.parse("2024-03-15T09:45:30.500Z");
Duration duration = Duration.between(start, end);
System.out.println(duration); // PT2H15M30.5S
System.out.println(duration.toMillis()); // 8130500
System.out.println(ChronoUnit.MINUTES.between(start, end)); // 135
System.out.println(start.until(end, ChronoUnit.SECONDS)); // 8130
System.out.println(Duration.between(end, start)); // PT-2H-15M-30.5S
}
} Если конец раньше начала, длительность отрицательная. Для замера времени работы кода Instant.now() подходит с оговоркой: системные часы могут перевести (например, при синхронизации NTP), и разница окажется неверной, поэтому для бенчмарков берут System.nanoTime(). Методы Duration (toMinutes(), getSeconds(), plus()) разобраны в уроке Класс Duration.
Форматирование Instant и разбор строки
instant.toString() выводит момент в формате ISO-8601 по UTC (DateTimeFormatter.ISO_INSTANT), например 2024-03-15T07:30:00.123Z. Для любого другого формата нужен DateTimeFormatter с часовым поясом: DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss").withZone(zone). Без withZone() форматтер не сможет вычислить дату и время суток.
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class InstantFormatExample {
public static void main(String[] args) {
Instant instant = Instant.parse("2024-03-15T07:30:00.123Z");
DateTimeFormatter moscow = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
.withZone(ZoneId.of("Europe/Moscow"));
DateTimeFormatter http = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
System.out.println(instant); // toString()
System.out.println(DateTimeFormatter.ISO_INSTANT.format(instant));
System.out.println(moscow.format(instant));
System.out.println(http.format(instant));
// Строка в «своём» формате -> Instant
Instant parsed = ZonedDateTime.parse("15.03.2024 10:30:00", moscow).toInstant();
System.out.println(parsed);
}
} 2024-03-15T07:30:00.123Z
2024-03-15T07:30:00.123Z
15.03.2024 10:30:00
Fri, 15 Mar 2024 07:30:00 GMT
2024-03-15T07:30:00Z Метод Instant.parse() принимает только ISO-формат с указанием UTC (а в новых версиях — и смещения). Строки без зоны не разбираются ни в одной версии Java:
Instant.parse("2024-03-15T07:30:00Z"); // 2024-03-15T07:30:00Z
Instant.parse("2024-03-15T07:30:00"); // DateTimeParseException: Text '2024-03-15T07:30:00' could not be parsed at index 19
Instant.parse("2024-03-15"); // DateTimeParseException: Text '2024-03-15' could not be parsed at index 10
Instant.parse("2024-03-15T10:30:00+03:00"); // Java 12+: 2024-03-15T07:30:00Z; Java 8-11: DateTimeParseException at index 19 Смещение в строке: зависит от версии Java
До Java 11 включительно Instant.parse() понимает только суффикс Z, и строка 2024-03-15T10:30:00+03:00 из чужого API падает с DateTimeParseException. Начиная с Java 12 ISO_INSTANT принимает и смещение (JDK-8166138). Если код должен работать и на Java 8, разбирайте такие строки через OffsetDateTime.parse(text).toInstant() — это работает во всех версиях.
Символы шаблона (dd, MM, HH и др.) и работа с Locale — в уроке Класс DateTimeFormatter.
Конвертация Instant в ZonedDateTime, LocalDateTime и Date
Чтобы из Instant получить дату и время «на часах», укажите часовой пояс: instant.atZone(zone) возвращает ZonedDateTime, instant.atOffset(offset) — OffsetDateTime, а LocalDateTime.ofInstant(instant, zone) — LocalDateTime. Обратно в Instant ведёт toInstant(). Со старым API связывают Date.from(instant) и date.toInstant().
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;
public class InstantConvertExample {
public static void main(String[] args) {
Instant instant = Instant.parse("2024-03-15T07:30:00Z");
ZonedDateTime moscow = instant.atZone(ZoneId.of("Europe/Moscow"));
OffsetDateTime plus3 = instant.atOffset(ZoneOffset.ofHours(3));
LocalDateTime tokyo = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Tokyo"));
LocalDate date = LocalDate.ofInstant(instant, ZoneOffset.UTC); // Java 9+
Date legacy = Date.from(instant);
Instant fromLegacy = legacy.toInstant();
Instant fromMillis = Instant.ofEpochMilli(legacy.getTime());
System.out.println(moscow);
System.out.println(plus3);
System.out.println(tokyo);
System.out.println(date);
System.out.println(moscow.getHour() + " ч., " + moscow.getDayOfWeek());
System.out.println(legacy.getTime());
System.out.println(moscow.toInstant().equals(instant));
System.out.println(fromLegacy.equals(fromMillis));
}
} 2024-03-15T10:30+03:00[Europe/Moscow]
2024-03-15T10:30+03:00
2024-03-15T16:30
2024-03-15
10 ч., FRIDAY
1710487800000
true
true Один и тот же Instant в Москве — 10:30, в Токио — 16:30: момент один, меняется только запись на часах. В Java 8 метода LocalDate.ofInstant() ещё нет, там пишут instant.atZone(zone).toLocalDate(). При конвертации в Date точность падает до миллисекунд, наносекунды теряются.
Обратное направление — из LocalDateTime в Instant — требует зоны (ldt.atZone(zone).toInstant()); подробный разбор с переводом часов на летнее время — в уроке LocalDateTime, а когда что хранить — в его разделе LocalDateTime vs Instant. Полная таблица перехода с Date и Calendar — в уроке Дата и время в Java.
Шпаргалка по методам Instant
Методы Instant делятся на создание, чтение значений от эпохи, сравнение, сдвиг и привязку к часовому поясу. Все методы, которые «меняют» момент, возвращают новый объект.
| Метод | Что делает | Граничный случай / ошибка |
|---|---|---|
now(), now(Clock) | Текущий момент в UTC | Точность: Java 8 — миллисекунды, Java 9+ — обычно микросекунды |
ofEpochSecond(s[, n]), ofEpochMilli(ms) | Момент относительно 1970-01-01T00:00:00Z | Отрицательные значения — до 1970 года; за пределами MIN/MAX — DateTimeException |
parse(text) | Разбор ISO-строки | Без Z — DateTimeParseException; смещение +03:00 — только с Java 12 |
getEpochSecond(), getNano(), toEpochMilli() | Секунды, наносекунды внутри секунды, миллисекунды от эпохи | toEpochMilli() отбрасывает наносекунды сверх миллисекунды |
get(ChronoField), getLong(ChronoField) | Поля NANO/MICRO/MILLI_OF_SECOND, INSTANT_SECONDS (через getLong) | HOUR_OF_DAY и др. — UnsupportedTemporalTypeException |
isAfter(), isBefore(), equals(), compareTo() | Сравнение моментов | compareTo() сравнивайте с нулём; isEqual() нет |
plusSeconds(), plusMillis(), plusNanos(), minus...(), plus(n, unit) | Сдвиг на N единиц | MONTHS, WEEKS, YEARS — Unsupported unit |
truncatedTo(unit) | Отбрасывает младшие единицы | DAYS — полночь по UTC, не по местному времени |
until(end, unit), Duration.between(a, b) | Разница между моментами | Если конец раньше начала — отрицательное значение |
atZone(ZoneId), atOffset(ZoneOffset) | Переход к ZonedDateTime / OffsetDateTime | Без этого шага нельзя получить год, месяц, час |
Типичные промахи при работе с Instant
Почти все ошибки с Instant происходят из одного: от него ждут даты и времени «на часах», а это лишь момент на шкале UTC без часового пояса. Ниже — частые случаи и исправления.
| Ошибка | Что происходит | Как правильно |
|---|---|---|
DateTimeFormatter.ofPattern("dd.MM.yyyy").format(instant) | UnsupportedTemporalTypeException: Unsupported field: DayOfMonth | ofPattern("dd.MM.yyyy").withZone(zone) |
instant.plus(1, ChronoUnit.MONTHS) | UnsupportedTemporalTypeException: Unsupported unit: Months | instant.atZone(zone).plusMonths(1).toInstant() |
| «Instant показывает время на 3 часа меньше» | Это не ошибка: toString() всегда выводит UTC | Для показа пользователю: instant.atZone(zone) или форматтер с withZone() |
Instant.parse("2024-03-15T07:30:00") | DateTimeParseException at index 19 | Добавить Z или LocalDateTime.parse(s).atZone(zone).toInstant() |
instant.truncatedTo(ChronoUnit.DAYS) как «начало дня» | Полночь по UTC (03:00 по Москве) | LocalDate.now(zone).atStartOfDay(zone).toInstant() |
instant.get(ChronoField.MILLI_OF_SECOND) как «миллисекунды от эпохи» | Возвращает 0–999 внутри секунды | instant.toEpochMilli() |
Часто задаваемые вопросы
Чем Instant отличается от ZonedDateTime и OffsetDateTime?
Все три могут описывать один и тот же момент, но по-разному. Instant — голая точка на шкале UTC, без календарных полей. OffsetDateTime добавляет фиксированное смещение (+03:00), а ZonedDateTime — зону с правилами перевода часов (Europe/Berlin). Храните и передавайте Instant, а ZonedDateTime создавайте, когда нужно показать время пользователю или прибавить календарные месяцы.
Учитывает ли Instant високосные секунды?
Нет. В java.time используется шкала Java Time-Scale: в каждых сутках ровно 86 400 секунд, а високосная секунда «размазывается» по последним минутам дня. Строку с секундой 60 разбор не отвергает, а сводит к обычной: Instant.parse("2016-12-31T23:59:60Z") вернёт 2016-12-31T23:59:59Z. Поэтому арифметика с Instant всегда предсказуема; точное астрономическое время требует отдельных библиотек.
Как сохранить Instant в базе данных и в JSON?
В JDBC 4.2 обязательна поддержка OffsetDateTime, поэтому самый переносимый способ — ps.setObject(1, instant.atOffset(ZoneOffset.UTC)) для столбца TIMESTAMP WITH TIME ZONE; в старом коде — Timestamp.from(instant) и timestamp.toInstant(). Hibernate 5.2+ отображает поле Instant сам. В JSON Jackson пишет Instant строкой вроде "2024-03-15T07:30:00Z" при подключённом JavaTimeModule и выключенном WRITE_DATES_AS_TIMESTAMPS, иначе — числом секунд с дробной частью.
Зачем Instant, если можно хранить long с миллисекундами?
long не говорит, что в нём лежит: миллисекунды, секунды или вообще идентификатор, и перепутать единицы легко. Instant типобезопасен, хранит наносекунды, печатается в читаемом ISO-виде и сразу сочетается с Duration, atZone() и форматтерами. Переход между ними — одна строка: Instant.ofEpochMilli(ms) и instant.toEpochMilli(), см. раздел Получение секунд и миллисекунд.
Practice
Reinforce this lesson with hands-on tasks, each with a level and a time estimate.
All module tasks →Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments