Skip to content

feat: add support for time-zone attribute #316

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ So, a relative date phrase is used for up to a month and then the actual date is
| `month` | `month` | `'numeric'\|'2-digit'\|'short'\|'long'\|'narrow'\|undefined` | <sup>***</sup> |
| `year` | `year` | `'numeric'\|'2-digit'\|undefined` | <sup>****</sup> |
| `timeZoneName` | `time-zone-name` | `'long'\|'short'\|'shortOffset'\|'longOffset'` `\|'shortGeneric'\|'longGeneric'\|undefined` | `undefined` |
| `timeZone` | `time-zone` | `string\|undefined` | Browser default time zone |
| `noTitle` | `no-title` | `-` | `-` |

<sup>*</sup>: If unspecified, `formatStyle` will return `'narrow'` if `format` is `'elapsed'` or `'micro'`, `'short'` if the format is `'relative'` or `'datetime'`, otherwise it will be `'long'`.
Expand Down Expand Up @@ -139,6 +140,19 @@ The `duration` format will display the time remaining (or elapsed time) from the
- `4 hours`
- `8 days, 30 minutes, 1 second`

##### time-zone (`string`)

The`time-zone` attribute allows you to specify the IANA time zone name (e.g., `America/New_York`, `Europe/London`) used for formatting the date and time.

You can set the time zone either as an attribute or property:
```html
<relative-time datetime="2024-06-01T12:00:00Z" ... time-zone="America/New_York">
June 1, 2024 8:00am EDT
</relative-time>
```

If the individual element does not have a `time-zone` attribute then it will traverse upwards in the tree to find the closest element that does, or default the `time-zone` to the browsers default.
Copy link
Preview

Copilot AI Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar: change “browsers default” to “browser's default” to fix the possessive form.

Suggested change
If the individual element does not have a `time-zone` attribute then it will traverse upwards in the tree to find the closest element that does, or default the `time-zone` to the browsers default.
If the individual element does not have a `time-zone` attribute then it will traverse upwards in the tree to find the closest element that does, or default the `time-zone` to the browser's default.

Copilot uses AI. Check for mistakes.


###### Deprecated Formats

###### `format=elapsed`
Expand Down
11 changes: 11 additions & 0 deletions src/relative-time-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
}
}

get timeZone() {
// Prefer attribute, then closest, then document
const tz =
this.closest('[time-zone]')?.getAttribute('time-zone') ||
this.ownerDocument.documentElement.getAttribute('time-zone')
return tz || undefined
}

#renderRoot: Node = this.shadowRoot ? this.shadowRoot : this.attachShadow ? this.attachShadow({mode: 'open'}) : this

static get observedAttributes() {
Expand All @@ -113,6 +121,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
'lang',
'title',
'aria-hidden',
'time-zone',
]
}

Expand All @@ -129,6 +138,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short',
timeZone: this.timeZone,
}).format(date)
}

Expand Down Expand Up @@ -198,6 +208,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor
month: this.month,
year: this.year,
timeZoneName: this.timeZoneName,
timeZone: this.timeZone,
})
return `${this.prefix} ${formatter.format(date)}`.trim()
}
Expand Down
58 changes: 58 additions & 0 deletions test/relative-time.js
Original file line number Diff line number Diff line change
Expand Up @@ -2586,4 +2586,62 @@ suite('relative-time', function () {
})
}
})

suite('[timeZone]', function () {
test('updates when the time-zone attribute is set', async () => {
const el = document.createElement('relative-time')
el.setAttribute('datetime', '2020-01-01T12:00:00.000Z')
el.setAttribute('time-zone', 'America/New_York')
el.setAttribute('format', 'datetime')
el.setAttribute('hour', 'numeric')
el.setAttribute('minute', '2-digit')
el.setAttribute('second', '2-digit')
el.setAttribute('time-zone-name', 'longGeneric')
await Promise.resolve()
assert.equal(el.shadowRoot.textContent, 'Wed, Jan 1, 2020, 7:00:00 AM Eastern Time')
})

test('updates when the time-zone attribute changes', async () => {
const el = document.createElement('relative-time')
el.setAttribute('datetime', '2020-01-01T12:00:00.000Z')
el.setAttribute('time-zone', 'America/New_York')
el.setAttribute('format', 'datetime')
el.setAttribute('hour', 'numeric')
el.setAttribute('minute', '2-digit')
el.setAttribute('second', '2-digit')
await Promise.resolve()
const initial = el.shadowRoot.textContent
el.setAttribute('time-zone', 'Asia/Tokyo')
await Promise.resolve()
assert.notEqual(el.shadowRoot.textContent, initial)
assert.equal(el.shadowRoot.textContent, 'Wed, Jan 1, 2020, 9:00:00 PM')
})

test('ignores empty time-zone attributes', async () => {
const el = document.createElement('relative-time')
el.setAttribute('datetime', '2020-01-01T12:00:00.000Z')
el.setAttribute('time-zone', '')
el.setAttribute('format', 'datetime')
el.setAttribute('hour', 'numeric')
el.setAttribute('minute', '2-digit')
el.setAttribute('second', '2-digit')
await Promise.resolve()
// Should fallback to default or system time zone
assert.equal(el.shadowRoot.textContent, 'Wed, Jan 1, 2020, 4:00:00 PM')
})

test('uses html time-zone if element time-zone is empty', async () => {
const time = document.createElement('relative-time')
time.setAttribute('datetime', '2020-01-01T12:00:00.000Z')
time.setAttribute('time-zone', '')
document.documentElement.setAttribute('time-zone', 'Asia/Tokyo')
time.setAttribute('format', 'datetime')
time.setAttribute('hour', 'numeric')
time.setAttribute('minute', '2-digit')
time.setAttribute('second', '2-digit')
await Promise.resolve()
assert.equal(time.shadowRoot.textContent, 'Wed, Jan 1, 2020, 9:00:00 PM')
document.documentElement.removeAttribute('time-zone')
})
})
})