Java LocalTime
Представление времени без даты и временной зоны в Java с помощью LocalTime.
LocalTime — это зеркальное отражение LocalDate: время суток — часы, минуты, секунды, наносекунды — без даты и без временной зоны. Оно представляет одно и то же показание часов в любой календарный день в любом городе: LocalTime.of(9, 30) — это полдевятого в любой день в любом городе.
Это правильный тип для повторяющегося времени суток — открытие магазина в 09:30, ежедневный cron-подобный процесс в 03:15, встреча, начинающаяся в 14:00 независимо от даты. Это не тот тип, который нужен для "момента, когда пользователь нажал кнопку отправки в 14:30 сегодня" — для этого нужна дата и, вероятно, временная зона. Их объединение даёт LocalDateTime, тема следующей главы.
Создание
LocalTime now = LocalTime.now(); // current time in the JVM default zone
LocalTime nine = LocalTime.of(9, 0); // hour, minute
LocalTime nineThirty = LocalTime.of(9, 30, 15); // hour, minute, second
LocalTime nineThirtyNanos = LocalTime.of(9, 30, 15, 500_000_000); // + nanosecond (0 ..999_999_999)
LocalTime parsed = LocalTime.parse("09:30:15"); // ISO-8601 HH:mm[:ss[.nnnnnnnnn]]Заранее определённые константы полезны для граничных условий:
LocalTime.MIDNIGHT // 00:00
LocalTime.NOON // 12:00
LocalTime.MIN // 00:00:00.000000000
LocalTime.MAX // 23:59:59.999999999MIN и MAX особенно полезны при объединении с LocalDate для охвата целого дня: LocalDateTime.of(date, LocalTime.MIN) — это "полночь в начале даты"; LocalTime.MAX — последняя представимая наносекунда дня.
Разрешение: наносекунды
LocalTime точен до наносекунды — девять полей разрешения (1 секунда = 1 000 000 000 нс). На большинстве операционных систем реальное разрешение часов составляет миллисекунду (1 000 000 нс) или микросекунду (1 000 нс); дополнительная точность предусмотрена, чтобы тип не терял информацию при взаимодействии с системами, обладающими часами более высокого разрешения.
Прямые методы доступа:
time.getHour(); // 0-23
time.getMinute(); // 0-59
time.getSecond(); // 0-59
time.getNano(); // 0-999_999_999Метода getMilli() нет; если нужны миллисекунды, разделите наносекунды: time.getNano() / 1_000_000.
24-часовой формат, без AM/PM
LocalTime внутренне использует 24-часовой формат. LocalTime.of(13, 0) означает "13:00", и в типе нет AM/PM. Разбор строк с "AM"/"PM" требует пользовательского DateTimeFormatter (глава Разбор дат охватывает эту тему) — стандартный parse работает только с ISO-8601 24-часовым форматом.
Арифметика и изменения
Тот же гибкий интерфейс, что и у LocalDate:
time.plusHours(2);
time.plusMinutes(30);
time.plusSeconds(45);
time.plusNanos(500_000);
time.withHour(14); // replace one field
time.withMinute(0);Поведение при переходе через полночь: каждый метод plus/minus у LocalTime молча переходит через полночь. LocalTime.of(23, 0).plusHours(2) возвращает 01:00, а не "01:00 следующего дня" — в LocalTime нет понятия "следующий день". Если нужно знать, произошёл ли переход, используйте LocalDateTime или вычислите самостоятельно:
LocalTime late = LocalTime.of(23, 0);
LocalTime later = late.plusHours(2); // 01:00 — silently wrapped
// To detect wrap: compare the new value's getHour with what you expected, or use LocalDateTime.Этот переход задокументирован и является намеренным, но это острый момент, о котором легко забыть. Для расчётов "когда заканчивается эта смена?", которые могут пересекать полночь, правильный тип — LocalDateTime, а не LocalTime.
Сравнение
time.isBefore(other);
time.isAfter(other);
time.compareTo(other);
time.equals(other);Лексикографическое упорядочивание по hour:minute:second:nano. LocalTime реализует Comparable<LocalTime>, поэтому можно отсортировать список времён или напрямую использовать его в качестве ключа TreeMap.
Разница
Duration.between и ChronoUnit.X.between работают оба:
Duration d = Duration.between(start, end);
long minutes = ChronoUnit.MINUTES.between(start, end);
long seconds = ChronoUnit.SECONDS.between(start, end);Знак: положительный, когда end после start, отрицательный в противном случае. Та же оговорка о переходе через полночь применима — Duration.between(LocalTime.of(23, 0), LocalTime.of(1, 0)) равно −22 часа, а не +2 часа; API считает 01:00 более ранним, чем 23:00 того же условного дня. Для "смена перешла через полночь" правильным инструментом является арифметика на основе LocalDateTime.
Объединение с LocalDate
Вы часто будете преобразовывать в дату-со-временем:
LocalDate date = LocalDate.of(2025, 11, 4);
LocalTime time = LocalTime.of(9, 30);
LocalDateTime dt = date.atTime(time); // 2025-11-04T09:30
LocalDateTime dt2 = time.atDate(date); // same thingatTime / atDate — мостовые методы. Результат — LocalDateTime — по-прежнему без зоны, но теперь привязан к календарному дню. Следующая глава развивает эту тему дальше.
Практический пример: небольшой помощник планирования
Программа ниже использует LocalTime для задачи типа ежедневного расписания: определяет окно "рабочих часов", проверяет, попадает ли заданный момент внутрь него, вычисляет время до следующего открытия и демонстрирует ловушку с переходом через полночь.
Что можно извлечь из запуска:
LocalTimeвыводится как09:00,17:30,12:30— каноническая ISO-8601 24-часовая форма. Никакого AM/PM в типе. Если нужно отобразить "5:30 PM" пользователю, глава Форматирование дат содержит нужный форматтер; сам тип об этом ничего не знает.- Проверка "попадает ли время в окно" использовала
!isBefore(open) && !isAfter(close). Это идиома полуоткрытого/закрытого интервала — оба конца включены. Для "строго внутри" переключитесь на формы без отрицания. Duration.between(LocalTime.of(22, 0), LocalTime.of(2, 0))вернулPT-20H, а неPT4H.LocalTimeне имеет понятия "следующий день" — когдаendчасовым значением меньшеstart, длительность становится отрицательной. Для смены, пересекающей полночь, переключите входные данные наLocalDateTimeи дайте датам разрешить неоднозначность. Это единственная крупнейшая ловушкаLocalTime.LocalTime.of(23, 30).plusHours(2)вернул01:30. Переход молча — без исключения, без флага, без переноса в дату. Если нужно знать "произошёл ли переход?", используйтеLocalDateTime. Если же нужна именно тактовая арифметика по модулю 24 (например, повторяющееся расписание), переход является корректным поведением.date.atTime(time)— это канонический мост кLocalDateTime. Зеркальный методtime.atDate(date)даёт тот же результат. Вы будете постоянно использовать их при чтении времени из одного источника и даты из другого, а затем объединяя их в единый объект, который требует нижестоящий API.
Что дальше
Следующая глава, Java LocalDateTime, объединяет LocalDate и LocalTime в третий "локальный" тип: дата и время, по-прежнему без временной зоны. Это естественная единица для "это произошло в 14:30 4 ноября", когда зона не важна или хранится отдельно.