diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts
index bf3722a977f2..b28748f90f26 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,
+ format?: RelativeTimeFormat,
includeTense = true
): 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..77abde4ffb01 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,12 @@ class HaRelativeTime extends ReactiveElement {
? parseISO(this.datetime)
: this.datetime;
- const relTime = relativeTime(date, this.hass.locale);
+ const relTime = relativeTime(
+ date,
+ this.hass.locale,
+ undefined,
+ this.format
+ );
this.innerHTML = this.capitalize
? capitalizeFirstLetter(relTime)
: relTime;
diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts
index 7c330ccde18c..bf5f2a015eba 100644
--- a/src/components/trace/hat-trace-timeline.ts
+++ b/src/components/trace/hat-trace-timeline.ts
@@ -74,7 +74,7 @@ class RenderedTimeTracker {
renderTime(from: Date, to: Date): void {
this.entries.push(html`
- ${relativeTime(from, this.hass.locale, to, false)} later
+ ${relativeTime(from, this.hass.locale, to, undefined, false)} later
`);
this.lastReportedTime = to;
diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts
index 908e34d5e05c..56c320a79050 100644
--- a/src/dialogs/more-info/controls/more-info-update.ts
+++ b/src/dialogs/more-info/controls/more-info-update.ts
@@ -106,6 +106,7 @@ class MoreInfoUpdate extends LitElement {
lastAutomaticBackupDate,
this.hass.locale,
now,
+ undefined,
true
),
}
diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts
index b238ad9767ab..e6be358531b6 100644
--- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts
+++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts
@@ -155,6 +155,7 @@ class HaBackupOverviewBackups extends LitElement {
lastAttemptDate,
this.hass.locale,
now,
+ undefined,
true
),
}
@@ -177,6 +178,7 @@ class HaBackupOverviewBackups extends LitElement {
new Date(lastUploadedBackup.date),
this.hass.locale,
now,
+ undefined,
true
),
count: Object.keys(lastUploadedBackup.agents)
@@ -245,6 +247,7 @@ class HaBackupOverviewBackups extends LitElement {
lastAttemptDate,
this.hass.locale,
now,
+ undefined,
true
),
}
@@ -264,6 +267,7 @@ class HaBackupOverviewBackups extends LitElement {
new Date(lastUploadedBackup.date),
this.hass.locale,
now,
+ undefined,
true
),
count: Object.keys(lastUploadedBackup.agents)
@@ -286,6 +290,7 @@ class HaBackupOverviewBackups extends LitElement {
new Date(lastBackup.date),
this.hass.locale,
now,
+ undefined,
true
),
count: Object.keys(lastBackup.agents).length,
diff --git a/src/panels/lovelace/badges/hui-entity-badge.ts b/src/panels/lovelace/badges/hui-entity-badge.ts
index b7d04e1a305b..df4a54856d85 100644
--- a/src/panels/lovelace/badges/hui-entity-badge.ts
+++ b/src/panels/lovelace/badges/hui-entity-badge.ts
@@ -184,6 +184,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 ef98a7e24fd0..ded513f7141b 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 a7fe97c773f2..ab5b4897fbc3 100644
--- a/src/panels/lovelace/cards/hui-tile-card.ts
+++ b/src/panels/lovelace/cards/hui-tile-card.ts
@@ -278,6 +278,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 ef6a20f31bde..81ce6512faf1 100644
--- a/src/panels/lovelace/cards/types.ts
+++ b/src/panels/lovelace/cards/types.ts
@@ -537,6 +537,7 @@ export interface TileCardConfig extends LovelaceCardConfig {
icon_double_tap_action?: ActionConfig;
features?: LovelaceCardFeatureConfig[];
features_position?: "bottom" | "inline";
+ 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 cc7ed22f4753..33e986569eb5 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";
@@ -19,7 +23,7 @@ const FORMATS: Record<
datetime: formatDateTime,
time: formatTime,
};
-const INTERVAL_FORMAT = ["relative", "total"];
+const INTERVAL_FORMAT = [...Object.keys(RelativeTimeFormat), "total"];
@customElement("hui-timestamp-display")
class HuiTimestampDisplay extends LitElement {
@@ -109,10 +113,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, this._format)
+ : relativeTime(
+ new Date(),
+ this.hass!.locale,
+ this.ts,
+ undefined,
+ 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/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts
index 6732fda5e530..3963ab02de07 100644
--- a/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-entity-badge-editor.ts
@@ -33,6 +33,7 @@ import { actionConfigStruct } from "../structs/action-struct";
import { baseLovelaceBadgeConfig } from "../structs/base-badge-struct";
import { configElementStyle } from "./config-elements-style";
import "./hui-card-features-editor";
+import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
const badgeConfigStruct = assign(
baseLovelaceBadgeConfig,
@@ -49,6 +50,7 @@ const badgeConfigStruct = assign(
show_entity_picture: optional(boolean()),
tap_action: optional(actionConfigStruct),
image: optional(string()), // For old badge config support
+ format: optional(enums(TIMESTAMP_RENDERING_FORMATS)),
})
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts
index 7e9bf434fd89..7081607ffdfb 100644
--- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts
@@ -37,6 +37,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import type { EditDetailElementEvent, EditSubElementEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
import "./hui-card-features-editor";
+import { TIMESTAMP_RENDERING_FORMATS } from "../../components/types";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -57,6 +58,7 @@ const cardConfigStruct = assign(
icon_double_tap_action: optional(actionConfigStruct),
features: optional(array(any())),
features_position: optional(enums(["bottom", "inline"])),
+ format: optional(enums(TIMESTAMP_RENDERING_FORMATS)),
})
);
diff --git a/src/state-display/state-display.ts b/src/state-display/state-display.ts
index b91411020d03..f2041095b3e9 100644
--- a/src/state-display/state-display.ts
+++ b/src/state-display/state-display.ts
@@ -11,6 +11,7 @@ import type { UpdateEntity } from "../data/update";
import { computeUpdateStateDisplay } from "../data/update";
import "../panels/lovelace/components/hui-timestamp-display";
import type { HomeAssistant } from "../types";
+import type { TimestampRenderingFormat } from "../panels/lovelace/components/types";
const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"];
@@ -59,6 +60,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;
@@ -90,7 +93,7 @@ class StateDisplay extends LitElement {
`;
@@ -133,6 +136,7 @@ class StateDisplay extends LitElement {
`;
diff --git a/test/common/datetime/relative_time.test.ts b/test/common/datetime/relative_time.test.ts
index 031f6350ca2a..4df7eb85724a 100644
--- a/test/common/datetime/relative_time.test.ts
+++ b/test/common/datetime/relative_time.test.ts
@@ -34,7 +34,10 @@ describe("relativeTime", () => {
assert.strictEqual(relativeTime(now, locale, now), "now");
});
it("returns 0 seconds without tense", () => {
- assert.strictEqual(relativeTime(now, locale, now, false), "0 seconds");
+ assert.strictEqual(
+ relativeTime(now, locale, now, undefined, false),
+ "0 seconds"
+ );
});
});
@@ -52,12 +55,12 @@ describe("relativeTime", () => {
it("without tense", () => {
assert.strictEqual(
- relativeTime(date1, locale, date2, false),
+ relativeTime(date1, locale, date2, undefined, false),
"33 seconds"
);
assert.strictEqual(
- relativeTime(date2, locale, date1, false),
+ relativeTime(date2, locale, date1, undefined, false),
"33 seconds"
);
});
@@ -77,12 +80,12 @@ describe("relativeTime", () => {
it("without tense", () => {
assert.strictEqual(
- relativeTime(date1, locale, date2, false),
+ relativeTime(date1, locale, date2, undefined, false),
"2 minutes"
);
assert.strictEqual(
- relativeTime(date2, locale, date1, false),
+ relativeTime(date2, locale, date1, undefined, false),
"2 minutes"
);
});
@@ -101,9 +104,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "2 hours");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "2 hours"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "2 hours");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "2 hours"
+ );
});
});
@@ -120,9 +129,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "23 hours");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "23 hours"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "23 hours");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "23 hours"
+ );
});
});
@@ -139,9 +154,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "1 day");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "1 day"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "1 day");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "1 day"
+ );
});
});
@@ -158,9 +179,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "2 days");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "2 days"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "2 days");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "2 days"
+ );
});
});
@@ -178,9 +205,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "5 days");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "5 days"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "5 days");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "5 days"
+ );
});
});
@@ -201,12 +234,12 @@ describe("relativeTime", () => {
it("without tense", () => {
assert.strictEqual(
- relativeTime(date1, locale_monday, date2, false),
+ relativeTime(date1, locale_monday, date2, undefined, false),
"1 week"
);
assert.strictEqual(
- relativeTime(date2, locale_monday, date1, false),
+ relativeTime(date2, locale_monday, date1, undefined, false),
"1 week"
);
});
@@ -227,9 +260,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "1 week");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "1 week"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "1 week");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "1 week"
+ );
});
});
@@ -250,12 +289,12 @@ describe("relativeTime", () => {
it("without tense", () => {
assert.strictEqual(
- relativeTime(date1, locale_monday, date2, false),
+ relativeTime(date1, locale_monday, date2, undefined, false),
"5 days"
);
assert.strictEqual(
- relativeTime(date2, locale_monday, date1, false),
+ relativeTime(date2, locale_monday, date1, undefined, false),
"5 days"
);
});
@@ -275,9 +314,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "2 weeks");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "2 weeks"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "2 weeks");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "2 weeks"
+ );
});
});
@@ -294,9 +339,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "4 weeks");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "4 weeks"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "4 weeks");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "4 weeks"
+ );
});
});
@@ -313,9 +364,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "1 month");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "1 month"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "1 month");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "1 month"
+ );
});
});
@@ -333,12 +390,12 @@ describe("relativeTime", () => {
it("without tense", () => {
assert.strictEqual(
- relativeTime(date1, locale, date2, false),
+ relativeTime(date1, locale, date2, undefined, false),
"11 months"
);
assert.strictEqual(
- relativeTime(date2, locale, date1, false),
+ relativeTime(date2, locale, date1, undefined, false),
"11 months"
);
});
@@ -357,9 +414,15 @@ describe("relativeTime", () => {
});
it("without tense", () => {
- assert.strictEqual(relativeTime(date1, locale, date2, false), "1 year");
+ assert.strictEqual(
+ relativeTime(date1, locale, date2, undefined, false),
+ "1 year"
+ );
- assert.strictEqual(relativeTime(date2, locale, date1, false), "1 year");
+ assert.strictEqual(
+ relativeTime(date2, locale, date1, undefined, false),
+ "1 year"
+ );
});
});
});