Between: примеры (JAVA)

Разбор применения between в java.time с примерами
Раздел: Время и дата (Date & Time API)
between(Temporal startInclusive, Temporal endExclusive): long

Общее описание методов между значениями (between) в Java

В Java явной функции с именем between как универсального оператора не существует, однако в стандартной библиотеке присутствуют несколько методов с именем between, используемых для вычисления разницы между временными значениями и для измерения промежутков. Основные реализations находятся в пакете java.time:

  • java.time.Duration.between(Temporal, Temporal) - вычисляет временной интервал между двумя моментами во времени с точностью до наносекунд. Аргументы: два объекта, реализующие интерфейс Temporal (например, Instant, LocalDateTime, ZonedDateTime). Возвращает Duration. Семантика: первый аргумент - начало (включительно), второй - конец (исключительно). Возможные исключения: DateTimeException, ArithmeticException, NullPointerException.
  • java.time.Period.between(LocalDate, LocalDate) - вычисляет период в целых годах, месяцах и днях между двумя датами. Аргументы: два LocalDate (startInclusive, endExclusive). Возвращает Period. Исключения: DateTimeException, NullPointerException.
  • java.time.temporal.ChronoUnit.between(Temporal, Temporal) - универсальный способ получить разницу в выбранных единицах (дни, часы, месяцы и т. п.). Возвращает long. Поддерживает отрицательные значения при обратном порядке аргументов. Исключения: UnsupportedTemporalTypeException, DateTimeException, ArithmeticException, NullPointerException.

Ключевые особенности поведения:

  • Семантика границ обычно: начало включается, конец исключается.
  • Duration отражает точную величину во времени (секунды и наносекунды), Period - календарные компоненты (годы, месяцы, дни), ChronoUnit.between возвращает количество выбранных единиц.
  • При работе с зонами и DST следует использовать типы с зоной (ZonedDateTime) и учитывать смещения.
  • Некорректные сочетания типов (например, попытка использовать Period.between для Instant) приводят к исключениям.

Короткие примеры использования между значениями

Примеры демонстрируют типичные варианты: Period, Duration и ChronoUnit.

// 1. Period.between - разница в годах/месяцах/днях
import java.time.LocalDate;
import java.time.Period;

LocalDate d1 = LocalDate.of(1980, 1, 1);
LocalDate d2 = LocalDate.of(2024, 4, 20);
Period p = Period.between(d1, d2);
System.out.println(p); // P44Y3M19D
System.out.println(p.getYears() + " years, " + p.getMonths() + " months, " + p.getDays() + " days");
P44Y3M19D
44 years, 3 months, 19 days
// 2. Duration.between - точный интервал между LocalDateTime
import java.time.Duration;
import java.time.LocalDateTime;

LocalDateTime t1 = LocalDateTime.of(2024, 4, 20, 10, 0);
LocalDateTime t2 = LocalDateTime.of(2024, 4, 21, 12, 30);
Duration d = Duration.between(t1, t2);
System.out.println(d); // PT26H30M
System.out.println(d.toHours() + " hours, " + d.toMinutesPart() + " minutes");
PT26H30M
26 hours, 30 minutes
// 3. ChronoUnit.between - количество дней
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

long days = ChronoUnit.DAYS.between(LocalDate.of(2024, 4, 20), LocalDate.of(2024, 4, 25));
System.out.println(days);
5
// 4. Отрицательный результат при обратном порядке
long rev = ChronoUnit.DAYS.between(LocalDate.of(2024, 4, 25), LocalDate.of(2024, 4, 20));
System.out.println(rev);
-5
// 5. Duration.between с Instant и вывод в секундах
import java.time.Duration;
import java.time.Instant;

Instant i1 = Instant.parse("2024-04-20T10:00:00Z");
Instant i2 = Instant.parse("2024-04-21T12:30:00Z");
Duration dd = Duration.between(i1, i2);
System.out.println(dd.toSeconds());
95400

