GrRelativeTime

Пакет: @feugene/granularity-chronoспутникГруппа: Прочее

Берут, когда свежесть важнее точности.

Когда брать

  • свежесть важнее точности — лента, комментарии, уведомления, аудит-лог: «5 минут назад» читается быстрее, чем «12.08.2026, 14:32»;
  • метка обязана оставаться вернойlive пересчитывает текст, и «только что» не висит час спустя;
  • меток на странице много — сотня меток стоит одного setInterval: такт общий и на скрытой вкладке снимается;
  • нужны обе формы — точная дата уезжает в title и в слот, поэтому наведение и скринридер дают абсолютный момент.

Когда взять другое

НужноБерите
Выбрать дату, а не показатьGrDatePicker
Выбрать момент со временемGrDateTimePicker
Показать последовательность событийGrTimeline
Показать обратный отсчёт до дедлайнасвоя обвязка над useChronoNow

Точные значения так не показывают. Договор, платёж, расписание рейса требуют даты: «через 2 дня» не отвечает на вопрос «какого числа». cutoff задаёт порог, после которого компонент сам переходит на абсолютную форму, — и для таких мест его выставляют, а не отключают живой пересчёт.

Единица выбирается двумя способами, и это видно на границах

До суток разрыв меряется прошедшим временем — секунда есть секунда, календарь тут ни при чём. От суток и дальше — календарными кортежами: дни, недели, полные месяцы, годы.

Отсюда два следствия. Месяц остаётся месяцем и в феврале, и в июле, потому что считается календарём. А сутки, длившиеся из-за перевода часов 23 часа, называются часами — потому что столько времени и правда прошло; иначе двухчасовой разрыв через полночь каждую ночь назывался бы вчерашним.

Такт зависит от единицы

Секунды пересчитываются раз в пять секунд, месяцы — раз в час. Одинаковый такт для всех означал бы либо секунду, дёргающую сотню меток впустую, либо минуту, из-за которой «только что» держится дольше, чем это правда.

Установка

npm i @feugene/granularity-chrono

Импорт

import { GrRelativeTime } from '@feugene/granularity-chrono/components/GrRelativeTime'

API

API этого компонента ещё не посчитан: генератор витрины пока обходит только ядро. Пока его нет, справочник — в документации пакета.

Примеры 3

Cutoff

Cutoff
<script setup lang="ts">
// `GrRelativeTime` подставляется авто-импортом (`unplugin-vue-components`).

const now = new Date(2026, 7, 12, 12, 0)

const posts = [
  new Date(2026, 7, 12, 9, 30),
  new Date(2026, 7, 5, 12, 0),
  new Date(2026, 6, 2, 12, 0),
  new Date(2025, 10, 20, 12, 0),
]

/** Вид абсолютной даты — опциями `Intl`, а не строкой-паттерном. */
const format: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short', year: 'numeric' }
</script>

<template>
  <div class="grid gap-2">
    <p class="showcase-demo-text text-sm opacity-70">
      Старше 30 дней — обычная дата: «347 дней назад» не помогает никому.
    </p>

    <div v-for="post in posts" :key="post.toISOString()" class="text-sm">
      <GrRelativeTime :value="post" :base="now" :cutoff="30" :format="format" />
    </div>
  </div>
</template>

Live

Live
<script setup lang="ts">
import { ref } from 'vue'

// `GrRelativeTime` подставляется авто-импортом (`unplugin-vue-components`).
import { GrButton } from '@feugene/granularity'

/**
 * Здесь `base` не задан: отсчёт идёт от общих часов пакета, и текст обновляется
 * сам. Такт компонент выбирает по единице — секунды пересчитываются часто,
 * месяцы редко, а таймер на такт в приложении один на всех.
 */
const events = ref<Date[]>([new Date()])

function add(): void {
  events.value = [new Date(), ...events.value].slice(0, 5)
}
</script>

<template>
  <div class="grid gap-3 justify-items-start">
    <GrButton size="sm" @click="add">
      Отметить событие
    </GrButton>

    <ul class="grid gap-1 text-sm">
      <li v-for="event in events" :key="event.toISOString()">
        <GrRelativeTime :value="event" />
      </li>
    </ul>
  </div>
</template>

Scale

Scale
<script setup lang="ts">
// `GrRelativeTime` подставляется авто-импортом (`unplugin-vue-components`).

/**
 * Момент отсчёта задан пропом `base` — от него и считается вся шкала. Так
 * пример показывает одно и то же в любой день и не зависит от часов машины,
 * на которой открыт.
 */
const now = new Date(2026, 7, 12, 12, 0)

function ago(ms: number): Date {
  return new Date(now.getTime() - ms)
}

const scale = [
  { label: 'секунды', value: ago(3 * 1000) },
  { label: 'минуты', value: ago(3 * 60_000) },
  { label: 'часы', value: ago(4 * 3_600_000) },
  { label: 'вчера', value: new Date(2026, 7, 11, 12, 0) },
  { label: 'недели', value: new Date(2026, 6, 29, 12, 0) },
  { label: 'месяцы', value: new Date(2026, 4, 12, 12, 0) },
  { label: 'годы', value: new Date(2024, 7, 12, 12, 0) },
  { label: 'будущее', value: new Date(2026, 7, 14, 12, 0) },
]
</script>

<template>
  <div class="grid gap-2">
    <div v-for="row in scale" :key="row.label" class="flex items-baseline gap-3 text-sm">
      <span class="showcase-demo-text w-24 shrink-0 opacity-70">{{ row.label }}</span>

      <GrRelativeTime :value="row.value" :base="now" />

      <GrRelativeTime :value="row.value" :base="now" width="short" class="opacity-70" />
    </div>
  </div>
</template>

Документация компонентаВсе компоненты