x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Time --><time datetime="2025-11-16T21:53:43Z" data-local-time-target="time"></time><!-- Date --><time datetime="2025-11-16T21:53:43Z" data-local-time-target="date"></time><!-- Shortdate --><time datetime="2025-11-16T21:53:43Z" data-local-time-target="shortdate"></time><!-- Datetime --><time datetime="2025-11-16T21:53:43Z" data-local-time-target="datetime"></time><!-- Ago --><time datetime="2025-11-16T21:48:43Z" data-local-time-target="ago"></time>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%# Time %><%= tag.time datetime: Time.current.iso8601, data: { local_time_target: "time" } %><%# Date %><%= tag.time datetime: Time.current.iso8601, data: { local_time_target: "date" } %><%# Shortdate %><%= tag.time datetime: Time.current.iso8601, data: { local_time_target: "shortdate" } %><%# Datetime %><%= tag.time datetime: Time.current.iso8601, data: { local_time_target: "datetime" } %><%# Ago %><%= tag.time datetime: 5.minutes.ago.iso8601, data: { local_time_target: "ago" } %>
CSS is not required or multiple files are needed, check the notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { Controller } from "@hotwired/stimulus"export default class extends Controller { static targets = [ "time", "date", "shortdate", "datetime", "ago" ] #timer initialize() { this.timeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "short" }) this.dateFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: "long" }) this.shortdateFormatter = new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric" }) this.datetimeFormatter = new Intl.DateTimeFormat(undefined, { timeStyle: "short", dateStyle: "short" }) this.agoFormatter = new TimeAgoFormat(undefined, { numeric: "always" }) } connect() { this.#timer = setInterval(() => this.#refreshRelativeTimes(), 30_000) } disconnect() { clearInterval(this.#timer) } timeTargetConnected(target) { this.#formatTime(this.timeFormatter, target) } dateTargetConnected(target) { this.#formatTime(this.dateFormatter, target) } datetimeTargetConnected(target) { this.#formatTime(this.datetimeFormatter, target) } shortdateTargetConnected(target) { this.#formatTime(this.shortdateFormatter, target) } agoTargetConnected(target) { this.#formatTime(this.agoFormatter, target) } #refreshRelativeTimes() { this.agoTargets.forEach(it => this.#formatTime(this.agoFormatter, it)) } #formatTime(formatter, target) { const date = new Date(target.getAttribute("datetime")) target.textContent = formatter.format(date) target.title = this.datetimeFormatter.format(date) }}class TimeAgoFormat extends Intl.RelativeTimeFormat { format(date) { const seconds = (Date.now() - date) / 1000 const minutes = seconds / 60 const hours = minutes / 60 const days = hours / 24 const weeks = days / 7 const months = days / 30.4 const years = days / 365 if (years >= 1) { return super.format(-Math.floor(years), "years") } else if (months >= 1) { return super.format(-Math.floor(months), "months") } else if (weeks >= 1) { return super.format(-Math.floor(weeks), "weeks") } else if (days >= 1) { return super.format(-Math.floor(days), "days") } else if (hours >= 1) { return super.format(-Math.floor(hours), "hours") } else if (minutes >= 1) { return super.format(-Math.floor(minutes), "minutes") } else { return this.#formatAsTime(date) } } #formatAsTime(date) { return new Intl.DateTimeFormat(undefined, { timeStyle: "short" }).format(date) }}Initialization
<body data-controller="local-time">...</body>