Похожие подходы в Java и их особенности

  • compareTo / isBefore / isAfter у типов LocalDate, LocalDateTime, Instant. Предназначены для сравнения, но не дают разницу в числах или в компонентах периода.
  • Temporal.until(Temporal endExclusive, TemporalUnit unit) - метод экземпляра, похожий на ChronoUnit.between, используется когда требуется вызвать от конкретного объекта.
  • java.time.temporal.ChronoUnit vs Period/Duration: ChronoUnit удобен для получения целого количества выбранных единиц, Period и Duration - для представления результата в специализированной форме.
  • Библиотеки (Guava, Joda-Time) предлагают свои способы работы с промежутками и диапазонами; встроенные java.time обычно предпочтительнее при работе с датами/временем в современных проектах.

Аналоги в других языках и отличия от Java

Краткие примеры для распространенных языков с выводом результата.

-- SQL: BETWEEN (инклюзивно)
SELECT * FROM orders WHERE order_date BETWEEN '2024-04-20' AND '2024-04-25';
-- оператор возвращает строки с датами в диапазоне включительно
-- Результат зависит от данных в таблице; оператор проверяет включительно обе границы
// JavaScript: разница в днях (нативный Date)
const d1 = new Date('2024-04-20');
const d2 = new Date('2024-04-25');
const days = (d2 - d1) / (1000 * 60 * 60 * 24);
console.log(days);
5
# Python: datetime и timedelta
from datetime import date
d1 = date(2024,4,20)
d2 = date(2024,4,25)
print((d2 - d1).days)
5
// C#: DateTime.Subtract -> TimeSpan
using System;

var d1 = new DateTime(2024,4,20);
var d2 = new DateTime(2024,4,25);
Console.WriteLine((d2 - d1).TotalDays);
5
-- PHP: DateTime::diff
$d1 = new DateTime('2024-04-20');
$d2 = new DateTime('2024-04-25');
$diff = $d1->diff($d2);
echo $diff->days; // общее количество дней
5
// Go: time.Sub
package main
import (
  "fmt"
  "time"
)
func main() {
  t1 := time.Date(2024,4,20,0,0,0,0,time.UTC)
  t2 := time.Date(2024,4,25,0,0,0,0,time.UTC)
  fmt.Println(t2.Sub(t1).Hours() / 24)
}
5

Отличия от Java:

  • SQL BETWEEN - оператор уровня запроса и по умолчанию инклюзивен, в то время как java.time методы имеют семантику включительно-исключительно для вычислений.
  • Во многих динамических языках (JS, Python, PHP) разницы выполняются через вычитание дат, результат часто представлен как число дней или объект timedelta/TimeSpan. Java предлагает специализированные типы Period и Duration с богатым набором методов.
  • Короткие операции в других языках обычно работают в миллисекундах/секундах по умолчанию; java.time гарантирует точность и явную семантику компонентов.

Типичные ошибки при использовании между методами

  • Путаница с включением границ: ожидание, что обе границы включаются, тогда как многие методы считают конец исключительным. Пример: ожидание 6 дней вместо 5 при неверной интерпретации.
  • Использование неподходящих типов: Period.between принимает именно LocalDate. Попытка передать Instant приводит к ошибке компиляции или исключению времени выполнения.
  • Игнорирование часовых поясов: при работе с локальным временем и ZonedDateTime разница может измениться из-за перехода на летнее/зимнее время.
  • Отсутствие проверки на null - типичные NullPointerException.
// Пример ошибки: смешение типов
import java.time.Instant;
import java.time.Period;

Instant a = Instant.now();
Instant b = Instant.now().plusSeconds(3600);
// Period p = Period.between(a, b); // Ошибка компиляции: нет подходящего метода
// Компилятор выдаст сообщение об отсутствующем методе для типов Instant
// Пример: влияние часового пояса (переход DST)
import java.time.ZonedDateTime;
import java.time.Duration;
import java.time.ZoneId;

