В библиотеке js-joda работа с временными границами строится вокруг неизменяемых типов данных и явного преобразования дат и времени. Любая операция, связанная с началом или концом периода, не изменяет исходный объект, а возвращает новый экземпляр.
Граница периода определяется через:
LocalDate,
YearMonth, Year)LocalTime,
LocalDateTime, ZonedDateTime)TemporalAdjustersКлючевая особенность заключается в строгом разделении календарных и абсолютных временных представлений.
Для получения начала дня используется метод:
const start = localDate.atStartOfDay();
Результатом становится LocalDateTime со значением
00:00.
Пример:
import { LocalDate } from '@js-joda/core';
const date = LocalDate.of(2026, 5, 25);
const startOfDay = date.atStartOfDay();
startOfDay будет:
2026-05-25T00:00
При необходимости привязки к часовому поясу применяется преобразование:
const zoned = startOfDay.atZone(ZoneId.of('Europe/Moscow'));
В js-joda отсутствует прямой метод atEndOfDay, поскольку
концепция конца дня требует явного определения точности.
На практике используется переход к следующему дню с вычитанием минимального шага времени:
import { LocalTime } from '@js-joda/core';
const endOfDay = localDate
.plusDays(1)
.atStartOfDay()
.minusNanos(1);
Итоговое значение:
23:59:59.999999999
Такой подход обеспечивает точность до наносекунд и исключает двусмысленность относительно включительности границы.
Для перехода к первому дню месяца используется
TemporalAdjusters:
import { TemporalAdjusters } from '@js-joda/core';
const firstDay = localDate.with(TemporalAdjusters.firstDayOfMonth());
Результат всегда нормализуется к первому числу месяца с сохранением года и месяца.
Для определения последнего дня месяца применяется аналогичный механизм:
const lastDay = localDate.with(TemporalAdjusters.lastDayOfMonth());
При необходимости получения конца дня месяца:
const endOfMonth = localDate
.with(TemporalAdjusters.lastDayOfMonth())
.atTime(23, 59, 59, 999999999);
const firstDayOfYear = localDate.with(TemporalAdjusters.firstDayOfYear());
Результат:
YYYY-01-01
Для получения начала временной метки:
const startOfYear = firstDayOfYear.atStartOfDay();
const lastDayOfYear = localDate.with(TemporalAdjusters.lastDayOfYear());
Преобразование в конец дня:
const endOfYear = lastDayOfYear
.atTime(23, 59, 59, 999999999);
В js-joda неделя не имеет фиксированной глобальной структуры, поэтому
используется локализованный подход через DayOfWeek.
import { DayOfWeek, TemporalAdjusters } from '@js-joda/core';
const startOfWeek = localDate.with(
TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)
);
Результат всегда приводит дату к ближайшему понедельнику (или текущему, если он уже понедельник).
const endOfWeek = localDate.with(
TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)
);
Для получения конечной временной метки:
const endOfWeekDateTime = endOfWeek.atTime(23, 59, 59, 999999999);
При работе с абсолютным временем используются Instant и
ZonedDateTime.
const zonedStart = ZonedDateTime.of(
localDate,
LocalTime.MIN,
ZoneId.of('UTC')
);
Эквивалент:
YYYY-MM-DDT00:00Z
const zonedEnd = ZonedDateTime.of(
localDate,
LocalTime.MAX,
ZoneId.of('UTC')
);
LocalTime.MAX представляет максимально возможное время в
сутках:
23:59:59.999999999
В js-joda присутствуют специальные константы:
LocalTime.MIN → начало сутокLocalTime.MAX → конец сутокПрименение:
const start = LocalDateTime.of(localDate, LocalTime.MIN);
const end = LocalDateTime.of(localDate, LocalTime.MAX);
Особенность LocalTime.MAX заключается в включении
максимально возможной точности временной шкалы.
Границы периодов часто используются для формирования интервалов между моментами времени.
const start = localDate.atStartOfDay();
const end = localDate
.plusDays(1)
.atStartOfDay();
const interval = { start, end };
Такой интервал является полуоткрытым:
[start, end)
Это стандартная модель для временных диапазонов.
const startOfMonth = localDate
.with(TemporalAdjusters.firstDayOfMonth())
.atStartOfDay();
const endOfMonth = localDate
.with(TemporalAdjusters.lastDayOfMonth())
.plusDays(1)
.atStartOfDay();
Использование полуоткрытого интервала упрощает сравнение и исключает ошибки включения границ.
При вычислении границ важно учитывать нормализацию типов:
LocalDate → календарная дата без времениLocalDateTime → дата и время без зоныZonedDateTime → абсолютное время с зонойПереход между ними всегда явный:
localDate.atStartOfDay() // LocalDate → LocalDateTime
localDateTime.atZone(zoneId) // LocalDateTime → ZonedDateTime
zonedDateTime.toInstant() // ZonedDateTime → Instant
При использовании ZonedDateTime границы периода могут
изменяться из-за переходов летнего и зимнего времени.
Пример:
const start = ZonedDateTime.of(
localDate,
LocalTime.MIN,
ZoneId.of('Europe/Berlin')
);
В дни перехода времени:
LocalTime.MAX не всегда соответствует реальному
последнему моменту в календарном смыслеПоэтому для вычислений интервалов предпочтение отдается
Instant.
Для проверки попадания времени в диапазон используются сравнения
Instant:
const isInside =
instant.compareTo(start.toInstant()) >= 0 &&
instant.compareTo(end.toInstant()) < 0;
Полуоткрытый интервал обеспечивает однозначность:
const nextDayStart = localDate
.plusDays(1)
.atStartOfDay();
const previousMonthStart = localDate
.minusMonths(1)
.with(TemporalAdjusters.firstDayOfMonth())
.atStartOfDay();
const truncated = localDateTime
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
Аналог начала дня без преобразования типа.
При проектировании систем хранения временных интервалов часто применяется структура:
{
start: Instant,
end: Instant
}
Либо:
{
start: ZonedDateTime,
end: ZonedDateTime
}
Выбор зависит от необходимости учитывать часовые пояса.
js-joda оперирует наносекундной точностью:
999_999_999
наносекундПример:
const preciseEnd = localDate.atTime(23, 59, 59, 999999999);
Алгоритмически вычисление начала и конца периода сводится к:
TemporalAdjusters или
MINЭта модель обеспечивает согласованность для дня, месяца, года и произвольных календарных интервалов.