Интерфейсы и frontend
January 21, 2024

"был в сети 15 минут назад" — учимся форматировать даты на фронтенде

Идея этого поста пришла ко мне во время кодревью, в процессе которого я вспомнил про такую штуку, как dayjs. Библиотека, которая на мой взгляд, в принципе не нужна, потому что существует Intl API (которым, к сожалению, мало кто пользуется). Причём про это даже в саркастичном ключе написал автор канала ExtremeCode. И ещё тогда я пересылал друзьям и писал, что смешно, что такое решение существует. Но пошутили и забыли, а тут я увидел его в MR и сразу забраковал. И дело вовсе не в саркастичных постах.

Если говорить кратко, dayjs это более современная замена moment.js. Только весит moment в распакованном виде 4.35 Мб, а dayjs всего 664 Кб. Впрочем, это не значит, что это решение в чём-то действительно лучше, поскольку, ни то, ни другое не использует браузерные API, а предоставляет вам свой собственный велосипед. И он действительно может быть неплох в каких-то моментах, но вряд ли вы будете спорить с тем, что решение, интегрированное в браузер будет менее оптимальным выбором.

В нашем случае, dayjs был втянут в проект только чтобы отрисовывать текст типа: "30 минут назад", вместо конкретной даты и времени. Именно это мне и не понравилось, что ради одного красивого вывода мы тянем целую отдельную библиотеку. В качестве альтернативы, я накидал небольшой пример для решения задачи, которую мой сотрудник пытался решить с помощью dayjs:

function countTimedeltaFromToday(date: Date) {
    const currDate = new Date();
    const delta = currDate.getTime() - date.getTime();

    const ONE_MILLISECOND = 1000;
    const HOUR_IN_SECONDS = 60 * 60;
    const DAY_IN_HOURS = 24;
    const MONTH_IN_DAYS = 30;
    const YEAR_IN_MONTH = 12;

    const daysDelta = delta / ONE_MILLISECOND / HOUR_IN_SECONDS / DAY_IN_HOURS;
    const monthsDelta = daysDelta / MONTH_IN_DAYS;
    const yearsDelta = monthsDelta / YEAR_IN_MONTH;

    return {
        delta,
        daysDelta,
        monthsDelta,
        yearsDelta,
    };
}

function formatDate(intl: Intl.RelativeTimeFormat, date: Date) {
    const MAX_DAYS_DELTA = 15;
    const MAX_MONTHS_DELTA = 11;

    const delta = countTimedeltaFromToday(date);

    const { daysDelta, monthsDelta, yearsDelta } = delta;

    if (daysDelta < MAX_DAYS_DELTA) {
        return intl.format(Math.floor(-daysDelta), 'day');
    }

    if (monthsDelta < MAX_MONTHS_DELTA) {
        return intl.format(Math.floor(-monthsDelta), 'month');
    }

    return intl.format(Math.floor(-yearsDelta), 'year');
}

const intl = new Intl.RelativeTimeFormat('ru', { style: 'long', numeric: 'auto' });

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