diff --git a/README.md b/README.md index 59ff70c..b60e744 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,8 @@ 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` | *** | | `year` | `year` | `'numeric'\|'2-digit'\|undefined` | **** | | `timeZoneName` | `time-zone-name` | `'long'\|'short'\|'shortOffset'\|'longOffset'` `\|'shortGeneric'\|'longGeneric'\|undefined` | `undefined` | -| `timeZone` | `time-zone` | `string\|undefined` | Browser default time zone | +| `timeZone` | `time-zone` | `string\|undefined` | Browser default time zone | +| `hourCycle` | `hour-cycle` | `'h11'\|'h12'\|'h23'\|'h24'\|undefined` | 'h12' or 'h23' based on browser | | `noTitle` | `no-title` | `-` | `-` | *: 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'`. diff --git a/examples/index.html b/examples/index.html index 886af5c..b190940 100644 --- a/examples/index.html +++ b/examples/index.html @@ -29,6 +29,20 @@

Format DateTime

+

+ h12 cycle: + + Jan 1 1970 + +

+ +

+ h23 cycle: + + Jan 1 1970 + +

+

Customised options: diff --git a/src/relative-time-element.ts b/src/relative-time-element.ts index 353655b..3ea3fb5 100644 --- a/src/relative-time-element.ts +++ b/src/relative-time-element.ts @@ -32,6 +32,18 @@ function getUnitFactor(el: RelativeTimeElement): number { return 60 * 60 * 1000 } +// Determine whether the user has a 12 (vs. 24) hour cycle preference. This relies on the hour formatting in +// a 12 hour preference being formatted like "1 AM" including a space, while with a 24 hour preference, the +// same is formatted as "01" without a space. In the future `Intl.Locale.prototype.getHourCycles()` could be +// used but it is not as well-supported as this method. +function isBrowser12hCycle() { + return Boolean(/\s/.exec(new Intl.DateTimeFormat([], {hour: 'numeric'}).format(new Date(0)))) +} + +function isHour12(hourCycle: Intl.DateTimeFormatOptions['hourCycle']) { + return hourCycle === 'h11' || hourCycle === 'h12' +} + const dateObserver = new (class { elements: Set = new Set() time = Infinity @@ -98,6 +110,14 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor return tz || undefined } + get hourCycle() { + // Prefer attribute, then closest, then document + const hc = + this.closest('[hour-cycle]')?.getAttribute('hour-cycle') || + this.ownerDocument.documentElement.getAttribute('hour-cycle') + return (hc || (isBrowser12hCycle() ? 'h12' : 'h23')) as Intl.DateTimeFormatOptions['hourCycle'] + } + #renderRoot: Node = this.shadowRoot ? this.shadowRoot : this.attachShadow ? this.attachShadow({mode: 'open'}) : this static get observedAttributes() { @@ -139,6 +159,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor minute: '2-digit', timeZoneName: 'short', timeZone: this.timeZone, + hour12: isHour12(this.hourCycle), }).format(date) } @@ -213,6 +234,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor year: this.year, timeZoneName: this.timeZoneName, timeZone: this.timeZone, + hour12: isHour12(this.hourCycle), }) return `${this.prefix} ${formatter.format(date)}`.trim() } @@ -226,6 +248,7 @@ export class RelativeTimeElement extends HTMLElement implements Intl.DateTimeFor minute: '2-digit', timeZoneName: 'short', timeZone: this.timeZone, + hour12: isHour12(this.hourCycle), }).format(date) }