Точность до наносекунд

Библиотека js-joda реализует модель времени, основанную на API java.time. Одной из ключевых особенностей является поддержка времени с точностью до наносекунд. В отличие от стандартного объекта Date в JavaScript, ограниченного миллисекундами, Js-joda предоставляет полноценную работу с высокоточной временной шкалой.


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

Объект Date хранит время как количество миллисекунд с эпохи Unix:

const now = new Date()

console.log(now.getMilliseconds())

Проблемы такого подхода:

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

Для систем мониторинга, распределённых вычислений, трейдинга, телеметрии и научных расчётов миллисекунд недостаточно.


Что такое наносекунда

Наносекунда (nanosecond) — одна миллиардная доля секунды.

Соотношения единиц времени:

Единица Значение
1 секунда 1 000 миллисекунд
1 миллисекунда 1 000 микросекунд
1 микросекунда 1 000 наносекунд
1 секунда 1 000 000 000 наносекунд

Js-joda хранит наносекунды отдельно от секундной части времени, благодаря чему достигается высокая точность без потерь.


Класс Instant

Instant представляет момент времени на глобальной временной шкале UTC.

const { Instant } = require('@js-joda/core')

const instant = Instant.now()

console.log(instant.toString())

Пример вывода:

2026-05-24T12:45:18.123456789Z

Последние 9 цифр после точки — наносекунды.


Получение наносекунд

Метод nano() возвращает наносекундную часть времени.

const { LocalTime } = require('@js-joda/core')

const time = LocalTime.parse('12:30:15.123456789')

console.log(time.nano())

Результат:

123456789

Важно понимать:

  • nano() возвращает часть секунды;
  • значение всегда находится в диапазоне:
0 — 999999999

Создание времени с наносекундами

Через LocalTime

const { LocalTime } = require('@js-joda/core')

const time = LocalTime.of(
    10,
    15,
    30,
    987654321
)

console.log(time.toString())

Результат:

10:15:30.987654321

Аргументы метода:

Параметр Значение
hour часы
minute минуты
second секунды
nanoOfSecond наносекунды

Через LocalDateTime

const { LocalDateTime } = require('@js-joda/core')

const dateTime = LocalDateTime.of(
    2026,
    5,
    24,
    14,
    22,
    10,
    111222333
)

console.log(dateTime.toString())

Через Instant.ofEpochSecond

Метод позволяет задавать:

  • секунды эпохи Unix;
  • дополнительную наносекундную часть.
const { Instant } = require('@js-joda/core')

const instant = Instant.ofEpochSecond(
    1700000000,
    555666777
)

console.log(instant.toString())

Нормализация наносекунд

Если количество наносекунд превышает предел одной секунды, Js-joda автоматически нормализует значение.

const { Instant } = require('@js-joda/core')

const instant = Instant.ofEpochSecond(
    10,
    1_500_000_000
)

console.log(instant.toString())

Результат:

1970-01-01T00:00:11.500Z

Что произошло:

Исходно После нормализации
10 секунд 11 секунд
1 500 000 000 нс 500 000 000 нс

Добавление наносекунд

Метод plusNanos

const { LocalTime } = require('@js-joda/core')

const time = LocalTime.parse(
    '23:59:59.999999999'
)

const next = time.plusNanos(1)

console.log(next.toString())

Результат:

00:00

Произошёл переход через границу суток.


Вычитание наносекунд

Метод minusNanos

const { LocalTime } = require('@js-joda/core')

const time = LocalTime.parse(
    '00:00:00.000000001'
)

const prev = time.minusNanos(1)

console.log(prev.toString())

Результат:

23:59:59.999999999

Работа с Duration

Duration хранит промежуток времени с наносекундной точностью.

const { Duration } = require('@js-joda/core')

const duration = Duration.ofNanos(500)

console.log(duration.toString())

Результат:

PT0.0000005S

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

const { Duration } = require('@js-joda/core')

const a = Duration.ofNanos(400)
const b = Duration.ofNanos(700)

const result = a.plus(b)

console.log(result.toNanos())

Результат:

1100

Конвертация в наносекунды

Метод toNanos

const { Duration } = require('@js-joda/core')

const duration = Duration.ofSeconds(2)

console.log(duration.toNanos())

Результат:

2000000000

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

const {
    Instant,
    Duration
} = require('@js-joda/core')

const start = Instant.parse(
    '2026-05-24T10:00:00.000000100Z'
)

const end = Instant.parse(
    '2026-05-24T10:00:00.000000900Z'
)

const diff = Duration.between(start, end)

console.log(diff.toNanos())

Результат:

800

Форматирование наносекунд

Js-joda сохраняет точность при преобразовании в строку.

const { LocalTime } = require('@js-joda/core')

const time = LocalTime.of(
    12,
    0,
    0,
    123456789
)

console.log(time.toString())

Результат:

12:00:00.123456789

Усечение времени

Метод truncatedTo() позволяет отбросить лишнюю точность.

const {
    LocalTime,
    ChronoUnit
} = require('@js-joda/core')

const time = LocalTime.parse(
    '10:15:30.987654321'
)

const truncated = time.truncatedTo(
    ChronoUnit.MILLIS
)

console.log(truncated.toString())

Результат:

10:15:30.987

Единицы времени ChronoUnit

Для наносекунд используется:

ChronoUnit.NANOS

Пример:

const {
    LocalDateTime,
    ChronoUnit
} = require('@js-joda/core')

const dateTime = LocalDateTime.now()

