OzTime

JavaScript-библиотека для работы с датой и временем.

OzTime предоставляет неизменяемые объекты времени, фабричные функции создания экземпляров, арифметику по фиксированным и календарным единицам, сравнение, форматирование, работу с часовыми поясами, интервалы и длительности.

Содержание

Почему OzTime

OzTime подойдёт, если нужна небольшая и понятная библиотека без перегруженного API.

Основные идеи:

  • время хранится как Unix timestamp в миллисекундах;
  • экземпляры OzTime неизменяемы;
  • часовой пояс и локаль сохраняются как часть объекта;
  • арифметика, сравнение и форматирование работают через единый API.

Библиотека хорошо подходит для:

  • прикладных JavaScript-проектов;
  • учебных и дипломных проектов;
  • небольших библиотек и утилит;
  • кода, где важна предсказуемость и неизменяемость.

Возможности

  • Неизменяемые экземпляры OzTime
  • Фабричные функции: now, fromTimestamp, fromDate, fromISO, fromComponents
  • Арифметика по времени: add, subtract
  • Сравнение: isSame, isBefore, isAfter, isBetween
  • Форматирование по шаблону
  • Работа с часовыми поясами: setTimezone, getTimezoneOffset
  • Интервалы: Interval, interval
  • Длительности: Duration, duration
  • Календарные утилиты: isLeapYear, daysInMonth

Установка

npm install @alexstukovnikov/oz-time

Быстрый старт

import { now, fromISO, duration, interval } from '@alexstukovnikov/oz-time';

const current = now('Europe/Moscow', 'ru-RU');
const release = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');

const nextWeek = release.add(7, 'day');
const diffHours = nextWeek.diff(release, 'hour');

console.log(current.getTimezone()); // ожидаемый результат: Europe/Moscow
console.log(nextWeek.format('DD.MM.YYYY HH:mm')); // ожидаемый результат: 01.06.2024 12:00
console.log(diffHours); // ожидаемый результат: 168

const range = interval(release, nextWeek);
console.log(range.contains(release)); // ожидаемый результат: true

const oneHour = duration(1, 'hour');
console.log(oneHour.asMinutes()); // ожидаемый результат: 60

Цепочка операций

import { fromISO } from '@alexstukovnikov/oz-time';

const result = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU')
    .add(1, 'day')
    .add(2, 'hour')
    .subtract(30, 'minute')
    .setTimezone('Europe/Moscow')
    .format('DD.MM.YYYY HH:mm:ss');

console.log(result);

Основные сценарии

Сценарий Что использовать Почему
Нужен текущий момент времени now() Самый простой способ получить новый экземпляр OzTime для текущего момента
Есть Unix timestamp в миллисекундах fromTimestamp() Подходит для работы с временем из API, БД и системных источников
Есть нативный Date fromDate() Удобно интегрировать библиотеку в существующий JavaScript-код
Есть ISO-строка fromISO() Подходит для внешних API и сериализованных значений времени
Нужно создать дату вручную из компонентов fromComponents() Удобно, когда год, месяц, день и время приходят по отдельности
Нужно прибавить или вычесть время add() / subtract() Возвращают новый экземпляр и не изменяют исходный объект
Нужно сравнить два значения isSame() / isBefore() / isAfter() Дают читаемый API для логики сравнения
Нужно проверить попадание в диапазон isBetween() Удобнее, чем писать проверку вручную
Нужно получить строковое представление format() Форматирование по шаблону с токенами
Нужно изменить отображаемый часовой пояс setTimezone() Меняет timezone у нового экземпляра, не меняя абсолютный момент времени
Нужно узнать смещение относительно UTC getTimezoneOffset() Полезно для отображения, отладки и расчётов
Нужно описать диапазон времени Interval / interval() Подходит для проверки попадания, пересечений и длительности интервала
Нужно описать фиксированную длительность Duration / duration() Удобно для преобразования между миллисекундами, секундами, минутами, часами и днями
Нужно проверить високосный год isLeapYear() Простая календарная утилита
Нужно узнать число дней в месяце daysInMonth() Удобно для валидации и генерации дат

Создание экземпляров

import { now, fromTimestamp, fromDate, fromISO, fromComponents } from '@alexstukovnikov/oz-time';

