Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions src/common/datetime/format_date.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
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";

const formatDateMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
})
);

export const formatDate = toLocaleDateStringSupportsOptions
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleDateString(locales.language, {
year: "numeric",
month: "long",
day: "numeric",
})
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateMem(locale).format(dateObj)
: (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
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleDateString(locales.language, {
weekday: "long",
month: "short",
day: "numeric",
})
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj)
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
56 changes: 36 additions & 20 deletions src/common/datetime/format_date_time.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
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 { 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
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleString(locales.language, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY, HH:mm");
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");

const formatDateTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
hour12: useAmPm(locale),
})
);

export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleString(locales.language, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
})
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY, HH:mm:ss");
? (dateObj: Date, locale: FrontendLocaleData) =>
formatDateTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
65 changes: 44 additions & 21 deletions src/common/datetime/format_time.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,52 @@
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 { 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
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleTimeString(locales.language, {
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => format(dateObj, "shortTime");
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");

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
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleTimeString(locales.language, {
hour: "numeric",
minute: "2-digit",
second: "2-digit",
})
: (dateObj: Date) => format(dateObj, "mediumTime");
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWithSecondsMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");

const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
hour: "numeric",
minute: "2-digit",
hour12: useAmPm(locale),
})
);

export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
? (dateObj: Date, locales: FrontendTranslationData) =>
dateObj.toLocaleTimeString(locales.language, {
weekday: "long",
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => format(dateObj, "dddd, HH:mm");
? (dateObj: Date, locale: FrontendLocaleData) =>
formatTimeWeekdayMem(locale).format(dateObj)
: (dateObj: Date, locale: FrontendLocaleData) =>
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
15 changes: 15 additions & 0 deletions src/common/datetime/use_am_pm.ts
Original file line number Diff line number Diff line change
@@ -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;
};
4 changes: 2 additions & 2 deletions src/common/entity/compute_state_display.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { FrontendTranslationData } from "../../data/translation";
import { FrontendLocaleData } from "../../data/translation";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
Expand All @@ -11,7 +11,7 @@ import { computeStateDomain } from "./compute_state_domain";
export const computeStateDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendTranslationData,
locale: FrontendLocaleData,
state?: string
): string => {
const compareState = state !== undefined ? state : stateObj.state;
Expand Down
4 changes: 2 additions & 2 deletions src/common/string/format_number.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -9,7 +9,7 @@ import { FrontendTranslationData, NumberFormat } from "../../data/translation";
*/
export const formatNumber = (
num: string | number,
locale?: FrontendTranslationData,
locale?: FrontendLocaleData,
options?: Intl.NumberFormatOptions
): string => {
let format: string | string[] | undefined;
Expand Down
13 changes: 2 additions & 11 deletions src/components/ha-date-range-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "lit";
import { customElement, property } from "lit/decorators";
import { formatDateTime } from "../common/datetime/format_date_time";
import { useAmPm } from "../common/datetime/use_am_pm";
import { computeRTLDirection } from "../common/util/compute_rtl";
import { HomeAssistant } from "../types";
import "./date-range-picker";
Expand Down Expand Up @@ -43,7 +44,7 @@ export class HaDateRangePicker extends LitElement {
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.locale !== this.hass.locale) {
this._hour24format = this._compute24hourFormat();
this._hour24format = !useAmPm(this.hass.locale);
this._rtlDirection = computeRTLDirection(this.hass);
}
}
Expand Down Expand Up @@ -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>) {
const dateRange = Object.values(this.ranges!)[ev.detail.index];
const dateRangePicker = this._dateRangePicker;
Expand Down
4 changes: 2 additions & 2 deletions src/components/ha-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import { formatNumber } from "../common/string/format_number";
import { afterNextRender } from "../common/util/render-status";
import { FrontendTranslationData } from "../data/translation";
import { FrontendLocaleData } from "../data/translation";
import { getValueInPercentage, normalize } from "../util/calculate";

const getAngle = (value: number, min: number, max: number) => {
Expand All @@ -23,7 +23,7 @@ export class Gauge extends LitElement {

@property({ type: Number }) public value = 0;

@property() public locale!: FrontendTranslationData;
@property() public locale!: FrontendLocaleData;

@property() public label = "";

Expand Down
14 changes: 8 additions & 6 deletions src/components/ha-selector/ha-selector-time.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { useAmPm } from "../../common/datetime/use_am_pm";
import { fireEvent } from "../../common/dom/fire_event";
import { TimeSelector } from "../../data/selector";
import { FrontendLocaleData } from "../../data/translation";
import { HomeAssistant } from "../../types";
import "../paper-time-input";

@customElement("ha-selector-time")
export class HaTimeSelector extends LitElement {
@property() public hass!: HomeAssistant;
Expand All @@ -17,13 +20,12 @@ export class HaTimeSelector extends LitElement {

@property({ type: Boolean }) public disabled = false;

private _useAmPm = memoizeOne((language: string) => {
const test = new Date().toLocaleString(language);
return test.includes("AM") || test.includes("PM");
});
private _useAmPmMem = memoizeOne((locale: FrontendLocaleData): boolean =>
useAmPm(locale)
);

protected render() {
const useAMPM = this._useAmPm(this.hass.locale.language);
const useAMPM = this._useAmPmMem(this.hass.locale);

const parts = this.value?.split(":") || [];
const hours = parts[0];
Expand All @@ -48,7 +50,7 @@ export class HaTimeSelector extends LitElement {

private _timeChanged(ev) {
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);
if (value && useAMPM) {
if (ev.target.amPm === "PM") {
Expand Down
4 changes: 2 additions & 2 deletions src/data/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { computeStateDomain } from "../common/entity/compute_state_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { LocalizeFunc } from "../common/translations/localize";
import { HomeAssistant } from "../types";
import { FrontendTranslationData } from "./translation";
import { FrontendLocaleData } from "./translation";

const DOMAINS_USE_LAST_UPDATED = ["climate", "humidifier", "water_heater"];
const LINE_ATTRIBUTES_TO_KEEP = [
Expand Down Expand Up @@ -109,7 +109,7 @@ const equalState = (obj1: LineChartState, obj2: LineChartState) =>

const processTimelineEntity = (
localize: LocalizeFunc,
language: FrontendTranslationData,
language: FrontendLocaleData,
states: HassEntity[]
): TimelineEntity => {
const data: TimelineState[] = [];
Expand Down
14 changes: 11 additions & 3 deletions src/data/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ export enum NumberFormat {
none = "none",
}

export interface FrontendTranslationData {
export enum TimeFormat {
language = "language",
system = "system",
am_pm = "12",
twenty_four = "24",
}

export interface FrontendLocaleData {
language: string;
number_format: NumberFormat;
time_format: TimeFormat;
}

declare global {
interface FrontendUserData {
language: FrontendTranslationData;
language: FrontendLocaleData;
}
}

Expand All @@ -36,7 +44,7 @@ export const fetchTranslationPreferences = (hass: HomeAssistant) =>

export const saveTranslationPreferences = (
hass: HomeAssistant,
data: FrontendTranslationData
data: FrontendLocaleData
) => saveFrontendUserData(hass.connection, "language", data);

export const getHassTranslations = async (
Expand Down
3 changes: 2 additions & 1 deletion src/fake_data/provide_hass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "../common/dom/apply_themes_on_element";
import { computeLocalize } from "../common/translations/localize";
import { DEFAULT_PANEL } from "../data/panel";
import { NumberFormat } from "../data/translation";
import { NumberFormat, TimeFormat } from "../data/translation";
import { translationMetadata } from "../resources/translations-metadata";
import { HomeAssistant } from "../types";
import { getLocalLanguage, getTranslation } from "../util/hass-translation";
Expand Down Expand Up @@ -215,6 +215,7 @@ export const provideHass = (
locale: {
language: localLanguage,
number_format: NumberFormat.language,
time_format: TimeFormat.language,
},
resources: null as any,
localize: () => "",
Expand Down
Loading