RelativeTime
composantA moment written by the server as a date, then by a script as the time from now, which it keeps right.
Aperçu
Code
<twig:RelativeTime datetime="2026-11-09 12:00" />
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" />
<twig:RelativeTime datetime="2026-11-09 12:00" />
Variantes
length
The length of the words.
Installation
php bin/console ux:install relative-time --kit=github.com/parler-haut-interagir-librement/ux-kit
Propriétés
| Propriété | Type | Par défaut | Description |
|---|---|---|---|
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>