ZonedDateTime z1 = ZonedDateTime.of(2024,3,30,1,0,0,0, ZoneId.of("Europe/Berlin"));
ZonedDateTime z2 = ZonedDateTime.of(2024,3,30,4,0,0,0, ZoneId.of("Europe/Berlin"));
System.out.println(Duration.between(z1, z2).toHours());
// Результат может быть 2 или 3 в зависимости от перехода на летнее время в этой зоне

Изменения и эволюция методов для вычисления промежутков

Основная функциональность методов between в пакете java.time была введена в Java 8 и с тех пор в базовой логике осталась стабильной. В последующих версиях JDK проводились расширения и фиксы вокруг поддержки дополнительных хронологий и удобных утилит, однако принципы работы Duration.between, Period.between и ChronoUnit.between не претерпели принципиальных изменений. Рекомендация для новых проектов - использование java.time вместо устаревших API (например, java.util.Date или Joda-Time).

Расширенные и редкие сценарии применения

Несколько подробных примеров с пояснениями.

Пример java
// 1. Подсчет рабочих дней (без учета праздников)
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

LocalDate start = LocalDate.of(2024,4,1);
LocalDate end = LocalDate.of(2024,4,15);
long workDays = 0;
for (LocalDate d = start; d.isBefore(end); d = d.plusDays(1)) {
  int dow = d.getDayOfWeek().getValue(); // 1 = Monday ... 7 = Sunday
  if (dow >= 1 && dow <= 5) workDays++;
}
System.out.println("Рабочих дней: " + workDays);
Рабочих дней: 11
Пример java
// 2. Разница в месяцах с учётом конца месяца
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

LocalDate d1 = LocalDate.of(2024,1,31);
LocalDate d2 = LocalDate.of(2024,2,29); // високосный год
long months = ChronoUnit.MONTHS.between(d1.withDayOfMonth(1), d2.withDayOfMonth(1));
System.out.println(months); // способ считать полные месяцы, учитывая края
1
Пример java
// 3. Совмещение Period и Duration для человекочитаемого результата
import java.time.*;

ZonedDateTime s = ZonedDateTime.of(2023,1,15,10,0,0,0, ZoneId.of("Europe/Moscow"));
ZonedDateTime e = ZonedDateTime.of(2024,4,20,12,30,0,0, ZoneId.of("Europe/Moscow"));
Period per = Period.between(s.toLocalDate(), e.toLocalDate());
ZonedDateTime interm = s.plus(per);
Duration dur = Duration.between(interm, e);
System.out.println(per); // P1Y3M5D
System.out.println(dur); // PT2H30M
P1Y3M5D
PT2H30M
Пример java
// 4. Учет перехода на летнее/зимнее время при вычислении интервала
import java.time.*;

ZonedDateTime a = ZonedDateTime.of(2024,3,30,1,30,0,0, ZoneId.of("Europe/Berlin"));
ZonedDateTime b = ZonedDateTime.of(2024,3,30,3,30,0,0, ZoneId.of("Europe/Berlin"));
System.out.println(Duration.between(a, b).toHours()); // может вернуть 1 или 2 в зависимости от перехода
1 или 2 (в зависимости от того, произошёл ли сдвиг времени в указанной зоне)
Пример java
// 5. Подсчет возраста в годах и месяцах с помощью ChronoUnit
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

LocalDate birth = LocalDate.of(1990,7,10);
LocalDate now = LocalDate.of(2024,4,20);
long years = ChronoUnit.YEARS.between(birth, now);
LocalDate tmp = birth.plusYears(years);
long months = ChronoUnit.MONTHS.between(tmp, now);
System.out.println(years + " years, " + months + " months");
33 years, 9 months

Пояснения: в сложных случаях рекомендуется разделять календарную и временную составляющие: Period для дат, Duration - для времени. ChronoUnit удобен для получения количества единиц, когда нужен простой числовой ответ.

джава between function comments

En
Between Calculates the amount of time between two temporal objects