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

Вычисление возраста в задачах JavaScript-обработки дат опирается на точное понимание календарной разницы между двумя моментами времени. В экосистеме работы с датами библиотека Day.js используется как компактная альтернатива Moment.js, предоставляя иммутабельную модель дат и цепочечный API, достаточный для большинства прикладных сценариев, включая расчёт возраста человека, аккаунта, события или любого временного интервала.

Возраст — это количество полных лет, прошедших между датой рождения и текущей датой. Ключевой момент заключается в том, что простое вычитание годов календаря не всегда даёт корректный результат. Необходимо учитывать, наступил ли уже день рождения в текущем году.

Формально задача сводится к вычислению:

  • разницы в годах между текущей датой и датой рождения
  • корректировки результата, если текущая дата находится до дня рождения в текущем году

Базовый подход через diff

В Day.js предусмотрен метод diff, позволяющий вычислять разницу между датами в различных единицах измерения.

Простейшая реализация возраста:

import dayjs from 'dayjs';

const birthDate = dayjs('1995-06-10');
const now = dayjs();

const age = now.diff(birthDate, 'year');

Метод diff с параметром 'year' возвращает количество полных лет между датами, автоматически отбрасывая неполные периоды.

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

Корректировка с учётом дня рождения

Более точный способ заключается в проверке, наступил ли день рождения в текущем году. Логика строится на сравнении текущей даты с датой рождения, «перенесённой» в текущий год.

import dayjs from 'dayjs';

const birthDate = dayjs('1995-06-10');
const today = dayjs();

let age = today.year() - birthDate.year();

const birthdayThisYear = birthDate.year(today.year());

if (today.isBefore(birthdayThisYear, 'day')) {
  age -= 1;
}

Здесь используется ключевой приём:

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

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

Работа с временными зонами и UTC

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

Day.js поддерживает плагины utc и timezone, позволяющие фиксировать базовую систему отсчёта.

Пример с UTC:

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';

dayjs.extend(utc);

const birthDate = dayjs.utc('1995-06-10');
const now = dayjs.utc();

let age = now.year() - birthDate.year();

const birthdayThisYear = birthDate.year(now.year());

if (now.isBefore(birthdayThisYear, 'day')) {
  age -= 1;
}

Использование UTC устраняет расхождения, связанные с локальными смещениями времени, особенно в распределённых системах.

Альтернативная реализация через startOf года

Ещё один способ повышения читаемости логики — использование начала года как контрольной точки.

import dayjs from 'dayjs';

const birthDate = dayjs('1995-06-10');
const today = dayjs();

const yearsDiff = today.diff(birthDate, 'year');
const birthdayPassed = birthDate
  .add(yearsDiff, 'year')
  .isSameOrBefore(today, 'day');

const age = birthdayPassed ? yearsDiff : yearsDiff - 1;

Здесь вычисляется предварительная разница, после чего проверяется корректность попадания в текущий жизненный цикл года.

Обработка пограничных дат

При вычислении возраста критичны следующие случаи:

Дата рождения — 29 февраля

В високосные годы возникают дополнительные сложности:

const birthDate = dayjs('2000-02-29');
const today = dayjs();

let age = today.diff(birthDate, 'year');
const leapYearBirthday = birthDate.year(today.year());

if (today.isBefore(leapYearBirthday, 'day')) {
  age -= 1;
}

В не високосные годы Day.js автоматически интерпретирует 29 февраля как 1 марта при операциях с датами, что может потребовать дополнительной бизнес-логики.

Дата рождения в будущем

Если дата рождения больше текущей даты:

const age = Math.max(0, dayjs().diff(dayjs('2030-01-01'), 'year'));

Без ограничений результат diff может быть отрицательным, что не всегда допустимо в прикладных системах.

Инкапсуляция логики вычисления возраста

В прикладных системах вычисление возраста часто выносится в отдельную функцию для обеспечения повторного использования:

import dayjs from 'dayjs';

export function calculateAge(dateString) {
  const birthDate = dayjs(dateString);
  const today = dayjs();

  let age = today.diff(birthDate, 'year');

  const birthdayThisYear = birthDate.year(today.year());

  if (today.isBefore(birthdayThisYear, 'day')) {
    age -= 1;
  }

  return age < 0 ? 0 : age;
}

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

Производственные особенности

В высоконагруженных системах использование Day.js для вычисления возраста остаётся эффективным благодаря:

  • иммутабельности объектов
  • отсутствию тяжёлых зависимостей
  • минимальному размеру библиотеки
  • предсказуемости операций diff

Однако при массовых вычислениях (например, обработка миллионов записей) имеет смысл минимизировать создание временных объектов и заранее нормализовать даты рождения.

Типовые ошибки при вычислении возраста

  • использование diff('year') без проверки дня рождения
  • игнорирование часового пояса
  • сравнение строк дат вместо объектов Day.js
  • отсутствие обработки будущих дат
  • неверная работа с 29 февраля без дополнительной логики

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