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 {
`;