Skip to content

Commit 1657fe8

Browse files
authored
Merge pull request #338 from github/use-today-format
Use today for today, and omit current year
2 parents 79e94a0 + 178a631 commit 1657fe8

File tree

2 files changed

+220
-5
lines changed

2 files changed

+220
-5
lines changed

src/relative-time-element.ts

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,57 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
217217
return `${this.prefix} ${formatter.format(date)}`.trim()
218218
}
219219

220-
#getUserPreferredAbsoluteTimeFormat(date: Date): string {
221-
return new Intl.DateTimeFormat(this.#lang, {
222-
day: 'numeric',
223-
month: 'short',
220+
#isToday(date: Date): boolean {
221+
const now = new Date()
222+
const formatter = new Intl.DateTimeFormat(this.#lang, {
223+
timeZone: this.timeZone,
224+
year: 'numeric',
225+
month: '2-digit',
226+
day: '2-digit',
227+
})
228+
return formatter.format(now) === formatter.format(date)
229+
}
230+
231+
#isCurrentYear(date: Date): boolean {
232+
const now = new Date()
233+
const formatter = new Intl.DateTimeFormat(this.#lang, {
234+
timeZone: this.timeZone,
224235
year: 'numeric',
236+
})
237+
return formatter.format(now) === formatter.format(date)
238+
}
239+
240+
// If current day, shows "Today" + time.
241+
// If current year, shows date without year.
242+
// In all other scenarios, show full date.
243+
#getUserPreferredAbsoluteTimeFormat(date: Date): string {
244+
const timeOnlyOptions: Intl.DateTimeFormatOptions = {
225245
hour: 'numeric',
226246
minute: '2-digit',
227247
timeZoneName: 'short',
228248
timeZone: this.timeZone,
249+
}
250+
251+
if (this.#isToday(date)) {
252+
const relativeFormatter = new Intl.RelativeTimeFormat(this.#lang, {numeric: 'auto'})
253+
let todayText = relativeFormatter.format(0, 'day')
254+
todayText = todayText.charAt(0).toLocaleUpperCase(this.#lang) + todayText.slice(1)
255+
const timeOnly = new Intl.DateTimeFormat(this.#lang, timeOnlyOptions).format(date)
256+
257+
return `${todayText} ${timeOnly}`
258+
}
259+
260+
const timeAndDateOptions: Intl.DateTimeFormatOptions = {
261+
...timeOnlyOptions,
262+
day: 'numeric',
263+
month: 'short',
264+
}
265+
if (this.#isCurrentYear(date)) {
266+
return new Intl.DateTimeFormat(this.#lang, timeAndDateOptions).format(date)
267+
}
268+
return new Intl.DateTimeFormat(this.#lang, {
269+
...timeAndDateOptions,
270+
year: 'numeric',
229271
}).format(date)
230272
}
231273

@@ -525,7 +567,11 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
525567
this.dispatchEvent(new RelativeTimeUpdatedEvent(oldText, newText, oldTitle, newTitle))
526568
}
527569

528-
if ((format === 'relative' || format === 'duration') && !displayUserPreferredAbsoluteTime) {
570+
const shouldObserve =
571+
format === 'relative' ||
572+
format === 'duration' ||
573+
(displayUserPreferredAbsoluteTime && (this.#isToday(date) || this.#isCurrentYear(date)))
574+
if (shouldObserve) {
529575
dateObserver.observe(this)
530576
} else {
531577
dateObserver.unobserve(this)

test/relative-time.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,10 @@ suite('relative-time', function () {
18861886
})
18871887

18881888
suite('experimental: [data-prefers-absolute-time]', async () => {
1889+
teardown(() => {
1890+
document.documentElement.removeAttribute('data-prefers-absolute-time')
1891+
document.body.removeAttribute('data-prefers-absolute-time')
1892+
})
18891893
test('formats with absolute time when data-prefers-absolute-time="true"', async () => {
18901894
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
18911895
const el = document.createElement('relative-time')
@@ -1930,6 +1934,171 @@ suite('relative-time', function () {
19301934

19311935
assert.match(el.shadowRoot.textContent, /[A-Z][a-z]{2} \d{1,2}, \d{4}, \d{1,2}:\d{2} (AM|PM)/)
19321936
})
1937+
1938+
test('formats today dates with "Today" text', async () => {
1939+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
1940+
1941+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
1942+
const el = document.createElement('relative-time')
1943+
el.setAttribute('lang', 'en-US')
1944+
el.setAttribute('time-zone', 'America/New_York')
1945+
1946+
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
1947+
await Promise.resolve()
1948+
1949+
assert.equal(el.shadowRoot.textContent, 'Today 12:00 PM EST')
1950+
})
1951+
1952+
test('formats current year dates without year', async () => {
1953+
freezeTime(new Date('2023-06-15T12:00:00.000Z'))
1954+
1955+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
1956+
const el = document.createElement('relative-time')
1957+
el.setAttribute('lang', 'en-US')
1958+
el.setAttribute('time-zone', 'America/New_York')
1959+
el.setAttribute('datetime', '2023-03-10T18:00:00.000Z')
1960+
await Promise.resolve()
1961+
1962+
assert.equal(el.shadowRoot.textContent, 'Mar 10, 1:00 PM EST')
1963+
})
1964+
1965+
test('formats different year dates as full date', async () => {
1966+
freezeTime(new Date('2023-06-15T12:00:00.000Z'))
1967+
1968+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
1969+
const el = document.createElement('relative-time')
1970+
el.setAttribute('lang', 'en-US')
1971+
el.setAttribute('time-zone', 'America/New_York')
1972+
el.setAttribute('datetime', '2022-03-10T18:00:00.000Z')
1973+
await Promise.resolve()
1974+
1975+
assert.equal(el.shadowRoot.textContent, 'Mar 10, 2022, 1:00 PM EST')
1976+
})
1977+
1978+
test('respects locale formatting', async () => {
1979+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
1980+
1981+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
1982+
const el = document.createElement('relative-time')
1983+
el.setAttribute('lang', 'es-ES')
1984+
el.setAttribute('time-zone', 'Europe/Madrid')
1985+
1986+
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
1987+
await Promise.resolve()
1988+
1989+
// Spanish formatting - "hoy" = "today", 24-hour format
1990+
assert.equal(el.shadowRoot.textContent, 'Hoy 18:00 CET')
1991+
})
1992+
1993+
test('uses element time-zone attribute', async () => {
1994+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
1995+
1996+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
1997+
const el = document.createElement('relative-time')
1998+
el.setAttribute('lang', 'en-US')
1999+
el.setAttribute('time-zone', 'Europe/Paris')
2000+
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
2001+
await Promise.resolve()
2002+
2003+
assert.equal(el.shadowRoot.textContent, 'Today 6:00 PM GMT+1')
2004+
})
2005+
2006+
suite('format exclusions', function () {
2007+
test('does not activate for format="duration"', async () => {
2008+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
2009+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2010+
2011+
const el = document.createElement('relative-time')
2012+
el.setAttribute('lang', 'en-US')
2013+
el.setAttribute('datetime', '2023-01-15T16:00:00.000Z')
2014+
el.setAttribute('format', 'duration')
2015+
await Promise.resolve()
2016+
2017+
assert.equal(el.shadowRoot.textContent, '1 hour')
2018+
})
2019+
2020+
test('does not activate for format="elapsed"', async () => {
2021+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
2022+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2023+
2024+
const el = document.createElement('relative-time')
2025+
el.setAttribute('lang', 'en-US')
2026+
el.setAttribute('datetime', '2023-01-15T16:00:00.000Z')
2027+
el.setAttribute('format', 'elapsed')
2028+
await Promise.resolve()
2029+
2030+
assert.equal(el.shadowRoot.textContent, '1h')
2031+
})
2032+
2033+
test('does not activate for format="micro"', async () => {
2034+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
2035+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2036+
2037+
const el = document.createElement('relative-time')
2038+
el.setAttribute('lang', 'en-US')
2039+
el.setAttribute('datetime', '2023-01-15T16:00:00.000Z')
2040+
el.setAttribute('format', 'micro')
2041+
await Promise.resolve()
2042+
2043+
assert.equal(el.shadowRoot.textContent, '1h')
2044+
})
2045+
2046+
test('activates for format="relative" (default)', async () => {
2047+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
2048+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2049+
2050+
const el = document.createElement('relative-time')
2051+
el.setAttribute('lang', 'en-US')
2052+
el.setAttribute('time-zone', 'GMT')
2053+
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
2054+
el.setAttribute('format', 'relative')
2055+
await Promise.resolve()
2056+
2057+
assert.equal(el.shadowRoot.textContent, 'Today 5:00 PM UTC')
2058+
})
2059+
2060+
test('activates for format="auto"', async () => {
2061+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
2062+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2063+
2064+
const el = document.createElement('relative-time')
2065+
el.setAttribute('lang', 'en-US')
2066+
el.setAttribute('time-zone', 'UTC')
2067+
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
2068+
el.setAttribute('format', 'auto')
2069+
await Promise.resolve()
2070+
2071+
assert.equal(el.shadowRoot.textContent, 'Today 5:00 PM UTC')
2072+
})
2073+
2074+
test('activates for format="datetime" if current day', async () => {
2075+
freezeTime(new Date('2023-01-15T17:00:00.000Z'))
2076+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2077+
2078+
const el = document.createElement('relative-time')
2079+
el.setAttribute('lang', 'en-US')
2080+
el.setAttribute('time-zone', 'America/New_York')
2081+
el.setAttribute('datetime', '2023-01-15T17:00:00.000Z')
2082+
el.setAttribute('format', 'datetime')
2083+
await Promise.resolve()
2084+
2085+
assert.equal(el.shadowRoot.textContent, 'Today 12:00 PM EST')
2086+
})
2087+
2088+
test('activates for format="datetime" if current year but not today', async () => {
2089+
freezeTime(new Date('2023-06-15T17:00:00.000Z'))
2090+
document.documentElement.setAttribute('data-prefers-absolute-time', 'true')
2091+
2092+
const el = document.createElement('relative-time')
2093+
el.setAttribute('lang', 'en-US')
2094+
el.setAttribute('time-zone', 'America/New_York')
2095+
el.setAttribute('datetime', '2023-03-10T18:00:00.000Z') // 18:00 UTC = 1:00 PM EST
2096+
el.setAttribute('format', 'datetime')
2097+
await Promise.resolve()
2098+
2099+
assert.equal(el.shadowRoot.textContent, 'Mar 10, 1:00 PM EST')
2100+
})
2101+
})
19332102
})
19342103

19352104
suite('[aria-hidden]', async () => {

0 commit comments

Comments
 (0)