W3docs

Java Duration

Работа с временными интервалами (часы, минуты, секунды) в Java с помощью Duration.

Duration представляет длину промежутка времени, измеряемого в секундах и наносекундах. «Два часа». «Пятьсот миллисекунд». «Сорок пять минут». Внутри это два числа — long seconds и int nanos — и все операции над ними являются целочисленной арифметикой. Тип не знает о календарях, месяцах и переходе на летнее время: если указать 24 часа, это означает ровно 86 400 секунд, независимо от того, приходится ли этот промежуток на переход DST.

Это правильный тип для: измерения времени выполнения, таймаутов, повторных попыток с экспоненциальной задержкой, «ожидать не более X секунд» — для любого случая, когда ответом является реальный временной интервал. Сопутствующий тип для календарных длительностей («один месяц», «два года») — Period, который рассматривается в следующей главе.

Создание

Три группы фабричных методов:

Duration.ofNanos(500_000_000);
Duration.ofMillis(500);
Duration.ofSeconds(45);
Duration.ofSeconds(60, 500_000_000);                         // with nanos
Duration.ofMinutes(2);
Duration.ofHours(1);
Duration.ofDays(7);                                           // exactly 7 * 24 hours

Duration.between(start, end);                                 // from two Temporals
Duration.parse("PT1H30M");                                    // ISO-8601: PT[hours]H[minutes]M[seconds]S

Duration.ofDays(n) — это фабричный метод, который большинство разработчиков используют и большинство из них понимают неправильно. Он производит ровно n * 24 * 3600 секунд. В день перехода на летнее время это не то же самое, что «следующий день в то же время по часам». Для «следующий день» в календарном смысле правильными инструментами являются LocalDate.plusDays(1) или Period.ofDays(1).

Строковый формат — это duration по ISO-8601: PT1H30M45S означает один час, тридцать минут, сорок пять секунд. Префикс PT обязателен (P = Period, T = Time). Для долей секунды используется форма PT0.5S или PT0.000000001S. Duration охватывает всё, что меньше суток; периоды (год, месяц, день) относятся к Period.

Арифметика

d.plus(Duration.ofSeconds(15));
d.plusSeconds(60);
d.plusMinutes(5);
d.plusHours(2);
d.minus(Duration.ofMillis(100));
d.multipliedBy(3);
d.dividedBy(2);
d.negated();                                                  // -d
d.abs();

Каждый метод возвращает новый объект Duration (принцип иммутабельности, начиная с LocalDate).

Методы преобразования:

d.toNanos();          // long
d.toMillis();         // long; throws if out of long range
d.toSeconds();        // since Java 9
d.toMinutes();
d.toHours();
d.toDays();           // assumes 24h days
d.getSeconds();       // raw seconds component
d.getNano();          // raw nanos component (0-999_999_999)

Методы toX() возвращают единственное значение long — суммарное значение в указанных единицах с усечением. Методы getX() возвращают необработанные компоненты разбивки. Это разные вещи; путаница между ними — самая распространённая ошибка при работе с Duration.

Duration d = Duration.ofSeconds(125);
d.toMinutes();        // 2  (125 / 60)
d.getSeconds();       // 125 (raw)

Для форматирования в виде «X минут Y секунд» используйте оба:

long mins = d.toMinutes();
long secs = d.minusMinutes(mins).getSeconds();
System.out.printf("%d:%02d%n", mins, secs);                  // 2:05

Или, начиная с Java 9, специальные вспомогательные методы разбивки:

d.toHoursPart();       // hours within the duration (0-23-ish on positive durations)
d.toMinutesPart();     // minutes within the duration (0-59)
d.toSecondsPart();     // seconds within the duration (0-59)
d.toMillisPart();      // millis within the second

Именно их следует использовать для отображения «1ч 23м 45с» без ручных вычислений по модулю.

Сравнение

d1.isZero();           // d == 0
d1.isNegative();        // d < 0
d1.compareTo(d2);
d1.equals(d2);

Duration реализует Comparable<Duration>. Порядок — знаковый: отрицательная длительность меньше нуля, а нуль меньше положительной длительности.

