mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 01:06:35 +00:00
Allow users to select time format for UI rendering (#9042)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
87e4c209f4
commit
70a1edd1dd
@ -1,21 +1,32 @@
|
|||||||
import { format } from "fecha";
|
import { format } from "fecha";
|
||||||
import { FrontendTranslationData } from "../../data/translation";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
|
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
|
||||||
|
|
||||||
|
const formatDateMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatDate = toLocaleDateStringSupportsOptions
|
export const formatDate = toLocaleDateStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleDateString(locales.language, {
|
formatDateMem(locale).format(dateObj)
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
})
|
|
||||||
: (dateObj: Date) => format(dateObj, "longDate");
|
: (dateObj: Date) => format(dateObj, "longDate");
|
||||||
|
|
||||||
|
const formatDateWeekdayMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
weekday: "long",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleDateString(locales.language, {
|
formatDateWeekdayMem(locale).format(dateObj)
|
||||||
weekday: "long",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
})
|
|
||||||
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
|
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
|
||||||
|
@ -1,26 +1,42 @@
|
|||||||
import { format } from "fecha";
|
import { format } from "fecha";
|
||||||
import { FrontendTranslationData } from "../../data/translation";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { toLocaleStringSupportsOptions } from "./check_options_support";
|
import { toLocaleStringSupportsOptions } from "./check_options_support";
|
||||||
|
import { useAmPm } from "./use_am_pm";
|
||||||
|
|
||||||
|
const formatDateTimeMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatDateTime = toLocaleStringSupportsOptions
|
export const formatDateTime = toLocaleStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleString(locales.language, {
|
formatDateTimeMem(locale).format(dateObj)
|
||||||
year: "numeric",
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
month: "long",
|
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||||
day: "numeric",
|
|
||||||
hour: "numeric",
|
const formatDateTimeWithSecondsMem = memoizeOne(
|
||||||
minute: "2-digit",
|
(locale: FrontendLocaleData) =>
|
||||||
})
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY, HH:mm");
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleString(locales.language, {
|
formatDateTimeWithSecondsMem(locale).format(dateObj)
|
||||||
year: "numeric",
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
month: "long",
|
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
|
||||||
day: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
})
|
|
||||||
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY, HH:mm:ss");
|
|
||||||
|
@ -1,29 +1,52 @@
|
|||||||
import { format } from "fecha";
|
import { format } from "fecha";
|
||||||
import { FrontendTranslationData } from "../../data/translation";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
|
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
|
||||||
|
import { useAmPm } from "./use_am_pm";
|
||||||
|
|
||||||
|
const formatTimeMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatTime = toLocaleTimeStringSupportsOptions
|
export const formatTime = toLocaleTimeStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleTimeString(locales.language, {
|
formatTimeMem(locale).format(dateObj)
|
||||||
hour: "numeric",
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
minute: "2-digit",
|
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
|
||||||
})
|
|
||||||
: (dateObj: Date) => format(dateObj, "shortTime");
|
const formatTimeWithSecondsMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleTimeString(locales.language, {
|
formatTimeWithSecondsMem(locale).format(dateObj)
|
||||||
hour: "numeric",
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
minute: "2-digit",
|
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
|
||||||
second: "2-digit",
|
|
||||||
})
|
const formatTimeWeekdayMem = memoizeOne(
|
||||||
: (dateObj: Date) => format(dateObj, "mediumTime");
|
(locale: FrontendLocaleData) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
weekday: "long",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: useAmPm(locale),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
||||||
? (dateObj: Date, locales: FrontendTranslationData) =>
|
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
dateObj.toLocaleTimeString(locales.language, {
|
formatTimeWeekdayMem(locale).format(dateObj)
|
||||||
weekday: "long",
|
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||||
hour: "numeric",
|
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||||
minute: "2-digit",
|
|
||||||
})
|
|
||||||
: (dateObj: Date) => format(dateObj, "dddd, HH:mm");
|
|
||||||
|
15
src/common/datetime/use_am_pm.ts
Normal file
15
src/common/datetime/use_am_pm.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { FrontendLocaleData, TimeFormat } from "../../data/translation";
|
||||||
|
|
||||||
|
export const useAmPm = (locale: FrontendLocaleData): boolean => {
|
||||||
|
if (
|
||||||
|
locale.time_format === TimeFormat.language ||
|
||||||
|
locale.time_format === TimeFormat.system
|
||||||
|
) {
|
||||||
|
const testLanguage =
|
||||||
|
locale.time_format === TimeFormat.language ? locale.language : undefined;
|
||||||
|
const test = new Date().toLocaleString(testLanguage);
|
||||||
|
return test.includes("AM") || test.includes("PM");
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale.time_format === TimeFormat.am_pm;
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
import { FrontendTranslationData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { formatDate } from "../datetime/format_date";
|
import { formatDate } from "../datetime/format_date";
|
||||||
import { formatDateTime } from "../datetime/format_date_time";
|
import { formatDateTime } from "../datetime/format_date_time";
|
||||||
import { formatTime } from "../datetime/format_time";
|
import { formatTime } from "../datetime/format_time";
|
||||||
@ -11,7 +11,7 @@ import { computeStateDomain } from "./compute_state_domain";
|
|||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
locale: FrontendTranslationData,
|
locale: FrontendLocaleData,
|
||||||
state?: string
|
state?: string
|
||||||
): string => {
|
): string => {
|
||||||
const compareState = state !== undefined ? state : stateObj.state;
|
const compareState = state !== undefined ? state : stateObj.state;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FrontendTranslationData, NumberFormat } from "../../data/translation";
|
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
|
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
|
||||||
@ -9,7 +9,7 @@ import { FrontendTranslationData, NumberFormat } from "../../data/translation";
|
|||||||
*/
|
*/
|
||||||
export const formatNumber = (
|
export const formatNumber = (
|
||||||
num: string | number,
|
num: string | number,
|
||||||
locale?: FrontendTranslationData,
|
locale?: FrontendLocaleData,
|
||||||
options?: Intl.NumberFormatOptions
|
options?: Intl.NumberFormatOptions
|
||||||
): string => {
|
): string => {
|
||||||
let format: string | string[] | undefined;
|
let format: string | string[] | undefined;
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { formatDateTime } from "../common/datetime/format_date_time";
|
import { formatDateTime } from "../common/datetime/format_date_time";
|
||||||
|
import { useAmPm } from "../common/datetime/use_am_pm";
|
||||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./date-range-picker";
|
import "./date-range-picker";
|
||||||
@ -43,7 +44,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
if (changedProps.has("hass")) {
|
if (changedProps.has("hass")) {
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
if (!oldHass || oldHass.locale !== this.hass.locale) {
|
||||||
this._hour24format = this._compute24hourFormat();
|
this._hour24format = !useAmPm(this.hass.locale);
|
||||||
this._rtlDirection = computeRTLDirection(this.hass);
|
this._rtlDirection = computeRTLDirection(this.hass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,16 +107,6 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compute24hourFormat() {
|
|
||||||
return (
|
|
||||||
new Intl.DateTimeFormat(this.hass.language, {
|
|
||||||
hour: "numeric",
|
|
||||||
})
|
|
||||||
.formatToParts(new Date(2020, 0, 1, 13))
|
|
||||||
.find((part) => part.type === "hour")!.value.length === 2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setDateRange(ev: CustomEvent<ActionDetail>) {
|
private _setDateRange(ev: CustomEvent<ActionDetail>) {
|
||||||
const dateRange = Object.values(this.ranges!)[ev.detail.index];
|
const dateRange = Object.values(this.ranges!)[ev.detail.index];
|
||||||
const dateRangePicker = this._dateRangePicker;
|
const dateRangePicker = this._dateRangePicker;
|
||||||
|
@ -4,7 +4,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
|||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { formatNumber } from "../common/string/format_number";
|
import { formatNumber } from "../common/string/format_number";
|
||||||
import { afterNextRender } from "../common/util/render-status";
|
import { afterNextRender } from "../common/util/render-status";
|
||||||
import { FrontendTranslationData } from "../data/translation";
|
import { FrontendLocaleData } from "../data/translation";
|
||||||
import { getValueInPercentage, normalize } from "../util/calculate";
|
import { getValueInPercentage, normalize } from "../util/calculate";
|
||||||
|
|
||||||
const getAngle = (value: number, min: number, max: number) => {
|
const getAngle = (value: number, min: number, max: number) => {
|
||||||
@ -23,7 +23,7 @@ export class Gauge extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Number }) public value = 0;
|
@property({ type: Number }) public value = 0;
|
||||||
|
|
||||||
@property() public locale!: FrontendTranslationData;
|
@property() public locale!: FrontendLocaleData;
|
||||||
|
|
||||||
@property() public label = "";
|
@property() public label = "";
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { useAmPm } from "../../common/datetime/use_am_pm";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { TimeSelector } from "../../data/selector";
|
import { TimeSelector } from "../../data/selector";
|
||||||
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../paper-time-input";
|
import "../paper-time-input";
|
||||||
|
|
||||||
@customElement("ha-selector-time")
|
@customElement("ha-selector-time")
|
||||||
export class HaTimeSelector extends LitElement {
|
export class HaTimeSelector extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -17,13 +20,12 @@ export class HaTimeSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
private _useAmPm = memoizeOne((language: string) => {
|
private _useAmPmMem = memoizeOne((locale: FrontendLocaleData): boolean =>
|
||||||
const test = new Date().toLocaleString(language);
|
useAmPm(locale)
|
||||||
return test.includes("AM") || test.includes("PM");
|
);
|
||||||
});
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const useAMPM = this._useAmPm(this.hass.locale.language);
|
const useAMPM = this._useAmPmMem(this.hass.locale);
|
||||||
|
|
||||||
const parts = this.value?.split(":") || [];
|
const parts = this.value?.split(":") || [];
|
||||||
const hours = parts[0];
|
const hours = parts[0];
|
||||||
@ -48,7 +50,7 @@ export class HaTimeSelector extends LitElement {
|
|||||||
|
|
||||||
private _timeChanged(ev) {
|
private _timeChanged(ev) {
|
||||||
let value = ev.target.value;
|
let value = ev.target.value;
|
||||||
const useAMPM = this._useAmPm(this.hass.locale.language);
|
const useAMPM = this._useAmPmMem(this.hass.locale);
|
||||||
let hours = Number(ev.target.hour || 0);
|
let hours = Number(ev.target.hour || 0);
|
||||||
if (value && useAMPM) {
|
if (value && useAMPM) {
|
||||||
if (ev.target.amPm === "PM") {
|
if (ev.target.amPm === "PM") {
|
||||||
|
@ -4,7 +4,7 @@ import { computeStateDomain } from "../common/entity/compute_state_domain";
|
|||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { FrontendTranslationData } from "./translation";
|
import { FrontendLocaleData } from "./translation";
|
||||||
|
|
||||||
const DOMAINS_USE_LAST_UPDATED = ["climate", "humidifier", "water_heater"];
|
const DOMAINS_USE_LAST_UPDATED = ["climate", "humidifier", "water_heater"];
|
||||||
const LINE_ATTRIBUTES_TO_KEEP = [
|
const LINE_ATTRIBUTES_TO_KEEP = [
|
||||||
@ -109,7 +109,7 @@ const equalState = (obj1: LineChartState, obj2: LineChartState) =>
|
|||||||
|
|
||||||
const processTimelineEntity = (
|
const processTimelineEntity = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
language: FrontendTranslationData,
|
language: FrontendLocaleData,
|
||||||
states: HassEntity[]
|
states: HassEntity[]
|
||||||
): TimelineEntity => {
|
): TimelineEntity => {
|
||||||
const data: TimelineState[] = [];
|
const data: TimelineState[] = [];
|
||||||
|
@ -10,14 +10,22 @@ export enum NumberFormat {
|
|||||||
none = "none",
|
none = "none",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FrontendTranslationData {
|
export enum TimeFormat {
|
||||||
|
language = "language",
|
||||||
|
system = "system",
|
||||||
|
am_pm = "12",
|
||||||
|
twenty_four = "24",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontendLocaleData {
|
||||||
language: string;
|
language: string;
|
||||||
number_format: NumberFormat;
|
number_format: NumberFormat;
|
||||||
|
time_format: TimeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface FrontendUserData {
|
interface FrontendUserData {
|
||||||
language: FrontendTranslationData;
|
language: FrontendLocaleData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +44,7 @@ export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
|||||||
|
|
||||||
export const saveTranslationPreferences = (
|
export const saveTranslationPreferences = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
data: FrontendTranslationData
|
data: FrontendLocaleData
|
||||||
) => saveFrontendUserData(hass.connection, "language", data);
|
) => saveFrontendUserData(hass.connection, "language", data);
|
||||||
|
|
||||||
export const getHassTranslations = async (
|
export const getHassTranslations = async (
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
} from "../common/dom/apply_themes_on_element";
|
} from "../common/dom/apply_themes_on_element";
|
||||||
import { computeLocalize } from "../common/translations/localize";
|
import { computeLocalize } from "../common/translations/localize";
|
||||||
import { DEFAULT_PANEL } from "../data/panel";
|
import { DEFAULT_PANEL } from "../data/panel";
|
||||||
import { NumberFormat } from "../data/translation";
|
import { NumberFormat, TimeFormat } from "../data/translation";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
|
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
|
||||||
@ -215,6 +215,7 @@ export const provideHass = (
|
|||||||
locale: {
|
locale: {
|
||||||
language: localLanguage,
|
language: localLanguage,
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.language,
|
||||||
},
|
},
|
||||||
resources: null as any,
|
resources: null as any,
|
||||||
localize: () => "",
|
localize: () => "",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||||
import { formatTimeWithSeconds } from "../../../common/datetime/format_time";
|
import { formatTimeWithSeconds } from "../../../common/datetime/format_time";
|
||||||
import { FrontendTranslationData } from "../../../data/translation";
|
import { FrontendLocaleData } from "../../../data/translation";
|
||||||
|
|
||||||
export const formatSystemLogTime = (date, locale: FrontendTranslationData) => {
|
export const formatSystemLogTime = (date, locale: FrontendLocaleData) => {
|
||||||
const today = new Date().setHours(0, 0, 0, 0);
|
const today = new Date().setHours(0, 0, 0, 0);
|
||||||
const dateTime = new Date(date * 1000);
|
const dateTime = new Date(date * 1000);
|
||||||
const dateTimeDay = new Date(date * 1000).setHours(0, 0, 0, 0);
|
const dateTimeDay = new Date(date * 1000).setHours(0, 0, 0, 0);
|
||||||
|
@ -4,12 +4,12 @@ import { formatDate } from "../../../common/datetime/format_date";
|
|||||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
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 { FrontendTranslationData } from "../../../data/translation";
|
import { FrontendLocaleData } from "../../../data/translation";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { TimestampRenderingFormats } from "./types";
|
import { TimestampRenderingFormats } from "./types";
|
||||||
|
|
||||||
const FORMATS: {
|
const FORMATS: {
|
||||||
[key: string]: (ts: Date, lang: FrontendTranslationData) => string;
|
[key: string]: (ts: Date, lang: FrontendLocaleData) => string;
|
||||||
} = {
|
} = {
|
||||||
date: formatDate,
|
date: formatDate,
|
||||||
datetime: formatDateTime,
|
datetime: formatDateTime,
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
LovelaceCardConfig,
|
LovelaceCardConfig,
|
||||||
LovelaceConfig,
|
LovelaceConfig,
|
||||||
} from "../../data/lovelace";
|
} from "../../data/lovelace";
|
||||||
import { FrontendTranslationData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { Constructor, HomeAssistant } from "../../types";
|
import { Constructor, HomeAssistant } from "../../types";
|
||||||
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
|
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
|
||||||
import { LovelaceHeaderFooterConfig } from "./header-footer/types";
|
import { LovelaceHeaderFooterConfig } from "./header-footer/types";
|
||||||
@ -23,7 +23,7 @@ export interface Lovelace {
|
|||||||
editMode: boolean;
|
editMode: boolean;
|
||||||
urlPath: string | null;
|
urlPath: string | null;
|
||||||
mode: "generated" | "yaml" | "storage";
|
mode: "generated" | "yaml" | "storage";
|
||||||
locale: FrontendTranslationData;
|
locale: FrontendLocaleData;
|
||||||
enableFullEditMode: () => void;
|
enableFullEditMode: () => void;
|
||||||
setEditMode: (editMode: boolean) => void;
|
setEditMode: (editMode: boolean) => void;
|
||||||
saveConfig: (newConfig: LovelaceConfig) => Promise<void>;
|
saveConfig: (newConfig: LovelaceConfig) => Promise<void>;
|
||||||
|
@ -29,6 +29,7 @@ import "./ha-pick-dashboard-row";
|
|||||||
import "./ha-pick-language-row";
|
import "./ha-pick-language-row";
|
||||||
import "./ha-pick-number-format-row";
|
import "./ha-pick-number-format-row";
|
||||||
import "./ha-pick-theme-row";
|
import "./ha-pick-theme-row";
|
||||||
|
import "./ha-pick-time-format-row";
|
||||||
import "./ha-push-notifications-row";
|
import "./ha-push-notifications-row";
|
||||||
import "./ha-refresh-tokens-card";
|
import "./ha-refresh-tokens-card";
|
||||||
import "./ha-set-suspend-row";
|
import "./ha-set-suspend-row";
|
||||||
@ -96,6 +97,10 @@ class HaPanelProfile extends LitElement {
|
|||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-pick-number-format-row>
|
></ha-pick-number-format-row>
|
||||||
|
<ha-pick-time-format-row
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-pick-time-format-row>
|
||||||
<ha-pick-theme-row
|
<ha-pick-theme-row
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -40,7 +40,7 @@ class NumberFormatRow extends LitElement {
|
|||||||
>
|
>
|
||||||
${Object.values(NumberFormat).map((format) => {
|
${Object.values(NumberFormat).map((format) => {
|
||||||
const formattedNumber = formatNumber(1234567.89, {
|
const formattedNumber = formatNumber(1234567.89, {
|
||||||
language: this.hass.locale.language,
|
...this.hass.locale,
|
||||||
number_format: format,
|
number_format: format,
|
||||||
});
|
});
|
||||||
const value = this.hass.localize(
|
const value = this.hass.localize(
|
||||||
@ -48,7 +48,7 @@ class NumberFormatRow extends LitElement {
|
|||||||
);
|
);
|
||||||
const twoLine = value.slice(value.length - 2) !== "89"; // Display explicit number formats on one line
|
const twoLine = value.slice(value.length - 2) !== "89"; // Display explicit number formats on one line
|
||||||
return html`
|
return html`
|
||||||
<paper-item .format=${format}>
|
<paper-item .format=${format} .label=${value}>
|
||||||
<paper-item-body ?two-line=${twoLine}>
|
<paper-item-body ?two-line=${twoLine}>
|
||||||
<div>${value}</div>
|
<div>${value}</div>
|
||||||
${twoLine
|
${twoLine
|
||||||
|
72
src/panels/profile/ha-pick-time-format-row.ts
Normal file
72
src/panels/profile/ha-pick-time-format-row.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { formatTime } from "../../common/datetime/format_time";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-card";
|
||||||
|
import "../../components/ha-paper-dropdown-menu";
|
||||||
|
import "../../components/ha-settings-row";
|
||||||
|
import { TimeFormat } from "../../data/translation";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
@customElement("ha-pick-time-format-row")
|
||||||
|
class TimeFormatRow extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public narrow!: boolean;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const date = new Date();
|
||||||
|
return html`
|
||||||
|
<ha-settings-row .narrow=${this.narrow}>
|
||||||
|
<span slot="heading">
|
||||||
|
${this.hass.localize("ui.panel.profile.time_format.header")}
|
||||||
|
</span>
|
||||||
|
<span slot="description">
|
||||||
|
${this.hass.localize("ui.panel.profile.time_format.description")}
|
||||||
|
</span>
|
||||||
|
<ha-paper-dropdown-menu
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.profile.time_format.dropdown_label"
|
||||||
|
)}
|
||||||
|
dynamic-align
|
||||||
|
.disabled=${this.hass.locale === undefined}
|
||||||
|
>
|
||||||
|
<paper-listbox
|
||||||
|
slot="dropdown-content"
|
||||||
|
.selected=${this.hass.locale.time_format}
|
||||||
|
@iron-select=${this._handleFormatSelection}
|
||||||
|
attr-for-selected="format"
|
||||||
|
>
|
||||||
|
${Object.values(TimeFormat).map((format) => {
|
||||||
|
const formattedTime = formatTime(date, {
|
||||||
|
...this.hass.locale,
|
||||||
|
time_format: format,
|
||||||
|
});
|
||||||
|
const value = this.hass.localize(
|
||||||
|
`ui.panel.profile.time_format.formats.${format}`
|
||||||
|
);
|
||||||
|
return html` <paper-item .format=${format} .label=${value}>
|
||||||
|
<paper-item-body two-line>
|
||||||
|
<div>${value}</div>
|
||||||
|
<div secondary>${formattedTime}</div>
|
||||||
|
</paper-item-body>
|
||||||
|
</paper-item>`;
|
||||||
|
})}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-paper-dropdown-menu>
|
||||||
|
</ha-settings-row>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleFormatSelection(ev: CustomEvent) {
|
||||||
|
fireEvent(this, "hass-time-format-select", ev.detail.item.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-pick-time-format-row": TimeFormatRow;
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ import { subscribeFrontendUserData } from "../data/frontend";
|
|||||||
import { forwardHaptic } from "../data/haptics";
|
import { forwardHaptic } from "../data/haptics";
|
||||||
import { DEFAULT_PANEL } from "../data/panel";
|
import { DEFAULT_PANEL } from "../data/panel";
|
||||||
import { serviceCallWillDisconnect } from "../data/service";
|
import { serviceCallWillDisconnect } from "../data/service";
|
||||||
import { NumberFormat } from "../data/translation";
|
import { NumberFormat, TimeFormat } from "../data/translation";
|
||||||
import { subscribePanels } from "../data/ws-panels";
|
import { subscribePanels } from "../data/ws-panels";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { Constructor, ServiceCallResponse } from "../types";
|
import { Constructor, ServiceCallResponse } from "../types";
|
||||||
@ -49,6 +49,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
|||||||
locale: {
|
locale: {
|
||||||
language,
|
language,
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.language,
|
||||||
},
|
},
|
||||||
resources: null as any,
|
resources: null as any,
|
||||||
localize: () => "",
|
localize: () => "",
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
getHassTranslationsPre109,
|
getHassTranslationsPre109,
|
||||||
NumberFormat,
|
NumberFormat,
|
||||||
saveTranslationPreferences,
|
saveTranslationPreferences,
|
||||||
|
TimeFormat,
|
||||||
TranslationCategory,
|
TranslationCategory,
|
||||||
} from "../data/translation";
|
} from "../data/translation";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
@ -28,6 +29,9 @@ declare global {
|
|||||||
"hass-number-format-select": {
|
"hass-number-format-select": {
|
||||||
number_format: NumberFormat;
|
number_format: NumberFormat;
|
||||||
};
|
};
|
||||||
|
"hass-time-format-select": {
|
||||||
|
time_format: TimeFormat;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +68,9 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
this.addEventListener("hass-number-format-select", (e) => {
|
this.addEventListener("hass-number-format-select", (e) => {
|
||||||
this._selectNumberFormat((e as CustomEvent).detail, true);
|
this._selectNumberFormat((e as CustomEvent).detail, true);
|
||||||
});
|
});
|
||||||
|
this.addEventListener("hass-time-format-select", (e) => {
|
||||||
|
this._selectTimeFormat((e as CustomEvent).detail, true);
|
||||||
|
});
|
||||||
this._loadCoreTranslations(getLocalLanguage());
|
this._loadCoreTranslations(getLocalLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +102,13 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
// We just got number_format from backend, no need to save back
|
// We just got number_format from backend, no need to save back
|
||||||
this._selectNumberFormat(locale.number_format, false);
|
this._selectNumberFormat(locale.number_format, false);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
locale?.time_format &&
|
||||||
|
this.hass!.locale.time_format !== locale.time_format
|
||||||
|
) {
|
||||||
|
// We just got time_format from backend, no need to save back
|
||||||
|
this._selectTimeFormat(locale.time_format, false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hass!.connection.subscribeEvents(
|
this.hass!.connection.subscribeEvents(
|
||||||
@ -133,6 +147,15 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _selectTimeFormat(time_format: TimeFormat, saveToBackend: boolean) {
|
||||||
|
this._updateHass({
|
||||||
|
locale: { ...this.hass!.locale, time_format: time_format },
|
||||||
|
});
|
||||||
|
if (saveToBackend) {
|
||||||
|
saveTranslationPreferences(this.hass!, this.hass!.locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _selectLanguage(language: string, saveToBackend: boolean) {
|
private _selectLanguage(language: string, saveToBackend: boolean) {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
// should not happen, do it to avoid use this.hass!
|
// should not happen, do it to avoid use this.hass!
|
||||||
|
@ -3213,6 +3213,17 @@
|
|||||||
"none": "None"
|
"none": "None"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"time_format": {
|
||||||
|
"header": "Time Format",
|
||||||
|
"dropdown_label": "Time format",
|
||||||
|
"description": "Choose how times are formatted.",
|
||||||
|
"formats": {
|
||||||
|
"language": "Auto (use language setting)",
|
||||||
|
"system": "Use system locale",
|
||||||
|
"12": "12 hours (AM/PM)",
|
||||||
|
"24": "24 hours"
|
||||||
|
}
|
||||||
|
},
|
||||||
"themes": {
|
"themes": {
|
||||||
"header": "Theme",
|
"header": "Theme",
|
||||||
"error_no_theme": "No themes available.",
|
"error_no_theme": "No themes available.",
|
||||||
|
13
src/types.ts
13
src/types.ts
@ -9,10 +9,7 @@ import {
|
|||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { LocalizeFunc } from "./common/translations/localize";
|
import { LocalizeFunc } from "./common/translations/localize";
|
||||||
import { CoreFrontendUserData } from "./data/frontend";
|
import { CoreFrontendUserData } from "./data/frontend";
|
||||||
import {
|
import { FrontendLocaleData, getHassTranslations } from "./data/translation";
|
||||||
FrontendTranslationData,
|
|
||||||
getHassTranslations,
|
|
||||||
} from "./data/translation";
|
|
||||||
import { Themes } from "./data/ws-themes";
|
import { Themes } from "./data/ws-themes";
|
||||||
import { ExternalMessaging } from "./external_app/external_messaging";
|
import { ExternalMessaging } from "./external_app/external_messaging";
|
||||||
|
|
||||||
@ -198,14 +195,14 @@ export interface HomeAssistant {
|
|||||||
panelUrl: string;
|
panelUrl: string;
|
||||||
// i18n
|
// i18n
|
||||||
// current effective language in that order:
|
// current effective language in that order:
|
||||||
// - backend saved user selected lanugage
|
// - backend saved user selected language
|
||||||
// - language in local appstorage
|
// - language in local app storage
|
||||||
// - browser language
|
// - browser language
|
||||||
// - english (en)
|
// - english (en)
|
||||||
language: string;
|
language: string;
|
||||||
// local stored language, keep that name for backward compability
|
// local stored language, keep that name for backward compatibility
|
||||||
selectedLanguage: string | null;
|
selectedLanguage: string | null;
|
||||||
locale: FrontendTranslationData;
|
locale: FrontendLocaleData;
|
||||||
resources: Resources;
|
resources: Resources;
|
||||||
localize: LocalizeFunc;
|
localize: LocalizeFunc;
|
||||||
translationMetadata: TranslationMetadata;
|
translationMetadata: TranslationMetadata;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
fetchTranslationPreferences,
|
fetchTranslationPreferences,
|
||||||
FrontendTranslationData,
|
FrontendLocaleData,
|
||||||
} from "../data/translation";
|
} from "../data/translation";
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -55,21 +55,24 @@ export function findAvailableLanguage(language: string) {
|
|||||||
*/
|
*/
|
||||||
export async function getUserLocale(
|
export async function getUserLocale(
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
): Promise<Partial<FrontendTranslationData>> {
|
): Promise<Partial<FrontendLocaleData>> {
|
||||||
const result = await fetchTranslationPreferences(hass);
|
const result = await fetchTranslationPreferences(hass);
|
||||||
const language = result?.language;
|
const language = result?.language;
|
||||||
const number_format = result?.number_format;
|
const number_format = result?.number_format;
|
||||||
|
const time_format = result?.time_format;
|
||||||
if (language) {
|
if (language) {
|
||||||
const availableLanguage = findAvailableLanguage(language);
|
const availableLanguage = findAvailableLanguage(language);
|
||||||
if (availableLanguage) {
|
if (availableLanguage) {
|
||||||
return {
|
return {
|
||||||
language: availableLanguage,
|
language: availableLanguage,
|
||||||
number_format,
|
number_format,
|
||||||
|
time_format,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
number_format,
|
number_format,
|
||||||
|
time_format,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { assert } from "chai";
|
import { assert } from "chai";
|
||||||
|
|
||||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||||
import { NumberFormat } from "../../../src/data/translation";
|
import { NumberFormat, TimeFormat } from "../../../src/data/translation";
|
||||||
|
|
||||||
describe("formatDate", () => {
|
describe("formatDate", () => {
|
||||||
const dateObj = new Date(2017, 10, 18, 11, 12, 13, 1400);
|
const dateObj = new Date(2017, 10, 18, 11, 12, 13, 1400);
|
||||||
@ -11,6 +11,7 @@ describe("formatDate", () => {
|
|||||||
formatDate(dateObj, {
|
formatDate(dateObj, {
|
||||||
language: "en",
|
language: "en",
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.language,
|
||||||
}),
|
}),
|
||||||
"November 18, 2017"
|
"November 18, 2017"
|
||||||
);
|
);
|
||||||
|
@ -4,32 +4,50 @@ import {
|
|||||||
formatDateTime,
|
formatDateTime,
|
||||||
formatDateTimeWithSeconds,
|
formatDateTimeWithSeconds,
|
||||||
} from "../../../src/common/datetime/format_date_time";
|
} from "../../../src/common/datetime/format_date_time";
|
||||||
import { NumberFormat } from "../../../src/data/translation";
|
import { NumberFormat, TimeFormat } from "../../../src/data/translation";
|
||||||
|
|
||||||
describe("formatDateTime", () => {
|
describe("formatDateTime", () => {
|
||||||
const dateObj = new Date(2017, 10, 18, 11, 12, 13, 400);
|
const dateObj = new Date(2017, 10, 18, 23, 12, 13, 400);
|
||||||
|
|
||||||
it("Formats English date times", () => {
|
it("Formats English date times", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatDateTime(dateObj, {
|
formatDateTime(dateObj, {
|
||||||
language: "en",
|
language: "en",
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
}),
|
}),
|
||||||
"November 18, 2017, 11:12 AM"
|
"November 18, 2017, 11:12 PM"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
formatDateTime(dateObj, {
|
||||||
|
language: "en",
|
||||||
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
}),
|
||||||
|
"November 18, 2017, 23:12"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("formatDateTimeWithSeconds", () => {
|
describe("formatDateTimeWithSeconds", () => {
|
||||||
const dateObj = new Date(2017, 10, 18, 11, 12, 13, 400);
|
const dateObj = new Date(2017, 10, 18, 23, 12, 13, 400);
|
||||||
|
|
||||||
it("Formats English date times with seconds", () => {
|
it("Formats English date times with seconds", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatDateTimeWithSeconds(dateObj, {
|
formatDateTimeWithSeconds(dateObj, {
|
||||||
language: "en",
|
language: "en",
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
}),
|
}),
|
||||||
"November 18, 2017, 11:12:13 AM"
|
"November 18, 2017, 11:12:13 PM"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
formatDateTimeWithSeconds(dateObj, {
|
||||||
|
language: "en",
|
||||||
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
}),
|
||||||
|
"November 18, 2017, 23:12:13"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,32 +4,50 @@ import {
|
|||||||
formatTime,
|
formatTime,
|
||||||
formatTimeWithSeconds,
|
formatTimeWithSeconds,
|
||||||
} from "../../../src/common/datetime/format_time";
|
} from "../../../src/common/datetime/format_time";
|
||||||
import { NumberFormat } from "../../../src/data/translation";
|
import { NumberFormat, TimeFormat } from "../../../src/data/translation";
|
||||||
|
|
||||||
describe("formatTime", () => {
|
describe("formatTime", () => {
|
||||||
const dateObj = new Date(2017, 10, 18, 11, 12, 13, 1400);
|
const dateObj = new Date(2017, 10, 18, 23, 12, 13, 1400);
|
||||||
|
|
||||||
it("Formats English times", () => {
|
it("Formats English times", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatTime(dateObj, {
|
formatTime(dateObj, {
|
||||||
language: "en",
|
language: "en",
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
}),
|
}),
|
||||||
"11:12 AM"
|
"11:12 PM"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
formatTime(dateObj, {
|
||||||
|
language: "en",
|
||||||
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
}),
|
||||||
|
"23:12"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("formatTimeWithSeconds", () => {
|
describe("formatTimeWithSeconds", () => {
|
||||||
const dateObj = new Date(2017, 10, 18, 11, 12, 13, 400);
|
const dateObj = new Date(2017, 10, 18, 23, 12, 13, 400);
|
||||||
|
|
||||||
it("Formats English times with seconds", () => {
|
it("Formats English times with seconds", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatTimeWithSeconds(dateObj, {
|
formatTimeWithSeconds(dateObj, {
|
||||||
language: "en",
|
language: "en",
|
||||||
number_format: NumberFormat.language,
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
}),
|
}),
|
||||||
"11:12:13 AM"
|
"11:12:13 PM"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
formatTimeWithSeconds(dateObj, {
|
||||||
|
language: "en",
|
||||||
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
}),
|
||||||
|
"23:12:13"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,20 +2,26 @@ import { assert } from "chai";
|
|||||||
import { computeStateDisplay } from "../../../src/common/entity/compute_state_display";
|
import { computeStateDisplay } from "../../../src/common/entity/compute_state_display";
|
||||||
import { UNKNOWN } from "../../../src/data/entity";
|
import { UNKNOWN } from "../../../src/data/entity";
|
||||||
import {
|
import {
|
||||||
FrontendTranslationData,
|
FrontendLocaleData,
|
||||||
NumberFormat,
|
NumberFormat,
|
||||||
|
TimeFormat,
|
||||||
} from "../../../src/data/translation";
|
} from "../../../src/data/translation";
|
||||||
|
|
||||||
const localeData: FrontendTranslationData = {
|
let localeData: FrontendLocaleData;
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.comma_decimal,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("computeStateDisplay", () => {
|
describe("computeStateDisplay", () => {
|
||||||
// Mock Localize function for testing
|
// Mock Localize function for testing
|
||||||
const localize = (message, ...args) =>
|
const localize = (message, ...args) =>
|
||||||
message + (args.length ? ": " + args.join(",") : "");
|
message + (args.length ? ": " + args.join(",") : "");
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localeData = {
|
||||||
|
language: "en",
|
||||||
|
number_format: NumberFormat.comma_decimal,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
it("Localizes binary sensor defaults", () => {
|
it("Localizes binary sensor defaults", () => {
|
||||||
const stateObj: any = {
|
const stateObj: any = {
|
||||||
entity_id: "binary_sensor.test",
|
entity_id: "binary_sensor.test",
|
||||||
@ -148,7 +154,7 @@ describe("computeStateDisplay", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Localizes input_datetime with full date time", () => {
|
describe("Localizes input_datetime with full date time", () => {
|
||||||
const stateObj: any = {
|
const stateObj: any = {
|
||||||
entity_id: "input_datetime.test",
|
entity_id: "input_datetime.test",
|
||||||
state: "123",
|
state: "123",
|
||||||
@ -158,15 +164,24 @@ describe("computeStateDisplay", () => {
|
|||||||
year: 2017,
|
year: 2017,
|
||||||
month: 11,
|
month: 11,
|
||||||
day: 18,
|
day: 18,
|
||||||
hour: 11,
|
hour: 23,
|
||||||
minute: 12,
|
minute: 12,
|
||||||
second: 13,
|
second: 13,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
it("Uses am/pm time format", () => {
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
assert.strictEqual(
|
||||||
"November 18, 2017, 11:12 AM"
|
computeStateDisplay(localize, stateObj, localeData),
|
||||||
);
|
"November 18, 2017, 11:12 PM"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("Uses 24h time format", () => {
|
||||||
|
localeData.time_format = TimeFormat.twenty_four;
|
||||||
|
assert.strictEqual(
|
||||||
|
computeStateDisplay(localize, stateObj, localeData),
|
||||||
|
"November 18, 2017, 23:12"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Localizes input_datetime with date", () => {
|
it("Localizes input_datetime with date", () => {
|
||||||
@ -179,7 +194,7 @@ describe("computeStateDisplay", () => {
|
|||||||
year: 2017,
|
year: 2017,
|
||||||
month: 11,
|
month: 11,
|
||||||
day: 18,
|
day: 18,
|
||||||
hour: 11,
|
hour: 23,
|
||||||
minute: 12,
|
minute: 12,
|
||||||
second: 13,
|
second: 13,
|
||||||
},
|
},
|
||||||
@ -190,7 +205,7 @@ describe("computeStateDisplay", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Localizes input_datetime with time", () => {
|
describe("Localizes input_datetime with time", () => {
|
||||||
const stateObj: any = {
|
const stateObj: any = {
|
||||||
entity_id: "input_datetime.test",
|
entity_id: "input_datetime.test",
|
||||||
state: "123",
|
state: "123",
|
||||||
@ -200,15 +215,25 @@ describe("computeStateDisplay", () => {
|
|||||||
year: 2017,
|
year: 2017,
|
||||||
month: 11,
|
month: 11,
|
||||||
day: 18,
|
day: 18,
|
||||||
hour: 11,
|
hour: 23,
|
||||||
minute: 12,
|
minute: 12,
|
||||||
second: 13,
|
second: 13,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert.strictEqual(
|
it("Uses am/pm time format", () => {
|
||||||
computeStateDisplay(localize, stateObj, localeData),
|
localeData.time_format = TimeFormat.am_pm;
|
||||||
"11:12 AM"
|
assert.strictEqual(
|
||||||
);
|
computeStateDisplay(localize, stateObj, localeData),
|
||||||
|
"11:12 PM"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("Uses 24h time format", () => {
|
||||||
|
localeData.time_format = TimeFormat.twenty_four;
|
||||||
|
assert.strictEqual(
|
||||||
|
computeStateDisplay(localize, stateObj, localeData),
|
||||||
|
"23:12"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Localizes unavailable", () => {
|
it("Localizes unavailable", () => {
|
||||||
@ -230,10 +255,9 @@ describe("computeStateDisplay", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Localizes custom state", () => {
|
it("Localizes custom state", () => {
|
||||||
const altLocalize = () => {
|
const altLocalize = () =>
|
||||||
// No matches can be found
|
// No matches can be found
|
||||||
return "";
|
"";
|
||||||
};
|
|
||||||
const stateObj: any = {
|
const stateObj: any = {
|
||||||
entity_id: "sensor.test",
|
entity_id: "sensor.test",
|
||||||
state: "My Custom State",
|
state: "My Custom State",
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
import { assert } from "chai";
|
import { assert } from "chai";
|
||||||
|
|
||||||
import { formatNumber } from "../../../src/common/string/format_number";
|
import { formatNumber } from "../../../src/common/string/format_number";
|
||||||
import { NumberFormat } from "../../../src/data/translation";
|
import {
|
||||||
|
FrontendLocaleData,
|
||||||
|
NumberFormat,
|
||||||
|
TimeFormat,
|
||||||
|
} from "../../../src/data/translation";
|
||||||
|
|
||||||
describe("formatNumber", () => {
|
describe("formatNumber", () => {
|
||||||
|
// Create default to not have to specify a not relevant TimeFormat over and over again.
|
||||||
|
const defaultLocale: FrontendLocaleData = {
|
||||||
|
language: "en",
|
||||||
|
number_format: NumberFormat.language,
|
||||||
|
time_format: TimeFormat.language,
|
||||||
|
};
|
||||||
|
|
||||||
// Node only ships with English support for `Intl`, so we can not test for other number formats here.
|
// Node only ships with English support for `Intl`, so we can not test for other number formats here.
|
||||||
it("Formats English numbers", () => {
|
it("Formats English numbers", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(formatNumber(1234.5, defaultLocale), "1,234.5");
|
||||||
formatNumber(1234.5, {
|
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.language,
|
|
||||||
}),
|
|
||||||
"1,234.5"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test format 'none' (keep dot despite language 'de')", () => {
|
it("Test format 'none' (keep dot despite language 'de')", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatNumber(1.23, {
|
formatNumber(1.23, {
|
||||||
|
...defaultLocale,
|
||||||
language: "de",
|
language: "de",
|
||||||
number_format: NumberFormat.none,
|
number_format: NumberFormat.none,
|
||||||
}),
|
}),
|
||||||
@ -26,57 +32,32 @@ describe("formatNumber", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Ensure zero is kept for format 'language'", () => {
|
it("Ensure zero is kept for format 'language'", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(formatNumber(0, defaultLocale), "0");
|
||||||
formatNumber(0, {
|
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.language,
|
|
||||||
}),
|
|
||||||
"0"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Ensure zero is kept for format 'none'", () => {
|
it("Ensure zero is kept for format 'none'", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatNumber(0, {
|
formatNumber(0, { ...defaultLocale, number_format: NumberFormat.none }),
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.none,
|
|
||||||
}),
|
|
||||||
"0"
|
"0"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test empty string input for format 'none'", () => {
|
it("Test empty string input for format 'none'", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatNumber("", {
|
formatNumber("", { ...defaultLocale, number_format: NumberFormat.none }),
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.none,
|
|
||||||
}),
|
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test empty string input for format 'language'", () => {
|
it("Test empty string input for format 'language'", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(formatNumber("", defaultLocale), "0");
|
||||||
formatNumber("", {
|
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.language,
|
|
||||||
}),
|
|
||||||
"0"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Formats number with options", () => {
|
it("Formats number with options", () => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
formatNumber(
|
formatNumber(1234.5, defaultLocale, {
|
||||||
1234.5,
|
minimumFractionDigits: 2,
|
||||||
{
|
}),
|
||||||
language: "en",
|
|
||||||
number_format: NumberFormat.language,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"1,234.50"
|
"1,234.50"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs"
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user