Aller au contenu

RelativeTime

composant

A moment written by the server as a date, then by a script as the time from now, which it keeps right.

Aperçu

Clair

Code
<twig:RelativeTime datetime="2026-11-09 12:00" />

Sombre

Code
<twig:RelativeTime datetime="2026-11-09 12:00" />

Essayer

Changez les propriétés ; le serveur rend l’exemple de nouveau.

<twig:RelativeTime datetime="2026-11-09 12:00" />

Variantes

length

The length of the words.

length="long"
length="short"
length="narrow"

Installation

php bin/console ux:install relative-time --kit=github.com/parler-haut-interagir-librement/ux-kit

Propriétés

PropriétéTypePar défautDescription
format string 'Y-m-d H:i' The date written by the server, in the format of the filter date.
locale string|null null The language of the text; that of the page when null.
length 'long'|'short'|'narrow' 'long' The length of the words.
limit int 0 How old a moment stays relative, in seconds; 0 for always.

Accessibility

A time element with its machine-readable date and the date in its title. It is no live region: the text changes without interrupting anyone. Without a script, or past limit, the date stays.

Fichiers à copier

assets/controllers/relative_time_controller.js
import { Controller } from '@hotwired/stimulus';

const SECOND = 1000;

const UNITS = [
    ['year', 365 * 86400 * SECOND],
    ['month', 30 * 86400 * SECOND],
    ['week', 7 * 86400 * SECOND],
    ['day', 86400 * SECOND],
    ['hour', 3600 * SECOND],
    ['minute', 60 * SECOND],
    ['second', SECOND],
];

/** The longest a timer waits, the words being right to the day beyond it. */
const LONGEST = 3600 * SECOND;

/**
 * What `then` is from `now`, in milliseconds: the number and the unit to write, and how long the words stay right.
 */
export function relative(then, now) {
    const delta = then - now;
    const [unit, size] = UNITS.find(([, length]) => Math.abs(delta) >= length) ?? UNITS[UNITS.length - 1];
    const left = size - (Math.abs(delta) % size);

    return { value: Math.trunc(delta / size), unit, next: Math.max(SECOND, Math.min(LONGEST, left)) };
}

/*
 * The time from now of a `time` element, kept right by one timer, set again when the page comes back. Past the limit
 * and for what cannot be read, the date of the server stays.
 */
export default class extends Controller {
    static values = {
        locale: String,
        length: { type: String, default: 'long' },
        limit: { type: Number, default: 0 },
    };

    #timer = null;
    #visible = () => this.#write();

    connect() {
        document.addEventListener('visibilitychange', this.#visible);
        this.#write();
    }

    disconnect() {
        clearTimeout(this.#timer);
        document.removeEventListener('visibilitychange', this.#visible);
    }

    #write() {
        clearTimeout(this.#timer);

        const then = Date.parse(this.element.getAttribute('datetime'));
        const now = Date.now();

        if (Number.isNaN(then) || (this.limitValue > 0 && Math.abs(then - now) > this.limitValue * SECOND)) {
            return;
        }

        const locale = this.localeValue || document.documentElement.lang || undefined;
        const { value, unit, next } = relative(then, now);
        const words = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: this.lengthValue });

        this.element.textContent = words.format(value, unit);
        this.#timer = setTimeout(() => this.#write(), next);
    }
}
templates/components/RelativeTime.html.twig
{#-
 # A moment, written as a date by the server and then as the time from now by the controller, which keeps it right.
 # The date stays in the title.
 #}
{%- props
    ## \DateTimeInterface|string The moment.
    datetime,
    ## string The date written by the server, in the format of the filter `date`.
    format = 'Y-m-d H:i',
    ## string|null The language of the text; that of the page when null.
    locale = null,
    ## 'long'|'short'|'narrow' The length of the words.
    length = 'long',
    ## int How old a moment stays relative, in seconds; 0 for always.
    limit = 0
-%}
<time datetime="{{ datetime|date('c') }}" title="{{ datetime|date(format) }}" data-relative-time-locale-value="{{ locale ?? '' }}" data-relative-time-length-value="{{ length }}" data-relative-time-limit-value="{{ limit }}"{{ attributes.defaults({class: 'isRelativeTime', 'data-controller': 'relative-time'}) }}>{{ datetime|date(format) }}</time>