modules/duration.js

import { isFixedUnit, unitToMilliseconds, normalizeUnit } from '../utils/units.js';

/**
 * Модуль для работы с фиксированными длительностями.
 *
 * @module modules/duration
 */

/**
 * Проверяет корректность числового значения amount.
 *
 * @private
 * @param {number} amount - Количество единиц времени.
 * @throws {TypeError} Выбрасывается, если amount не является корректным числом.
 * @returns {void}
 */
function assertAmount(amount) {
    if (typeof amount !== 'number' || Number.isNaN(amount)) {
        throw new TypeError('amount must be a valid number');
    }
}

/**
 * Представляет фиксированную длительность, хранящуюся в миллисекундах.
 *
 * @class
 * @example
 * import { Duration } from '@alexstukovnikov/oz-time';
 *
 * const duration = new Duration(3600000);
 * console.log(duration.asHours()); // ожидаемый результат: 1
 */
export class Duration {
    /**
     * Создаёт экземпляр Duration.
     *
     * @param {number} milliseconds - Длительность в миллисекундах.
     * @throws {TypeError} Выбрасывается, если milliseconds некорректен.
     */
    constructor(milliseconds) {
        if (typeof milliseconds !== 'number' || Number.isNaN(milliseconds)) {
            throw new TypeError('Duration: milliseconds must be a valid number');
        }

        this._milliseconds = milliseconds;
    }

    /**
     * Возвращает длительность в миллисекундах.
     *
     * @returns {number} Длительность в миллисекундах.
     * @example
     * import { duration } from '@alexstukovnikov/oz-time';
     *
     * const value = duration(3600000, 'millisecond');
     * console.log(value.asMilliseconds()); // ожидаемый результат: 3600000
     */
    asMilliseconds() {
        return this._milliseconds;
    }

    /**
     * Возвращает длительность в секундах.
     *
     * @returns {number} Длительность в секундах.
     * @example
     * import { duration } from '@alexstukovnikov/oz-time';
     *
     * const value = duration(3600000, 'millisecond');
     * console.log(value.asSeconds()); // ожидаемый результат: 3600
     */
    asSeconds() {
        return this._milliseconds / unitToMilliseconds('second');
    }

    /**
     * Возвращает длительность в минутах.
     *
     * @returns {number} Длительность в минутах.
     * @example
     * import { duration } from '@alexstukovnikov/oz-time';
     *
     * const value = duration(3600000, 'millisecond');
     * console.log(value.asMinutes()); // ожидаемый результат: 60
     */
    asMinutes() {
        return this._milliseconds / unitToMilliseconds('minute');
    }

    /**
     * Возвращает длительность в часах.
     *
     * @returns {number} Длительность в часах.
     * @example
     * import { duration } from '@alexstukovnikov/oz-time';
     *
     * const value = duration(36000000, 'millisecond');
     * console.log(value.asHours()); // ожидаемый результат: 10
     */
    asHours() {
        return this._milliseconds / unitToMilliseconds('hour');
    }

    /**
     * Возвращает длительность в днях.
     *
     * @returns {number} Длительность в днях.
     * @example
     * import { duration } from '@alexstukovnikov/oz-time';
     *
     * const value = duration(120, 'hour');
     * console.log(value.asDays()); // ожидаемый результат: 5
     */
    asDays() {
        return this._milliseconds / unitToMilliseconds('day');
    }

    /**
     * Возвращает новую длительность, равную сумме текущей и переданной длительности.
     *
     * @param {Duration} other - Вторая длительность.
     * @throws {TypeError} Выбрасывается, если other не является экземпляром Duration.
     * @returns {Duration} Новая длительность, равная сумме двух значений.
     * @example
     * import { duration } from '@alexstukovnikov/oz-time';
     *
     * const a = duration(1, 'second');
     * const b = duration(2, 'second');
     * console.log(a.add(b).asMilliseconds()); // ожидаемый результат: 3000
     */
    add(other) {
        if (!(other instanceof Duration)) {
            throw new TypeError('Duration.add: other must be Duration');
        }

        return new Duration(this._milliseconds + other._milliseconds);
    }
}

/**
 * Создаёт и возвращает экземпляр {@link Duration} из числа
 * и фиксированной единицы времени.
 *
 * @param {number} amount - Количество единиц времени.
 * @param {string} unit - Фиксированная единица времени.
 * @throws {TypeError} Выбрасывается, если amount некорректен.
 * @throws {Error} Выбрасывается, если unit не является фиксированной единицей времени.
 * @returns {Duration} Экземпляр Duration.
 * @example
 * import { duration } from '@alexstukovnikov/oz-time';
 *
 * const value = duration(2, 'hour');
 * console.log(value.asMinutes()); // ожидаемый результат: 120
 */
export function duration(amount, unit) {
    assertAmount(amount);

    const normalizedUnit = normalizeUnit(unit);

    if (!isFixedUnit(normalizedUnit)) {
        throw new Error(`duration supports only fixed units: ${unit}`);
    }

    return new Duration(amount * unitToMilliseconds(normalizedUnit));
}