diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts index bf3722a977f2..b0620f8843a4 100644 --- a/src/common/datetime/relative_time.ts +++ b/src/common/datetime/relative_time.ts @@ -2,24 +2,40 @@ import memoizeOne from "memoize-one"; import type { FrontendLocaleData } from "../../data/translation"; import { selectUnit } from "../util/select-unit"; +export enum RelativeTimeFormat { + relative = "long", + relative_short = "short", + relative_narrow = "narrow", +} + +export type RelativeTimeStyle = `${RelativeTimeFormat}`; + +export function isRelativeTimeFormat( + format: string +): format is RelativeTimeFormat { + return Object.keys(RelativeTimeFormat).includes(format as RelativeTimeFormat); +} + const formatRelTimeMem = memoizeOne( - (locale: FrontendLocaleData) => - new Intl.RelativeTimeFormat(locale.language, { numeric: "auto" }) + (locale: FrontendLocaleData, style: RelativeTimeStyle) => + new Intl.RelativeTimeFormat(locale.language, { numeric: "auto", style }) ); export const relativeTime = ( from: Date, locale: FrontendLocaleData, to?: Date, - includeTense = true + includeTense = true, + format?: RelativeTimeFormat ): string => { const diff = selectUnit(from, to, locale); + const style: RelativeTimeStyle = format ? RelativeTimeFormat[format] : "long"; if (includeTense) { - return formatRelTimeMem(locale).format(diff.value, diff.unit); + return formatRelTimeMem(locale, style).format(diff.value, diff.unit); } return Intl.NumberFormat(locale.language, { style: "unit", unit: diff.unit, - unitDisplay: "long", + unitDisplay: style, }).format(Math.abs(diff.value)); }; diff --git a/src/components/ha-relative-time.ts b/src/components/ha-relative-time.ts index cb9cd7d7b16e..1fb65501d5ca 100644 --- a/src/components/ha-relative-time.ts +++ b/src/components/ha-relative-time.ts @@ -2,7 +2,10 @@ import { parseISO } from "date-fns"; import type { PropertyValues } from "lit"; import { ReactiveElement } from "lit"; import { customElement, property } from "lit/decorators"; -import { relativeTime } from "../common/datetime/relative_time"; +import { + relativeTime, + type RelativeTimeFormat, +} from "../common/datetime/relative_time"; import { capitalizeFirstLetter } from "../common/string/capitalize-first-letter"; import type { HomeAssistant } from "../types"; @@ -12,6 +15,8 @@ class HaRelativeTime extends ReactiveElement { @property({ attribute: false }) public datetime?: string | Date; + @property({ attribute: false }) public format?: RelativeTimeFormat; + @property({ type: Boolean }) public capitalize = false; private _interval?: number; @@ -65,7 +70,13 @@ class HaRelativeTime extends ReactiveElement { ? parseISO(this.datetime) : this.datetime; - const relTime = relativeTime(date, this.hass.locale); + const relTime = relativeTime( + date, + this.hass.locale, + undefined, + undefined, + this.format + ); this.innerHTML = this.capitalize ? capitalizeFirstLetter(relTime) : relTime; diff --git a/src/panels/lovelace/badges/hui-entity-badge.ts b/src/panels/lovelace/badges/hui-entity-badge.ts index 093e8f92aff3..34c084444357 100644 --- a/src/panels/lovelace/badges/hui-entity-badge.ts +++ b/src/panels/lovelace/badges/hui-entity-badge.ts @@ -185,6 +185,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge { .hass=${this.hass} .content=${this._config.state_content} .name=${this._config.name} + .format=${this._config.format} > `; diff --git a/src/panels/lovelace/badges/types.ts b/src/panels/lovelace/badges/types.ts index 6a5b47994364..76a09788872b 100644 --- a/src/panels/lovelace/badges/types.ts +++ b/src/panels/lovelace/badges/types.ts @@ -2,6 +2,7 @@ import type { ActionConfig } from "../../../data/lovelace/config/action"; import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import type { LegacyStateFilter } from "../common/evaluate-filter"; import type { Condition } from "../common/validate-condition"; +import type { TimestampRenderingFormat } from "../components/types"; import type { EntityFilterEntityConfig } from "../entity-rows/types"; import type { DisplayType } from "./hui-entity-badge"; @@ -42,6 +43,7 @@ export interface EntityBadgeConfig extends LovelaceBadgeConfig { tap_action?: ActionConfig; hold_action?: ActionConfig; double_tap_action?: ActionConfig; + format?: TimestampRenderingFormat; /** * @deprecated use `show_state`, `show_name`, `icon_type` */ diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 457363681fd6..6bcbca43dcc6 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -255,6 +255,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { .hass=${this.hass} .content=${this._config.state_content} .name=${this._config.name} + .format=${this._config.format} > `; diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 9f741915fa80..437397b0f397 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -515,6 +515,7 @@ export interface TileCardConfig extends LovelaceCardConfig { icon_hold_action?: ActionConfig; icon_double_tap_action?: ActionConfig; features?: LovelaceCardFeatureConfig[]; + format?: TimestampRenderingFormat; } export interface HeadingCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/components/hui-timestamp-display.ts b/src/panels/lovelace/components/hui-timestamp-display.ts index 84d5cde3aec2..f35ab1d50481 100644 --- a/src/panels/lovelace/components/hui-timestamp-display.ts +++ b/src/panels/lovelace/components/hui-timestamp-display.ts @@ -5,7 +5,11 @@ import { customElement, property, state } from "lit/decorators"; import { formatDate } from "../../../common/datetime/format_date"; import { formatDateTime } from "../../../common/datetime/format_date_time"; import { formatTime } from "../../../common/datetime/format_time"; -import { relativeTime } from "../../../common/datetime/relative_time"; +import { + isRelativeTimeFormat, + RelativeTimeFormat, + relativeTime, +} from "../../../common/datetime/relative_time"; import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter"; import type { FrontendLocaleData } from "../../../data/translation"; import type { HomeAssistant } from "../../../types"; @@ -22,7 +26,7 @@ const FORMATS: { datetime: formatDateTime, time: formatTime, }; -const INTERVAL_FORMAT = ["relative", "total"]; +const INTERVAL_FORMAT = [...Object.keys(RelativeTimeFormat), "total"]; @customElement("hui-timestamp-display") class HuiTimestampDisplay extends LitElement { @@ -112,10 +116,15 @@ class HuiTimestampDisplay extends LitElement { private _updateRelative(): void { if (this.ts && this.hass?.localize) { - this._relative = - this._format === "relative" - ? relativeTime(this.ts, this.hass!.locale) - : relativeTime(new Date(), this.hass!.locale, this.ts, false); + this._relative = isRelativeTimeFormat(this._format) + ? relativeTime( + this.ts, + this.hass!.locale, + undefined, + undefined, + this._format + ) + : relativeTime(new Date(), this.hass!.locale, this.ts, false); this._relative = this.capitalize ? capitalizeFirstLetter(this._relative) diff --git a/src/panels/lovelace/components/types.ts b/src/panels/lovelace/components/types.ts index e05443f873eb..abfad26e7a51 100644 --- a/src/panels/lovelace/components/types.ts +++ b/src/panels/lovelace/components/types.ts @@ -9,6 +9,8 @@ export interface ConditionalBaseConfig extends LovelaceCardConfig { export const TIMESTAMP_RENDERING_FORMATS = [ "relative", + "relative_narrow", + "relative_short", "total", "date", "time", diff --git a/src/state-display/state-display.ts b/src/state-display/state-display.ts index b91411020d03..ad703fb5b079 100644 --- a/src/state-display/state-display.ts +++ b/src/state-display/state-display.ts @@ -11,6 +11,10 @@ import type { UpdateEntity } from "../data/update"; import { computeUpdateStateDisplay } from "../data/update"; import "../panels/lovelace/components/hui-timestamp-display"; import type { HomeAssistant } from "../types"; +import { + TIMESTAMP_RENDERING_FORMATS, + type TimestampRenderingFormat, +} from "../panels/lovelace/components/types"; const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; @@ -59,6 +63,8 @@ class StateDisplay extends LitElement { @property({ attribute: false }) public name?: string; + @property({ attribute: false }) public format?: TimestampRenderingFormat; + @property({ type: Boolean, attribute: "dash-unavailable" }) public dashUnavailable?: boolean; @@ -77,7 +83,10 @@ class StateDisplay extends LitElement { const stateObj = this.stateObj; const domain = computeStateDomain(stateObj); - if (content === "state") { + if ( + content === "state" || + (this.format && TIMESTAMP_RENDERING_FORMATS.includes(this.format)) + ) { if (this.dashUnavailable && isUnavailableState(stateObj.state)) { return "—"; } @@ -90,7 +99,7 @@ class StateDisplay extends LitElement { `; @@ -133,6 +142,7 @@ class StateDisplay extends LitElement { `;