Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tense to relative-time #181

Merged
merged 2 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/extended-time-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class ExtendedTimeElement extends HTMLElement {
'title',
'weekday',
'year',
'tense',
'time-zone-name'
]
}
Expand Down
28 changes: 27 additions & 1 deletion src/relative-time-element.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {Tense, Format} from './relative-time.js'
import RelativeTime from './relative-time.js'
import ExtendedTimeElement from './extended-time-element.js'
import {localeFromElement} from './utils.js'
Expand All @@ -6,7 +7,32 @@ export default class RelativeTimeElement extends ExtendedTimeElement {
getFormattedDate(): string | undefined {
const date = this.date
if (!date) return
return new RelativeTime(date, localeFromElement(this)).toString(this.getAttribute('format') || undefined)
return new RelativeTime(date, localeFromElement(this)).toString({
tense: this.tense,
format: this.format
})
}

get tense(): Tense {
const tense = this.getAttribute('tense')
if (tense === 'past') return 'past'
if (tense === 'future') return 'future'
return 'auto'
}

set tense(value: Tense) {
this.setAttribute('tense', value)
}

get format(): Format {
const format = this.getAttribute('format')
if (format === 'micro') return 'micro'
if (format && format.includes('%')) return format
return 'auto'
}

set format(value: Format) {
this.setAttribute('format', value)
}

connectedCallback(): void {
Expand Down
28 changes: 20 additions & 8 deletions src/relative-time.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {makeFormatter, makeRelativeFormat, isDayFirst, isThisYear, isYearSeparator} from './utils.js'
import {strftime} from './strftime.js'

export type Format = 'auto' | 'micro' | string
export type Tense = 'auto' | 'past' | 'future'

export default class RelativeTime {
date: Date
locale: string
Expand All @@ -10,16 +13,25 @@ export default class RelativeTime {
this.locale = locale
}

toString(format?: string): string {
const ago = this.timeElapsed()
if (ago) {
return ago
toString({format = 'auto', tense = 'auto'}: {format?: Format; tense?: Tense} = {}): string | undefined {
const micro = format === 'micro'
if (tense === 'past') {
return micro ? this.microTimeAgo() : this.timeAgo()
}
if (tense === 'future') {
return micro ? this.microTimeUntil() : this.timeUntil()
}
const ahead = this.timeAhead()
if (ahead) {
return ahead
if (format === 'auto') {
const ago = micro ? this.microTimeAgo() : this.timeElapsed()
keithamus marked this conversation as resolved.
Show resolved Hide resolved
if (ago) {
return ago
}
const ahead = micro ? this.microTimeUntil() : this.timeAhead()
if (ahead) {
return ahead
}
}
if (format) {
if (format !== 'auto' && format !== 'micro') {
return this.formatDate(format)
}
return `on ${this.formatDate()}`
Expand Down
215 changes: 215 additions & 0 deletions test/relative-time.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,219 @@ suite('relative-time', function () {
assert.equal(time.getFormattedDate(), 'hace 3 días')
})
}

suite('[tense=past]', function () {
let dateNow

function freezeTime(expected) {
dateNow = Date

function MockDate(...args) {
if (args.length) {
return new dateNow(...args)
}
return new dateNow(expected)
}

MockDate.UTC = dateNow.UTC
MockDate.parse = dateNow.parse
MockDate.now = () => expected.getTime()
MockDate.prototype = dateNow.prototype

// eslint-disable-next-line no-global-assign
Date = MockDate
}

teardown(function () {
if (dateNow) {
// eslint-disable-next-line no-global-assign
Date = dateNow
dateNow = null
}
})

test('always uses relative dates', function () {
const now = new Date(Date.now() - 10 * 365 * 24 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
assert.equal(time.textContent, '10 years ago')
})

test('rewrites from now past datetime to minutes ago', function () {
const now = new Date(Date.now() - 3 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
assert.equal(time.textContent, '3 minutes ago')
})

test('rewrites a few seconds ago to now', function () {
const now = new Date().toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
assert.equal(time.textContent, 'now')
})

test('displays future times as now', function () {
const now = new Date(Date.now() + 3 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
assert.equal(time.textContent, 'now')
})

test('sets relative contents when parsed element is upgraded', function () {
const now = new Date().toISOString()
const root = document.createElement('div')
root.innerHTML = `<relative-time tense="past" datetime="${now}"></relative-time>`
if ('CustomElements' in window) {
window.CustomElements.upgradeSubtree(root)
}
assert.equal(root.children[0].textContent, 'now')
})

test('rewrites from now past datetime to months ago', function () {
const now = new Date(Date.now() - 3 * 30 * 24 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
assert.equal(time.textContent, '3 months ago')
})

test('rewrites relative-time datetimes < 18 months as "months ago"', function () {
freezeTime(new Date(2020, 0, 1))
const then = new Date(2018, 9, 1).toISOString()
const timeElement = document.createElement('relative-time')
timeElement.setAttribute('tense', 'past')
timeElement.setAttribute('datetime', then)
assert.equal(timeElement.textContent, '15 months ago')
})

test('rewrites relative-time datetimes >= 18 months as "years ago"', function () {
freezeTime(new Date(2020, 0, 1))
const then = new Date(2018, 6, 1).toISOString()
const timeElement = document.createElement('relative-time')
timeElement.setAttribute('tense', 'past')
timeElement.setAttribute('datetime', then)
assert.equal(timeElement.textContent, '2 years ago')
})

test('micro formats years', function () {
const now = new Date(Date.now() - 10 * 365 * 24 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '10y')
})

test('micro formats future times', function () {
const now = new Date(Date.now() + 3 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '1m')
})

test('micro formats hours', function () {
const now = new Date(Date.now() - 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '1h')
})

test('micro formats days', function () {
const now = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'past')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '1d')
})
})

suite('[tense=future]', function () {
test('always uses relative dates', function () {
const now = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
assert.equal(time.textContent, 'in 10 years')
})

test('rewrites from now future datetime to minutes ago', function () {
const now = new Date(Date.now() + 3 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
assert.equal(time.textContent, 'in 3 minutes')
})

test('rewrites a few seconds from now to now', function () {
const now = new Date().toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
assert.equal(time.textContent, 'now')
})

test('displays past times as now', function () {
const now = new Date(Date.now() + 3 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
assert.equal(time.textContent, 'now')
})

test('sets relative contents when parsed element is upgraded', function () {
const now = new Date().toISOString()
const root = document.createElement('div')
root.innerHTML = `<relative-time tense="future" datetime="${now}"></relative-time>`
if ('CustomElements' in window) {
window.CustomElements.upgradeSubtree(root)
}
assert.equal(root.children[0].textContent, 'now')
})

test('micro formats years', function () {
const now = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '10y')
})

test('micro formats past times', function () {
const now = new Date(Date.now() + 3 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '1m')
})

test('micro formats hours', function () {
const now = new Date(Date.now() + 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '1h')
})

test('micro formats days', function () {
const now = new Date(Date.now() + 25 * 60 * 60 * 1000).toISOString()
const time = document.createElement('relative-time')
time.setAttribute('tense', 'future')
time.setAttribute('datetime', now)
time.setAttribute('format', 'micro')
assert.equal(time.textContent, '1d')
})
})
})