Skip to content

Commit

Permalink
Improve duration formatting (#23025)
Browse files Browse the repository at this point in the history
  • Loading branch information
piitaya authored Nov 27, 2024
1 parent bc195c6 commit 164944c
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 216 deletions.
109 changes: 0 additions & 109 deletions src/common/datetime/duration.ts

This file was deleted.

132 changes: 128 additions & 4 deletions src/common/datetime/format_duration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { DurationFormat } from "@formatjs/intl-durationformat";
import type { DurationInput } from "@formatjs/intl-durationformat/src/types";
import memoizeOne from "memoize-one";
import type { HaDurationData } from "../../components/ha-duration-input";
import type { FrontendLocaleData } from "../../data/translation";
import { round } from "../number/round";

const leftPad = (num: number) => (num < 10 ? `0${num}` : num);

Expand Down Expand Up @@ -44,10 +47,131 @@ export const formatNumericDuration = (
return null;
};

const formatDurationLongMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "long",
})
);

export const formatDurationLong = (
locale: FrontendLocaleData,
duration: HaDurationData
) =>
new DurationFormat(locale.language, {
style: "long",
}).format(duration);
) => formatDurationLongMem(locale).format(duration);

const formatDigitalDurationMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "digital",
hoursDisplay: "auto",
})
);

export const formatDurationDigital = (
locale: FrontendLocaleData,
duration: HaDurationData
) => formatDigitalDurationMem(locale).format(duration);

export const DURATION_UNITS = ["ms", "s", "min", "h", "d"] as const;

type DurationUnit = (typeof DURATION_UNITS)[number];

const formatDurationDayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "narrow",
daysDisplay: "always",
})
);

const formatDurationHourMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "narrow",
hoursDisplay: "always",
})
);

const formatDurationMinuteMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "narrow",
minutesDisplay: "always",
})
);

const formatDurationSecondMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "narrow",
secondsDisplay: "always",
})
);

const formatDurationMillisecondMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "narrow",
millisecondsDisplay: "always",
})
);

export const formatDuration = (
locale: FrontendLocaleData,
duration: string,
unit: DurationUnit,
precision?: number | undefined
): string => {
const value =
precision !== undefined
? round(parseFloat(duration), precision)
: parseFloat(duration);

switch (unit) {
case "d": {
const days = Math.floor(value);
const hours = Math.floor((value - days) * 24);
const input: DurationInput = {
days,
hours,
};
return formatDurationDayMem(locale).format(input);
}
case "h": {
const hours = Math.floor(value);
const minutes = Math.floor((value - hours) * 60);
const input: DurationInput = {
hours,
minutes,
};
return formatDurationHourMem(locale).format(input);
}
case "min": {
const minutes = Math.floor(value);
const seconds = Math.floor((value - minutes) * 60);
const input: DurationInput = {
minutes,
seconds,
};
return formatDurationMinuteMem(locale).format(input);
}
case "s": {
const seconds = Math.floor(value);
const milliseconds = Math.floor((value - seconds) * 1000);
const input: DurationInput = {
seconds,
milliseconds,
};
return formatDurationSecondMem(locale).format(input);
}
case "ms": {
const milliseconds = Math.floor(value);
const input: DurationInput = {
milliseconds,
};
return formatDurationMillisecondMem(locale).format(input);
}
default:
throw new Error("Invalid duration unit");
}
};
6 changes: 3 additions & 3 deletions src/common/entity/compute_state_display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import type { FrontendLocaleData } from "../../data/translation";
import { TimeZone } from "../../data/translation";
import type { HomeAssistant } from "../../types";
import { DURATION_UNITS, formatDuration } from "../datetime/duration";
import { DURATION_UNITS, formatDuration } from "../datetime/format_duration";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
Expand Down Expand Up @@ -72,10 +72,10 @@ export const computeStateDisplayFromEntityAttributes = (
) {
try {
return formatDuration(
locale,
state,
attributes.unit_of_measurement,
entity?.display_precision,
locale
entity?.display_precision
);
} catch (_err) {
// fallback to default
Expand Down
10 changes: 5 additions & 5 deletions src/data/automation_i18n.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { HassConfig } from "home-assistant-js-websocket";
import { ensureArray } from "../common/array/ensure-array";
import {
formatNumericDuration,
formatDurationLong,
formatNumericDuration,
} from "../common/datetime/format_duration";
import {
formatTime,
Expand All @@ -12,6 +12,10 @@ import secondsToDuration from "../common/datetime/seconds_to_duration";
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import {
formatListWithAnds,
formatListWithOrs,
} from "../common/string/format-list";
import type { HomeAssistant } from "../types";
import type { Condition, ForDict, Trigger } from "./automation";
import type { DeviceCondition, DeviceTrigger } from "./device_automation";
Expand All @@ -21,10 +25,6 @@ import {
} from "./device_automation";
import type { EntityRegistryEntry } from "./entity_registry";
import type { FrontendLocaleData } from "./translation";
import {
formatListWithAnds,
formatListWithOrs,
} from "../common/string/format-list";
import { isTriggerList } from "./trigger";

const triggerTranslationBaseKey =
Expand Down
10 changes: 7 additions & 3 deletions src/data/entity_attributes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatNumericDuration } from "../common/datetime/format_duration";
import { formatDurationDigital } from "../common/datetime/format_duration";
import type { FrontendLocaleData } from "./translation";

export const STATE_ATTRIBUTES = [
Expand Down Expand Up @@ -99,7 +99,11 @@ export const DOMAIN_ATTRIBUTES_FORMATERS: Record<
},
media_player: {
volume_level: (value) => Math.round(value * 100).toString(),
media_duration: (value, locale) =>
formatNumericDuration(locale, { seconds: value })!,
media_duration: (value, locale) => {
const hours = Math.floor(value / 3600);
const minutes = Math.floor((value % 3600) / 60);
const seconds = value % 60;
return formatDurationDigital(locale, { hours, minutes, seconds })!;
},
},
};
6 changes: 4 additions & 2 deletions src/panels/lovelace/cards/hui-entity-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
</ha-attribute-value>
`
: this.hass.localize("state.default.unknown")
: isNumericState(stateObj) || this._config.unit
: (isNumericState(stateObj) || this._config.unit) &&
stateObj.attributes.device_class !== "duration"
? formatNumber(
stateObj.state,
this.hass.locale,
Expand All @@ -185,7 +186,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
? html`
<span class="measurement"
>${this._config.unit ||
(this._config.attribute
(this._config.attribute ||
stateObj.attributes.device_class === "duration"
? ""
: stateObj.attributes.unit_of_measurement)}</span
>
Expand Down
Loading

0 comments on commit 164944c

Please sign in to comment.