Точные вычисления времени

Работа с датами и временем в JavaScript сопровождается множеством скрытых сложностей:

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

Библиотека date-fns предоставляет набор функций, позволяющих выполнять вычисления времени предсказуемо и безопасно.


Разница между календарными и точными вычислениями

В библиотеке существует важное разделение:

  • calendar calculations — календарные вычисления;
  • exact calculations — точные вычисления.

Например:

import { differenceInDays } from 'date-fns'

const start = new Date('2025-01-01')
const end = new Date('2025-01-10')

console.log(differenceInDays(end, start))

Результат:

9

Функция учитывает календарные сутки.

Однако существуют ситуации, когда разница должна измеряться строго по миллисекундам.


Вычисление разницы во времени через миллисекунды

differenceInMilliseconds

import { differenceInMilliseconds } from 'date-fns'

const start = new Date('2025-01-01T10:00:00')
const end = new Date('2025-01-01T10:00:01')

console.log(differenceInMilliseconds(end, start))

Результат:

1000

Функция возвращает строгое количество миллисекунд между двумя моментами времени.


Разница в секундах

differenceInSeconds

import { differenceInSeconds } from 'date-fns'

const start = new Date('2025-01-01T10:00:00')
const end = new Date('2025-01-01T10:00:45')

console.log(differenceInSeconds(end, start))

Результат:

45

Разница в минутах

differenceInMinutes

import { differenceInMinutes } from 'date-fns'

const start = new Date('2025-01-01T10:00:00')
const end = new Date('2025-01-01T10:42:00')

console.log(differenceInMinutes(end, start))

Результат:

42

Разница в часах

differenceInHours

import { differenceInHours } from 'date-fns'

const start = new Date('2025-01-01T08:00:00')
const end = new Date('2025-01-01T20:00:00')

console.log(differenceInHours(end, start))

Результат:

12

Разница в днях

differenceInDays

import { differenceInDays } from 'date-fns'

const start = new Date('2025-01-01')
const end = new Date('2025-01-15')

console.log(differenceInDays(end, start))

Результат:

14

Разница в неделях

differenceInWeeks

import { differenceInWeeks } from 'date-fns'

const start = new Date('2025-01-01')
const end = new Date('2025-02-01')

console.log(differenceInWeeks(end, start))

Разница в месяцах

differenceInMonths

import { differenceInMonths } from 'date-fns'

const start = new Date('2025-01-01')
const end = new Date('2025-06-01')

console.log(differenceInMonths(end, start))

Результат:

5

Разница в годах

differenceInYears

import { differenceInYears } from 'date-fns'

const start = new Date('2020-01-01')
const end = new Date('2025-01-01')

console.log(differenceInYears(end, start))

Результат:

5

Точные вычисления через timestamp

JavaScript хранит дату как количество миллисекунд с 1 января 1970 года UTC.

Получение timestamp:

const date = new Date()

console.log(date.getTime())

В date-fns многие функции используют именно timestamp для точных вычислений.


Сложение времени

addMilliseconds

import { addMilliseconds } from 'date-fns'

const now = new Date()

const result = addMilliseconds(now, 500)

addSeconds

import { addSeconds } from 'date-fns'

const now = new Date()

const result = addSeconds(now, 30)

addMinutes

import { addMinutes } from 'date-fns'

const now = new Date()

const result = addMinutes(now, 15)

addHours

import { addHours } from 'date-fns'

const now = new Date()

const result = addHours(now, 6)

addDays

import { addDays } from 'date-fns'

const now = new Date()

const result = addDays(now, 7)

Вычитание времени

subMinutes

import { subMinutes } from 'date-fns'

const now = new Date()

const result = subMinutes(now, 10)

subHours

import { subHours } from 'date-fns'

const now = new Date()

const result = subHours(now, 3)

subDays

import { subDays } from 'date-fns'

const now = new Date()

const result = subDays(now, 30)

Интервалы времени

intervalToDuration

Функция преобразует разницу между двумя датами в объект длительности.

import { intervalToDuration } from 'date-fns'

const start = new Date('2025-01-01')
const end = new Date('2025-03-15')

const duration = intervalToDuration({
  start,
  end
})

console.log(duration)

Результат:

{
  months: 2,
  days: 14
}

Преобразование длительности

formatDuration

import { intervalToDuration, formatDuration } from 'date-fns'

const duration = intervalToDuration({
  start: new Date('2025-01-01'),
  end: new Date('2025-01-03')
})

console.log(formatDuration(duration))

Результат:

2 days

Работа с Unix Time

fromUnixTime

Unix timestamp хранится в секундах.

import { fromUnixTime } from 'date-fns'