const a = now('Europe/Moscow', 'ru-RU');
const b = fromTimestamp(1716638400000, 'UTC', 'ru-RU');
const c = fromDate(new Date('2024-05-25T12:00:00Z'), 'UTC', 'ru-RU');
const d = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');
const e = fromComponents(2024, 5, 25, 12, 0, 0, 0, 'UTC', 'ru-RU');
  • now() — создаёт экземпляр OzTime для текущего момента времени.
  • fromTimestamp() — создаёт экземпляр из Unix timestamp в миллисекундах.
  • fromDate() — создаёт экземпляр из объекта Date.
  • fromISO() — создаёт экземпляр из ISO-строки.
  • fromComponents() — создаёт экземпляр из отдельных компонентов даты и времени.

Арифметика

Все арифметические операции возвращают новый экземпляр OzTime и не изменяют исходный объект.

Добавление и вычитание

import { fromISO } from '@alexstukovnikov/oz-time';

const time = fromISO('2024-05-25T12:00:00Z');

console.log(time.add(1, 'day').toISOString()); // ожидаемый результат: 2024-05-26T12:00:00.000Z
console.log(time.subtract(2, 'hour').toISOString()); // ожидаемый результат: 2024-05-25T10:00:00.000Z

Разница между датами

import { fromISO } from '@alexstukovnikov/oz-time';

const start = fromISO('2024-05-25T12:00:00Z');
const end = fromISO('2024-05-25T15:00:00Z');

console.log(end.diff(start, 'hour')); // ожидаемый результат: 3

Календарная арифметика

import { fromISO } from '@alexstukovnikov/oz-time';

const date = fromISO('2024-01-31T00:00:00Z');
const result = date.add(1, 'month');

console.log(result.toISOString()); // ожидаемый результат: 2024-02-29T00:00:00.000Z

Сравнение

import { fromISO } from '@alexstukovnikov/oz-time';

const a = fromISO('2024-05-25T12:00:00.100Z');
const b = fromISO('2024-05-25T12:00:00.900Z');
const c = fromISO('2024-05-26T12:00:00Z');

console.log(a.isSame(b, 'second')); // ожидаемый результат: true
console.log(a.isBefore(c)); // ожидаемый результат: true
console.log(c.isAfter(a)); // ожидаемый результат: true
console.log(a.isBetween(a.subtract(1, 'day'), c)); // ожидаемый результат: true

Форматирование

Функция format() и метод OzTime#format() поддерживают шаблоны форматирования.

import { fromISO } from '@alexstukovnikov/oz-time';

const time = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');

console.log(time.format('DD.MM.YYYY HH:mm')); // ожидаемый результат: 25.05.2024 12:00
console.log(time.format('dddd, D MMMM YYYY', 'ru-RU')); // пример: суббота, 25 мая 2024

Часовые пояса

OzTime хранит абсолютное время как timestamp, а часовой пояс используется как метаданные для отображения и форматирования.

Смена часового пояса

import { fromISO } from '@alexstukovnikov/oz-time';

const time = fromISO('2024-05-25T12:00:00Z', 'UTC', 'ru-RU');
const moscow = time.setTimezone('Europe/Moscow');

console.log(moscow.getTimezone()); // ожидаемый результат: Europe/Moscow

Смещение относительно UTC

import { fromISO } from '@alexstukovnikov/oz-time';

const time = fromISO('2024-05-25T12:00:00Z', 'Europe/Moscow', 'ru-RU');
console.log(time.getTimezoneOffset()); // ожидаемый результат: 180

Интервалы и длительности

Интервалы

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

import { interval, fromISO } from '@alexstukovnikov/oz-time';

const start = fromISO('2024-05-25T10:00:00Z');
const end = fromISO('2024-05-25T12:00:00Z');
const range = interval(start, end);

console.log(range.contains(fromISO('2024-05-25T11:00:00Z'))); // ожидаемый результат: true
console.log(range.duration('hour')); // ожидаемый результат: 2

Пересечение интервалов

import { Interval, fromISO } from '@alexstukovnikov/oz-time';

const a = new Interval(fromISO('2024-05-25T10:00:00Z'), fromISO('2024-05-25T12:00:00Z'));

const b = new Interval(fromISO('2024-05-25T11:00:00Z'), fromISO('2024-05-25T13:00:00Z'));

console.log(a.overlaps(b)); // ожидаемый результат: true

Длительности

Duration используется для работы с фиксированными единицами времени.

import { duration } from '@alexstukovnikov/oz-time';

const twoHours = duration(2, 'hour');

