mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 13:57:21 +00:00
Allow timestamp attribute formatting + central timestamp formats (#8162)
This commit is contained in:
parent
6f4dbdc959
commit
fe13853b8b
7
src/common/datetime/check_valid_date.ts
Normal file
7
src/common/datetime/check_valid_date.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function checkValidDate(date?: Date): boolean {
|
||||||
|
if (!date) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date instanceof Date && !isNaN(date.valueOf());
|
||||||
|
}
|
@ -12,6 +12,7 @@ import { formatDateTime } from "../../../common/datetime/format_date_time";
|
|||||||
import { formatTime } from "../../../common/datetime/format_time";
|
import { formatTime } from "../../../common/datetime/format_time";
|
||||||
import relativeTime from "../../../common/datetime/relative_time";
|
import relativeTime from "../../../common/datetime/relative_time";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { TimestampRenderingFormats } from "./types";
|
||||||
|
|
||||||
const FORMATS: { [key: string]: (ts: Date, lang: string) => string } = {
|
const FORMATS: { [key: string]: (ts: Date, lang: string) => string } = {
|
||||||
date: formatDate,
|
date: formatDate,
|
||||||
@ -26,12 +27,7 @@ class HuiTimestampDisplay extends LitElement {
|
|||||||
|
|
||||||
@property() public ts?: Date;
|
@property() public ts?: Date;
|
||||||
|
|
||||||
@property() public format?:
|
@property() public format?: TimestampRenderingFormats;
|
||||||
| "relative"
|
|
||||||
| "total"
|
|
||||||
| "date"
|
|
||||||
| "datetime"
|
|
||||||
| "time";
|
|
||||||
|
|
||||||
@internalProperty() private _relative?: string;
|
@internalProperty() private _relative?: string;
|
||||||
|
|
||||||
|
@ -6,3 +6,10 @@ export interface ConditionalBaseConfig extends LovelaceCardConfig {
|
|||||||
card: LovelaceCardConfig | LovelaceElementConfig;
|
card: LovelaceCardConfig | LovelaceElementConfig;
|
||||||
conditions: Condition[];
|
conditions: Condition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TimestampRenderingFormats =
|
||||||
|
| "relative"
|
||||||
|
| "total"
|
||||||
|
| "date"
|
||||||
|
| "time"
|
||||||
|
| "datetime";
|
||||||
|
@ -22,10 +22,11 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
|
|||||||
import "../components/hui-generic-entity-row";
|
import "../components/hui-generic-entity-row";
|
||||||
import "../components/hui-timestamp-display";
|
import "../components/hui-timestamp-display";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
|
import { TimestampRenderingFormats } from "../components/types";
|
||||||
import { LovelaceRow } from "./types";
|
import { LovelaceRow } from "./types";
|
||||||
|
|
||||||
interface SensorEntityConfig extends EntitiesCardEntityConfig {
|
interface SensorEntityConfig extends EntitiesCardEntityConfig {
|
||||||
format?: "relative" | "total" | "date" | "time" | "datetime";
|
format?: TimestampRenderingFormats;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("hui-sensor-entity-row")
|
@customElement("hui-sensor-entity-row")
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ActionConfig } from "../../../data/lovelace";
|
import { ActionConfig } from "../../../data/lovelace";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { Condition } from "../common/validate-condition";
|
import { Condition } from "../common/validate-condition";
|
||||||
|
import { TimestampRenderingFormats } from "../components/types";
|
||||||
|
|
||||||
export interface EntityConfig {
|
export interface EntityConfig {
|
||||||
entity: string;
|
entity: string;
|
||||||
@ -84,8 +85,10 @@ export interface ConditionalRowConfig extends EntityConfig {
|
|||||||
row: EntityConfig;
|
row: EntityConfig;
|
||||||
conditions: Condition[];
|
conditions: Condition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeRowConfig extends EntityConfig {
|
export interface AttributeRowConfig extends EntityConfig {
|
||||||
attribute: string;
|
attribute: string;
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
|
format?: TimestampRenderingFormats;
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,11 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import checkValidDate from "../../../common/datetime/check_valid_date";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import "../components/hui-generic-entity-row";
|
import "../components/hui-generic-entity-row";
|
||||||
|
import "../components/hui-timestamp-display";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import { AttributeRowConfig, LovelaceRow } from "../entity-rows/types";
|
import { AttributeRowConfig, LovelaceRow } from "../entity-rows/types";
|
||||||
|
|
||||||
@ -44,7 +46,6 @@ class HuiAttributeRow extends LitElement implements LovelaceRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.hass.states[this._config.entity];
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
const attribute = stateObj.attributes[this._config.attribute];
|
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
@ -54,10 +55,24 @@ class HuiAttributeRow extends LitElement implements LovelaceRow {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attribute = stateObj.attributes[this._config.attribute];
|
||||||
|
let date: Date | undefined;
|
||||||
|
if (this._config.format) {
|
||||||
|
date = new Date(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||||
<div>
|
<div>
|
||||||
${this._config.prefix} ${attribute ?? "-"} ${this._config.suffix}
|
${this._config.prefix}
|
||||||
|
${this._config.format && checkValidDate(date)
|
||||||
|
? html` <hui-timestamp-display
|
||||||
|
.hass=${this.hass}
|
||||||
|
.ts=${date}
|
||||||
|
.format=${this._config.format}
|
||||||
|
></hui-timestamp-display>`
|
||||||
|
: attribute ?? "-"}
|
||||||
|
${this._config.suffix}
|
||||||
</div>
|
</div>
|
||||||
</hui-generic-entity-row>
|
</hui-generic-entity-row>
|
||||||
`;
|
`;
|
||||||
|
19
test-mocha/common/datetime/check_valid_date.ts
Normal file
19
test-mocha/common/datetime/check_valid_date.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { assert } from "chai";
|
||||||
|
|
||||||
|
import checkValidDate from "../../../src/common/datetime/check_valid_date";
|
||||||
|
|
||||||
|
describe("checkValidDate", () => {
|
||||||
|
it("works", () => {
|
||||||
|
assert.strictEqual(checkValidDate(new Date()), true);
|
||||||
|
assert.strictEqual(
|
||||||
|
checkValidDate(new Date("2021-01-19T11:36:57+00:00")),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
checkValidDate(new Date("2021-01-19X11:36:57+00:00")),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert.strictEqual(checkValidDate(new Date("2021-01-19")), true);
|
||||||
|
assert.strictEqual(checkValidDate(undefined), false);
|
||||||
|
});
|
||||||
|
});
|
217
test-mocha/common/datetime/relative_time.ts
Normal file
217
test-mocha/common/datetime/relative_time.ts
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import { assert } from "chai";
|
||||||
|
|
||||||
|
import relativeTime from "../../../src/common/datetime/relative_time";
|
||||||
|
|
||||||
|
describe("relativeTime", () => {
|
||||||
|
// Mock localize function for testing
|
||||||
|
const localize = (message, ...args) =>
|
||||||
|
message + (args.length ? ": " + args.join(",") : "");
|
||||||
|
|
||||||
|
it("now", () => {
|
||||||
|
const now = new Date();
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(now, localize, { compareTime: now }),
|
||||||
|
"ui.components.relative_time.just_now"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("past_second", () => {
|
||||||
|
const inputdt = new Date("2021-02-03T11:22:00+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.past_duration.second: count,33"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.second: count,33"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("past_minute", () => {
|
||||||
|
const inputdt = new Date("2021-02-03T11:20:33+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.past_duration.minute: count,2"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.minute: count,2"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("past_hour", () => {
|
||||||
|
const inputdt = new Date("2021-02-03T09:22:33+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.past_duration.hour: count,2"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.hour: count,2"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("past_day", () => {
|
||||||
|
let inputdt = new Date("2021-02-01T11:22:33+00:00");
|
||||||
|
let compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.past_duration.day: count,2"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.day: count,2"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test switch from days to weeks
|
||||||
|
inputdt = new Date("2021-01-28T11:22:33+00:00");
|
||||||
|
compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.day: count,6"
|
||||||
|
);
|
||||||
|
inputdt = new Date("2021-01-27T11:22:33+00:00");
|
||||||
|
compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.notStrictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.day: count,7"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("past_week", () => {
|
||||||
|
const inputdt = new Date("2021-01-03T11:22:33+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.past_duration.week: count,4"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.week: count,4"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("future_second", () => {
|
||||||
|
const inputdt = new Date("2021-02-03T11:22:55+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.future_duration.second: count,22"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.second: count,22"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("future_minute", () => {
|
||||||
|
const inputdt = new Date("2021-02-03T11:24:33+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.future_duration.minute: count,2"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.minute: count,2"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("future_hour", () => {
|
||||||
|
const inputdt = new Date("2021-02-03T13:22:33+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.future_duration.hour: count,2"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.hour: count,2"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("future_day", () => {
|
||||||
|
let inputdt = new Date("2021-02-05T11:22:33+00:00");
|
||||||
|
let compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.future_duration.day: count,2"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.day: count,2"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test switch from days to weeks
|
||||||
|
inputdt = new Date("2021-02-09T11:22:33+00:00");
|
||||||
|
compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.day: count,6"
|
||||||
|
);
|
||||||
|
inputdt = new Date("2021-02-10T11:22:33+00:00");
|
||||||
|
compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.notStrictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.day: count,7"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("future_week", () => {
|
||||||
|
const inputdt = new Date("2021-03-03T11:22:33+00:00");
|
||||||
|
const compare = new Date("2021-02-03T11:22:33+00:00");
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, { compareTime: compare }),
|
||||||
|
"ui.components.relative_time.future_duration.week: count,4"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
relativeTime(inputdt, localize, {
|
||||||
|
compareTime: compare,
|
||||||
|
includeTense: false,
|
||||||
|
}),
|
||||||
|
"ui.components.relative_time.duration.week: count,4"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user