const updated = dateTime.plus(
    500,
    ChronoUnit.NANOS
)

console.log(updated.toString())

Сравнение высокоточных временных значений

const { Instant } = require('@js-joda/core')

const a = Instant.parse(
    '2026-05-24T10:00:00.000000001Z'
)

const b = Instant.parse(
    '2026-05-24T10:00:00.000000002Z'
)

console.log(a.isBefore(b))

Результат:

true

Сравнение происходит с учётом наносекунд.


Потеря точности при преобразовании в Date

При конвертации в стандартный JavaScript Date наносекунды теряются.

const { Instant } = require('@js-joda/core')

const instant = Instant.parse(
    '2026-05-24T10:00:00.123456789Z'
)

const millis = instant.toEpochMilli()

console.log(millis)

Результат содержит только миллисекунды:

123

Часть:

456789

будет потеряна.


Сериализация JSON

Неправильный подход

JSON.stringify({
    time: new Date()
})

Точность ограничена миллисекундами.


Правильный подход

const { Instant } = require('@js-joda/core')

const payload = {
    time: Instant.now().toString()
}

console.log(JSON.stringify(payload))

Результат:

{
  "time":"2026-05-24T12:10:15.123456789Z"
}

Высокоточное логирование

const { Instant } = require('@js-joda/core')

function log(message) {
    const timestamp = Instant.now()

    console.log(
        `[${timestamp}] ${message}`
    )
}

log('Connection opened')

Пример:

[2026-05-24T12:10:15.123456789Z] Connection opened

Измерение производительности

const {
    Instant,
    Duration
} = require('@js-joda/core')

const start = Instant.now()

for (let i = 0; i < 1_000_000; i++) {}

const end = Instant.now()

const duration = Duration.between(
    start,
    end
)

console.log(duration.toNanos())

Работа с базами данных

Многие современные СУБД поддерживают наносекундную точность:

СУБД Поддержка
PostgreSQL микросекунды
Oracle наносекунды
SQL Server 100 нс
MongoDB миллисекунды

При использовании Js-joda важно учитывать ограничения конкретного драйвера и типа поля.


Ограничения JavaScript Number

JavaScript использует тип Number на основе IEEE 754.

Максимальное безопасное целое:

Number.MAX_SAFE_INTEGER

Результат:

9007199254740991

При работе с огромными временными интервалами в наносекундах возможна потеря точности.


Использование BigInt

Для сверхбольших интервалов применяется BigInt.

const nanos = 1234567890123456789n

console.log(nanos)

Js-joda внутри старается избегать проблем переполнения, но при интеграции с пользовательским кодом это необходимо учитывать.


Типичные ошибки

Ошибка: путаница миллисекунд и наносекунд

.plusNanos(1000)

Это:

1 микросекунда

а не 1 миллисекунда.


Ошибка: неверное количество цифр

LocalTime.parse(
    '10:00:00.123'
)

Это миллисекунды:

123000000 наносекунд

Ошибка: потеря точности через Date

new Date(
    instant.toEpochMilli()
)

Наносекунды исчезают.


Практический пример: высокоточная очередь событий

const {
    Instant
} = require('@js-joda/core')

const events = []

function addEvent(name) {
    events.push({
        name,
        createdAt: Instant.now()
    })
}

addEvent('task-1')
addEvent('task-2')

console.log(events)

Даже события, созданные почти одновременно, будут иметь разные наносекундные значения.


Практический пример: сортировка событий

events.sort((a, b) =>
    a.createdAt.compareTo(
        b.createdAt
    )
)

Сортировка выполняется с учётом наносекундной точности.


Практический пример: генерация уникальных timestamp

const { Instant } = require('@js-joda/core')

function createId() {
    return Instant.now()
        .toEpochMilli()
        .toString()
}

Для действительно уникальных идентификаторов миллисекунд недостаточно. Более надёжный вариант:

function createPreciseId() {
    return Instant.now().toString()
}

Производительность операций с наносекундами

Операции Js-joda являются неизменяемыми (immutable).

Каждое изменение создаёт новый объект:

const updated = instant.plusNanos(1)

Преимущества:

  • отсутствие побочных эффектов;
  • потокобезопасность;
  • предсказуемость вычислений.

Недостаток:

  • дополнительные выделения памяти при массовых операциях.

Поддерживаемые классы с наносекундами

Класс Поддержка наносекунд
Instant Да
LocalTime Да
LocalDateTime Да
OffsetDateTime Да
ZonedDateTime Да
Duration Да
Period Нет

Отличие Duration от Period

Duration

Хранит точное количество времени:

Duration.ofNanos(1)

Period

Хранит календарные единицы:

Period.ofDays(1)

Period не работает с наносекундами.


Высокоточные временные метки в UTC

Наиболее надёжный формат хранения:

2026-05-24T12:10:15.123456789Z

Особенности:

  • ISO-8601;
  • UTC;
  • наносекундная точность;
  • отсутствие неоднозначности временной зоны.

Рекомендации по использованию

Для хранения точного времени

Использовать:

Instant

Для локального времени

Использовать:

LocalTime
LocalDateTime

Для временных интервалов

Использовать:

Duration

Избегать преобразования в Date

Конвертацию выполнять только на границе интеграции со сторонними API.


Для сетевых протоколов

Передавать время в ISO-8601 строках:

instant.toString()

Для высоконагруженных систем

Учитывать:

  • стоимость создания immutable-объектов;
  • сериализацию;
  • ограничения СУБД;
  • потерю точности при интеграции со сторонними библиотеками.