const date = fromUnixTime(1735689600)

console.log(date)

getUnixTime

import { getUnixTime } from 'date-fns'

const date = new Date()

console.log(getUnixTime(date))

Измерение времени выполнения операций

Практический пример

import { differenceInMilliseconds } from 'date-fns'

const start = new Date()

for (let i = 0; i < 1000000; i++) {
  Math.sqrt(i)
}

const end = new Date()

console.log(
  differenceInMilliseconds(end, start)
)

Проверка попадания времени в интервал

isWithinInterval

import { isWithinInterval } from 'date-fns'

const target = new Date('2025-01-10')

const result = isWithinInterval(target, {
  start: new Date('2025-01-01'),
  end: new Date('2025-01-31')
})

console.log(result)

Результат:

true

Нормализация времени

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

startOfMinute

import { startOfMinute } from 'date-fns'

const now = new Date()

console.log(startOfMinute(now))

startOfHour

import { startOfHour } from 'date-fns'

const now = new Date()

console.log(startOfHour(now))

startOfDay

import { startOfDay } from 'date-fns'

const now = new Date()

console.log(startOfDay(now))

Округление времени

roundToNearestMinutes

import { roundToNearestMinutes } from 'date-fns'

const date = new Date('2025-01-01T10:07:00')

const rounded = roundToNearestMinutes(date, {
  nearestTo: 5
})

console.log(rounded)

Результат:

10:05

Максимальная и минимальная даты

max

import { max } from 'date-fns'

const result = max([
  new Date('2025-01-01'),
  new Date('2025-03-01'),
  new Date('2025-02-01')
])

console.log(result)

min

import { min } from 'date-fns'

const result = min([
  new Date('2025-01-01'),
  new Date('2025-03-01'),
  new Date('2025-02-01')
])

console.log(result)

Сравнение дат

isAfter

import { isAfter } from 'date-fns'

const result = isAfter(
  new Date('2025-02-01'),
  new Date('2025-01-01')
)

console.log(result)

isBefore

import { isBefore } from 'date-fns'

const result = isBefore(
  new Date('2025-01-01'),
  new Date('2025-02-01')
)

console.log(result)

isEqual

import { isEqual } from 'date-fns'

const result = isEqual(
  new Date('2025-01-01'),
  new Date('2025-01-01')
)

console.log(result)

Работа с часовыми поясами

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

Для точных вычислений обычно применяются:

  • UTC;
  • Unix timestamp;
  • ISO-строки.

Использование ISO-формата

const date = new Date('2025-01-01T10:00:00Z')

Символ Z означает UTC.

Это позволяет избежать неоднозначностей локального времени.


Проблема перехода на летнее время

Некоторые сутки могут содержать:

  • 23 часа;
  • 24 часа;
  • 25 часов.

Из-за этого вычисление:

24 * 60 * 60 * 1000

не всегда эквивалентно одному календарному дню.

Поэтому:

addDays(date, 1)

предпочтительнее ручного прибавления миллисекунд.


Различие между addDays и addHours

import { addDays, addHours } from 'date-fns'

const date = new Date()

const a = addDays(date, 1)
const b = addHours(date, 24)

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


Безопасные вычисления времени

Рекомендуемые практики:

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

Практический пример: таймер обратного отсчёта

import {
  differenceInSeconds,
  intervalToDuration
} from 'date-fns'

const finish = new Date('2025-12-31T23:59:59')

setInterval(() => {
  const now = new Date()

  const seconds = differenceInSeconds(
    finish,
    now
  )

  const duration = intervalToDuration({
    start: now,
    end: finish
  })

  console.log(seconds)
  console.log(duration)
}, 1000)

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

import {
  isAfter,
  addDays
} from 'date-fns'

const createdAt = new Date('2025-01-01')

const expiresAt = addDays(createdAt, 30)

const expired = isAfter(
  new Date(),
  expiresAt
)

console.log(expired)

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

import { differenceInYears } from 'date-fns'

const birthDate = new Date('1995-06-10')

const age = differenceInYears(
  new Date(),
  birthDate
)

console.log(age)

Практический пример: определение длительности сессии

import {
  differenceInMinutes,
  differenceInSeconds
} from 'date-fns'

const login = new Date('2025-01-01T10:00:00')
const logout = new Date('2025-01-01T11:45:30')

console.log(
  differenceInMinutes(logout, login)
)

console.log(
  differenceInSeconds(logout, login)
)

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

const events = [
  { date: new Date('2025-03-01') },
  { date: new Date('2025-01-01') },
  { date: new Date('2025-02-01') }
]

events.sort(
  (a, b) => a.date - b.date
)

console.log(events)