Расстояние между двумя Temporal

Фабричный метод Duration.between(start, end) — тот, который вы будете использовать чаще всего:

Duration d = Duration.between(start, end);                    // works on Instant, LocalDateTime, ZonedDateTime, LocalTime

Он принимает любую пару значений Temporal, поддерживающих временны́е единицы. LocalDate (только дата) не поддерживает — Duration.between(date1, date2) бросает DateTimeException, поскольку дата не имеет компонента времени. Для календарного расстояния используйте Period.between(date1, date2) или ChronoUnit.DAYS.between(date1, date2).

Соглашение о знаке: положительное значение, если end после start, иначе отрицательное.

Прибавление к другим временным типам

Duration является TemporalAmount, поэтому любой Temporal принимает его через plus/minus:

Instant later = Instant.now().plus(Duration.ofMinutes(30));
LocalDateTime then = LocalDateTime.now().plus(Duration.ofHours(8));

Добавление Duration к LocalDate бросает исключение — по той же причине: нет компонента времени. Компилятор это не обнаружит; вызов завершится ошибкой во время выполнения. Если вы хотите именно это, почти наверняка вам нужен Period.

Практический пример: измерение, форматирование, экспоненциальная задержка

Программа ниже измеряет прошедшее время для небольшого фрагмента кода, выводит его в удобочитаемом виде с помощью вспомогательных методов toXxxPart, вычисляет расписание экспоненциальной задержки и демонстрирует границу между Duration и Period, показывая как Duration.between для Instantов, так и Period.between для LocalDateов для одного и того же концептуального временного промежутка.

java— editable, runs on the server

Что следует вынести из запуска:

  • Блок Thread.sleep(125) измерен как PT0.125-something-S — реальный перерасход sleep объясняется варьированием на конкретной машине. Duration.between(t0, t1) — правильный вызов: два Instantа, зона не нужна, точный ответ с разрешением системных часов.
  • Вспомогательные методы toXxxPart() дали чистые целые числа: «часть часов», «часть минут», «часть секунд», «часть миллисекунд». Без них пришлось бы вручную делать total / 3600 и (total % 3600) / 60. Используйте их всякий раз, когда нужно форматировать длительность для человека.
  • Цикл экспоненциальной задержки масштабировал длительность с помощью multipliedBy(2) и выводил каждый результат. Строковое представление ISO-8601 (PT2S, PT4S, PT8S, ...) — это то, что печатает Duration.toString; оно лаконично и однозначно, идеально для журналов.
  • Блок «Jan 1 → Apr 1 тремя способами» показал границу между Duration и Period. Period.between(start, end) вернул P3M — три календарных месяца. Duration.between для эквивалентных UTC-инстантов вернул реальное количество времени (90 дней). Оба результата правильны; они отвечают на разные вопросы. Выбирайте тот, чей смысл соответствует намерению вашего кода.
  • Последний блок попытался вызвать Duration.between(LocalDate, LocalDate) и получил DateTimeException. Система типов знает, что у даты нет компонента времени, поэтому отказывается вычислять временну́ю длительность. Исправление: либо (1) добавить время (startDate.atStartOfDay(zone)), либо (2) использовать Period.between, если вам нужен именно календарный счёт. Исключение — правильное поведение: молчаливый ответ на этот вопрос был бы вводящим в заблуждение.

Что дальше

Duration — это длина в секундах и наносекундах. Следующая глава, Java Period, — это длина в годах, месяцах и днях, то есть календарный аналог. Эти два типа никогда не смешиваются: Duration не знает о месяцах, Period не знает о часах, и любой код, работающий с обоими, обращается к одному или другому в зависимости от того, должен ли ответ следовать календарю или реальному времени.

Практика

Практика
Вы хотите повторить HTTP-запрос через 'один день', то есть '24 часа с этого момента вне зависимости от DST.' Какой тип и вызов для этого подходит?
Вы хотите повторить HTTP-запрос через 'один день', то есть '24 часа с этого момента вне зависимости от DST.' Какой тип и вызов для этого подходит?
Was this page helpful?