console.log(twoHours.asMilliseconds()); // ожидаемый результат: 7200000
console.log(twoHours.asMinutes()); // ожидаемый результат: 120
console.log(twoHours.asHours()); // ожидаемый результат: 2

Сложение длительностей

import { duration } from '@alexstukovnikov/oz-time';

const a = duration(30, 'minute');
const b = duration(45, 'minute');

console.log(a.add(b).asMinutes()); // ожидаемый результат: 75

Поддерживаемые единицы времени

Поддерживаются канонические имена и алиасы.

Единица Каноническое имя Алиасы
Миллисекунда millisecond milliseconds, ms
Секунда second seconds, s
Минута minute minutes, m
Час hour hours, h
День day days, d
Месяц month months
Год year years, y

Фиксированные единицы:

  • millisecond
  • second
  • minute
  • hour
  • day

Календарные единицы:

  • month
  • year

Токены форматирования

Токен Описание Пример
YYYY Год из 4 цифр 2024
YY Короткий год 24
MMMM Полное название месяца май / May
MMM Короткое название месяца мая / May
MM Месяц с ведущим нулём 05
M Месяц без ведущего нуля 5
dddd Полное название дня недели суббота
ddd Короткое название дня недели сб
DD День месяца с ведущим нулём 25
D День месяца без ведущего нуля 25
HH Часы в 24-часовом формате 09, 18
H Часы в 24-часовом формате без нуля 9, 18
hh Часы в 12-часовом формате 01, 12
h Часы в 12-часовом формате без нуля 1, 12
mm Минуты 07
ss Секунды 05
SSS Миллисекунды 123
A AM/PM AM, PM

FAQ

Чем fromISO() отличается от fromTimestamp()?

fromISO() принимает строку в формате ISO 8601 и сначала парсит её, а fromTimestamp() принимает уже готовое число миллисекунд. Если данные приходят из JSON API как строка, чаще удобнее fromISO(). Если уже есть timestamp из базы, системы или вычислений, лучше использовать fromTimestamp().

Меняет ли setTimezone() сам момент времени?

Нет. setTimezone() не меняет абсолютный момент времени. Он возвращает новый экземпляр с тем же timestamp, но с другим часовым поясом для форматирования и вычисления смещения.

Изменяют ли методы add() и subtract() текущий объект?

Нет. OzTime построен как неизменяемый объект. Все операции возвращают новый экземпляр, а исходный объект остаётся без изменений.

Почему Duration не поддерживает month и year?

Потому что Duration предназначен только для фиксированных единиц времени, которые можно точно перевести в миллисекунды. Месяцы и годы имеют переменную длину, поэтому для них используется календарная арифметика через OzTime#add() и OzTime#diff().

Почему diff(..., 'month') и diff(..., 'year') работают не так, как разница в миллисекундах?

Потому что для месяцев и лет применяется календарная логика, а не деление на фиксированное число миллисекунд. Это позволяет корректнее учитывать разную длину месяцев и переходы между датами.

Что лучше использовать: метод экземпляра или функцию модуля?

Если уже есть экземпляр OzTime, обычно удобнее использовать методы экземпляра: time.add(...), time.format(...), time.isBefore(...). Если нужен функциональный стиль или работаешь с импортированными утилитами напрямую, можно использовать функции модулей.

В каком часовом поясе хранится время внутри OzTime?

Внутри хранится Unix timestamp в миллисекундах. Это абсолютное значение времени. Часовой пояс хранится отдельно как метаданные экземпляра.

Можно ли использовать библиотеку без locale и timezone?

Да. По умолчанию используются:

  • timezone = 'UTC'
  • locale = 'en-US'

Когда использовать Interval, а когда Duration?

Используй Interval, когда есть начало и конец диапазона. Используй Duration, когда нужна именно фиксированная длина времени, например 2 часа, 30 минут или 7 дней.

Ограничения и особенности

  • Внутреннее значение времени хранится как Unix timestamp в миллисекундах
  • Часовой пояс не меняет абсолютный момент времени, а влияет на форматирование и вычисление смещения
  • Методы add, subtract, setTimezone и другие операции не изменяют текущий экземпляр, а возвращают новый
  • Duration поддерживает только фиксированные единицы времени
  • Календарная арифметика через month и year учитывает реальную длину месяцев
  • Разница в month и year считается календарно, а не через точное число миллисекунд
  • Поддержка часовых поясов зависит от Intl и доступных IANA time zone в среде выполнения

Лицензия

ISC