From 780de42e4bdeb4e84000271d452f515b7b799c5b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 13 Jun 2023 12:12:13 +0200 Subject: [PATCH] Allow to show times in the UI in the timezone of the server (#16799) --- gallery/src/pages/date-time/date.ts | 59 ++++++--- gallery/src/pages/misc/entity-state.ts | 1 + .../components/supervisor-backup-content.ts | 10 +- src/common/datetime/absolute_time.ts | 8 +- src/common/datetime/calc_date.ts | 25 ++++ src/common/datetime/format_date.ts | 119 ++++++++++++------ src/common/datetime/format_date_time.ts | 47 ++++--- src/common/datetime/format_time.ts | 39 ++++-- .../entity/compute_attribute_display.ts | 8 +- src/common/entity/compute_state_display.ts | 30 +++-- src/common/translations/day_names.ts | 10 +- src/common/translations/month_names.ts | 10 +- src/components/chart/chart-date-adapter.ts | 84 ++++++++++--- .../chart/state-history-chart-line.ts | 1 + .../chart/state-history-chart-timeline.ts | 13 +- src/components/chart/statistics-chart.ts | 1 + .../entity/ha-entity-state-picker.ts | 2 + src/components/entity/ha-state-label-badge.ts | 1 + src/components/ha-absolute-time.ts | 6 +- src/components/ha-attributes.ts | 1 + src/components/ha-climate-state.ts | 3 + src/components/ha-date-input.ts | 10 +- src/components/ha-date-range-picker.ts | 94 ++++++++++++-- src/components/trace/ha-trace-path-details.ts | 3 +- src/components/trace/hat-trace-timeline.ts | 6 +- src/data/automation_i18n.ts | 45 ++++--- src/data/context.ts | 3 +- src/data/energy.ts | 46 +++++-- src/data/history.ts | 8 +- src/data/logbook.ts | 1 + src/data/timer.ts | 2 + src/data/translation.ts | 6 + .../components/fan/ha-more-info-fan-speed.ts | 1 + .../components/ha-more-info-state-header.ts | 1 + .../more-info/controls/more-info-climate.ts | 4 + .../more-info/controls/more-info-cover.ts | 1 + .../more-info/controls/more-info-fan.ts | 3 + .../controls/more-info-humidifier.ts | 1 + .../more-info/controls/more-info-light.ts | 2 + .../controls/more-info-media_player.ts | 2 + .../more-info/controls/more-info-remote.ts | 1 + .../more-info/controls/more-info-sun.ts | 3 +- .../more-info/controls/more-info-vacuum.ts | 4 + .../more-info/controls/more-info-weather.ts | 6 +- .../configurator-notification-item.ts | 1 + .../persistent-notification-item.ts | 2 +- src/fake_data/provide_hass.ts | 2 + .../calendar/dialog-calendar-event-detail.ts | 21 ++-- src/panels/calendar/recurrence.ts | 8 +- .../config/automation/ha-automation-picker.ts | 12 +- .../config/automation/ha-automation-trace.ts | 3 +- .../config/cloud/account/cloud-account.ts | 3 +- .../dialog-cloud-certificate.ts | 3 +- .../mqtt/mqtt-messages.ts | 3 +- .../config/hardware/ha-config-hardware.ts | 1 + .../config/helpers/forms/ha-schedule-form.ts | 10 +- .../integrations/dialog-add-integration.ts | 3 +- .../mqtt/mqtt-subscribe-card.ts | 2 +- .../config/logs/dialog-system-log-detail.ts | 12 +- src/panels/config/logs/system-log-card.ts | 12 +- src/panels/config/logs/util.ts | 11 +- .../config/repairs/dialog-repairs-issue.ts | 3 +- .../repairs/dialog-system-information.ts | 12 +- src/panels/config/scene/ha-scene-dashboard.ts | 6 +- src/panels/config/script/ha-script-picker.ts | 8 +- src/panels/config/script/ha-script-trace.ts | 3 +- .../debug/assist-pipeline-debug.ts | 3 +- .../event/event-subscribe-card.ts | 3 +- .../dialog-statistics-adjust-sum.ts | 12 +- src/panels/history/ha-panel-history.ts | 38 +----- src/panels/logbook/ha-logbook-renderer.ts | 9 +- src/panels/logbook/ha-panel-logbook.ts | 32 ----- .../cards/energy/hui-energy-compare-card.ts | 17 ++- .../cards/energy/hui-energy-gas-graph-card.ts | 14 ++- .../energy/hui-energy-solar-graph-card.ts | 14 ++- .../energy/hui-energy-usage-graph-card.ts | 14 ++- .../energy/hui-energy-water-graph-card.ts | 14 ++- src/panels/lovelace/cards/hui-button-card.ts | 12 +- src/panels/lovelace/cards/hui-entity-card.ts | 2 + src/panels/lovelace/cards/hui-glance-card.ts | 1 + .../lovelace/cards/hui-humidifier-card.ts | 1 + src/panels/lovelace/cards/hui-light-card.ts | 1 + src/panels/lovelace/cards/hui-map-card.ts | 14 ++- .../lovelace/cards/hui-picture-entity-card.ts | 1 + .../lovelace/cards/hui-picture-glance-card.ts | 2 + .../lovelace/cards/hui-thermostat-card.ts | 3 + src/panels/lovelace/cards/hui-tile-card.ts | 1 + .../cards/hui-weather-forecast-card.ts | 12 +- .../components/hui-energy-period-selector.ts | 57 ++++++--- .../components/hui-timestamp-display.ts | 11 +- .../elements/hui-state-label-element.ts | 1 + .../entity-rows/hui-group-entity-row.ts | 1 + .../entity-rows/hui-humidifier-entity-row.ts | 1 + .../hui-input-number-entity-row.ts | 1 + .../hui-media-player-entity-row.ts | 1 + .../entity-rows/hui-number-entity-row.ts | 1 + .../entity-rows/hui-select-entity-row.ts | 1 + .../entity-rows/hui-sensor-entity-row.ts | 1 + .../entity-rows/hui-simple-entity-row.ts | 1 + .../entity-rows/hui-toggle-entity-row.ts | 1 + .../entity-rows/hui-weather-entity-row.ts | 1 + .../special-rows/hui-attribute-row.ts | 1 + .../hui-fan-speed-tile-feature.ts | 1 + src/panels/profile/ha-panel-profile.ts | 5 + src/panels/profile/ha-pick-date-format-row.ts | 12 +- src/panels/profile/ha-pick-time-format-row.ts | 12 +- src/panels/profile/ha-pick-time-zone-row.ts | 76 +++++++++++ src/state-summary/state-card-display.ts | 3 +- src/state-summary/state-card-input_number.ts | 1 + src/state-summary/state-card-select.ts | 1 + src/state/connection-mixin.ts | 2 + src/state/translations-mixin.ts | 23 ++++ src/translations/en.json | 9 ++ src/util/common-translation.ts | 7 +- test/common/datetime/format_date.ts | 21 ++-- test/common/datetime/format_date_time.ts | 79 +++++++----- test/common/datetime/format_time.ts | 116 ++++++++++------- test/common/datetime/relative_time.ts | 3 + test/common/entity/compute_state_display.ts | 66 +++++++--- test/common/string/format_number.ts | 2 + 120 files changed, 1169 insertions(+), 442 deletions(-) create mode 100644 src/common/datetime/calc_date.ts create mode 100644 src/panels/profile/ha-pick-time-zone-row.ts diff --git a/gallery/src/pages/date-time/date.ts b/gallery/src/pages/date-time/date.ts index c344f6f7cc..90776e89b1 100644 --- a/gallery/src/pages/date-time/date.ts +++ b/gallery/src/pages/date-time/date.ts @@ -10,7 +10,9 @@ import { TimeFormat, DateFormat, FirstWeekday, + TimeZone, } from "../../../../src/data/translation"; +import "@material/mwc-list/mwc-list"; @customElement("demo-date-time-date") export class DemoDateTimeDate extends LitElement { @@ -22,6 +24,7 @@ export class DemoDateTimeDate extends LitElement { number_format: NumberFormat.language, time_format: TimeFormat.language, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.language, }; const date = new Date(); @@ -41,32 +44,48 @@ export class DemoDateTimeDate extends LitElement {
${value.nativeName}
- ${formatDateNumeric(date, { - ...defaultLocale, - language: key, - date_format: DateFormat.language, - })} + ${formatDateNumeric( + date, + { + ...defaultLocale, + language: key, + date_format: DateFormat.language, + }, + this.hass.config + )}
- ${formatDateNumeric(date, { - ...defaultLocale, - language: key, - date_format: DateFormat.DMY, - })} + ${formatDateNumeric( + date, + { + ...defaultLocale, + language: key, + date_format: DateFormat.DMY, + }, + this.hass.config + )}
- ${formatDateNumeric(date, { - ...defaultLocale, - language: key, - date_format: DateFormat.MDY, - })} + ${formatDateNumeric( + date, + { + ...defaultLocale, + language: key, + date_format: DateFormat.MDY, + }, + this.hass.config + )}
- ${formatDateNumeric(date, { - ...defaultLocale, - language: key, - date_format: DateFormat.YMD, - })} + ${formatDateNumeric( + date, + { + ...defaultLocale, + language: key, + date_format: DateFormat.YMD, + }, + this.hass.config + )}
` diff --git a/gallery/src/pages/misc/entity-state.ts b/gallery/src/pages/misc/entity-state.ts index 01beaa0507..aa0905d9b2 100644 --- a/gallery/src/pages/misc/entity-state.ts +++ b/gallery/src/pages/misc/entity-state.ts @@ -354,6 +354,7 @@ export class DemoEntityState extends LitElement { hass.localize, entry.stateObj, hass.locale, + hass.config, hass.entities )}`, }, diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index 3480cf9aba..ded21bf63a 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -143,7 +143,11 @@ export class SupervisorBackupContent extends LitElement { : this._localize("partial_backup")} (${Math.ceil(this.backup.size * 10) / 10 + " MB"})
${this.hass - ? formatDateTime(new Date(this.backup.date), this.hass.locale) + ? formatDateTime( + new Date(this.backup.date), + this.hass.locale, + this.hass.config + ) : this.backup.date} ` : html` { const _to = to ?? new Date(); if (isSameDay(from, _to)) { - return formatTime(from, locale); + return formatTime(from, locale, config); } if (isSameYear(from, _to)) { - return formatShortDateTime(from, locale); + return formatShortDateTime(from, locale, config); } - return formatShortDateTimeWithYear(from, locale); + return formatShortDateTimeWithYear(from, locale, config); }; diff --git a/src/common/datetime/calc_date.ts b/src/common/datetime/calc_date.ts new file mode 100644 index 0000000000..5a4da09a29 --- /dev/null +++ b/src/common/datetime/calc_date.ts @@ -0,0 +1,25 @@ +import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz"; +import { HassConfig } from "home-assistant-js-websocket"; +import { FrontendLocaleData, TimeZone } from "../../data/translation"; + +const calcZonedDate = ( + date: Date, + tz: string, + fn: (date: Date, options?: any) => Date, + options? +) => { + const inputZoned = utcToZonedTime(date, tz); + const fnZoned = fn(inputZoned, options); + return zonedTimeToUtc(fnZoned, tz); +}; + +export const calcDate = ( + date: Date, + fn: (date: Date, options?: any) => Date, + locale: FrontendLocaleData, + config: HassConfig, + options? +) => + locale.time_zone === TimeZone.server + ? calcZonedDate(date, config.time_zone, fn, options) + : fn(date, options); diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index cb6893a625..ffb48bc64e 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -1,3 +1,4 @@ +import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData, DateFormat } from "../../data/translation"; import "../../resources/intl-polyfill"; @@ -5,37 +6,44 @@ import "../../resources/intl-polyfill"; // Tuesday, August 10 export const formatDateWeekdayDay = ( dateObj: Date, - locale: FrontendLocaleData -) => formatDateWeekdayDayMem(locale).format(dateObj); + locale: FrontendLocaleData, + config: HassConfig +) => formatDateWeekdayDayMem(locale, config.time_zone).format(dateObj); const formatDateWeekdayDayMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { weekday: "long", month: "long", day: "numeric", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); // August 10, 2021 -export const formatDate = (dateObj: Date, locale: FrontendLocaleData) => - formatDateMem(locale).format(dateObj); +export const formatDate = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateMem(locale, config.time_zone).format(dateObj); const formatDateMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { year: "numeric", month: "long", day: "numeric", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); // 10/08/2021 export const formatDateNumeric = ( dateObj: Date, - locale: FrontendLocaleData + locale: FrontendLocaleData, + config: HassConfig ) => { - const formatter = formatDateNumericMem(locale); + const formatter = formatDateNumericMem(locale, config.time_zone); if ( locale.date_format === DateFormat.language || @@ -67,83 +75,120 @@ export const formatDateNumeric = ( return formats[locale.date_format]; }; -const formatDateNumericMem = memoizeOne((locale: FrontendLocaleData) => { - const localeString = - locale.date_format === DateFormat.system ? undefined : locale.language; +const formatDateNumericMem = memoizeOne( + (locale: FrontendLocaleData, serverTimeZone: string) => { + const localeString = + locale.date_format === DateFormat.system ? undefined : locale.language; + + if ( + locale.date_format === DateFormat.language || + locale.date_format === DateFormat.system + ) { + return new Intl.DateTimeFormat(localeString, { + year: "numeric", + month: "numeric", + day: "numeric", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, + }); + } - if ( - locale.date_format === DateFormat.language || - locale.date_format === DateFormat.system - ) { return new Intl.DateTimeFormat(localeString, { year: "numeric", month: "numeric", day: "numeric", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }); } - - return new Intl.DateTimeFormat(localeString, { - year: "numeric", - month: "numeric", - day: "numeric", - }); -}); +); // Aug 10 -export const formatDateShort = (dateObj: Date, locale: FrontendLocaleData) => - formatDateShortMem(locale).format(dateObj); +export const formatDateShort = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateShortMem(locale, config.time_zone).format(dateObj); const formatDateShortMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { day: "numeric", month: "short", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); // August 2021 export const formatDateMonthYear = ( dateObj: Date, - locale: FrontendLocaleData -) => formatDateMonthYearMem(locale).format(dateObj); + locale: FrontendLocaleData, + config: HassConfig +) => formatDateMonthYearMem(locale, config.time_zone).format(dateObj); const formatDateMonthYearMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { month: "long", year: "numeric", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); // August -export const formatDateMonth = (dateObj: Date, locale: FrontendLocaleData) => - formatDateMonthMem(locale).format(dateObj); +export const formatDateMonth = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateMonthMem(locale, config.time_zone).format(dateObj); const formatDateMonthMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { month: "long", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); // 2021 -export const formatDateYear = (dateObj: Date, locale: FrontendLocaleData) => - formatDateYearMem(locale).format(dateObj); +export const formatDateYear = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateYearMem(locale, config.time_zone).format(dateObj); const formatDateYearMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { year: "numeric", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); // Monday -export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) => - formatDateWeekdayMem(locale).format(dateObj); +export const formatDateWeekday = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateWeekdayMem(locale, config.time_zone).format(dateObj); const formatDateWeekdayMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat(locale.language, { weekday: "long", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, + }) +); + +// Mon +export const formatDateWeekdayShort = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateWeekdayShortMem(locale, config.time_zone).format(dateObj); + +const formatDateWeekdayShortMem = memoizeOne( + (locale: FrontendLocaleData, serverTimeZone: string) => + new Intl.DateTimeFormat(locale.language, { + weekday: "short", + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts index 7c38d1fd18..d9b7a3a862 100644 --- a/src/common/datetime/format_date_time.ts +++ b/src/common/datetime/format_date_time.ts @@ -1,16 +1,20 @@ +import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; import "../../resources/intl-polyfill"; -import { useAmPm } from "./use_am_pm"; import { formatDateNumeric } from "./format_date"; import { formatTime } from "./format_time"; +import { useAmPm } from "./use_am_pm"; // August 9, 2021, 8:23 AM -export const formatDateTime = (dateObj: Date, locale: FrontendLocaleData) => - formatDateTimeMem(locale).format(dateObj); +export const formatDateTime = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatDateTimeMem(locale, config.time_zone).format(dateObj); const formatDateTimeMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -22,6 +26,7 @@ const formatDateTimeMem = memoizeOne( hour: useAmPm(locale) ? "numeric" : "2-digit", minute: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); @@ -29,11 +34,12 @@ const formatDateTimeMem = memoizeOne( // Aug 9, 2021, 8:23 AM export const formatShortDateTimeWithYear = ( dateObj: Date, - locale: FrontendLocaleData -) => formatShortDateTimeWithYearMem(locale).format(dateObj); + locale: FrontendLocaleData, + config: HassConfig +) => formatShortDateTimeWithYearMem(locale, config.time_zone).format(dateObj); const formatShortDateTimeWithYearMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -45,6 +51,7 @@ const formatShortDateTimeWithYearMem = memoizeOne( hour: useAmPm(locale) ? "numeric" : "2-digit", minute: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); @@ -52,11 +59,12 @@ const formatShortDateTimeWithYearMem = memoizeOne( // Aug 9, 8:23 AM export const formatShortDateTime = ( dateObj: Date, - locale: FrontendLocaleData -) => formatShortDateTimeMem(locale).format(dateObj); + locale: FrontendLocaleData, + config: HassConfig +) => formatShortDateTimeMem(locale, config.time_zone).format(dateObj); const formatShortDateTimeMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -67,6 +75,7 @@ const formatShortDateTimeMem = memoizeOne( hour: useAmPm(locale) ? "numeric" : "2-digit", minute: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); @@ -74,11 +83,12 @@ const formatShortDateTimeMem = memoizeOne( // August 9, 2021, 8:23:15 AM export const formatDateTimeWithSeconds = ( dateObj: Date, - locale: FrontendLocaleData -) => formatDateTimeWithSecondsMem(locale).format(dateObj); + locale: FrontendLocaleData, + config: HassConfig +) => formatDateTimeWithSecondsMem(locale, config.time_zone).format(dateObj); const formatDateTimeWithSecondsMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -91,6 +101,7 @@ const formatDateTimeWithSecondsMem = memoizeOne( minute: "2-digit", second: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); @@ -98,5 +109,11 @@ const formatDateTimeWithSecondsMem = memoizeOne( // 9/8/2021, 8:23 AM export const formatDateTimeNumeric = ( dateObj: Date, - locale: FrontendLocaleData -) => `${formatDateNumeric(dateObj, locale)}, ${formatTime(dateObj, locale)}`; + locale: FrontendLocaleData, + config: HassConfig +) => + `${formatDateNumeric(dateObj, locale, config)}, ${formatTime( + dateObj, + locale, + config + )}`; diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index ec4459fe2c..41827b4f8d 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -1,14 +1,18 @@ +import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; import "../../resources/intl-polyfill"; import { useAmPm } from "./use_am_pm"; // 9:15 PM || 21:15 -export const formatTime = (dateObj: Date, locale: FrontendLocaleData) => - formatTimeMem(locale).format(dateObj); +export const formatTime = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatTimeMem(locale, config.time_zone).format(dateObj); const formatTimeMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -17,6 +21,7 @@ const formatTimeMem = memoizeOne( hour: "numeric", minute: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); @@ -24,11 +29,12 @@ const formatTimeMem = memoizeOne( // 9:15:24 PM || 21:15:24 export const formatTimeWithSeconds = ( dateObj: Date, - locale: FrontendLocaleData -) => formatTimeWithSecondsMem(locale).format(dateObj); + locale: FrontendLocaleData, + config: HassConfig +) => formatTimeWithSecondsMem(locale, config.time_zone).format(dateObj); const formatTimeWithSecondsMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -38,16 +44,20 @@ const formatTimeWithSecondsMem = memoizeOne( minute: "2-digit", second: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); // Tuesday 7:00 PM || Tuesday 19:00 -export const formatTimeWeekday = (dateObj: Date, locale: FrontendLocaleData) => - formatTimeWeekdayMem(locale).format(dateObj); +export const formatTimeWeekday = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatTimeWeekdayMem(locale, config.time_zone).format(dateObj); const formatTimeWeekdayMem = memoizeOne( - (locale: FrontendLocaleData) => + (locale: FrontendLocaleData, serverTimeZone: string) => new Intl.DateTimeFormat( locale.language === "en" && !useAmPm(locale) ? "en-u-hc-h23" @@ -57,20 +67,25 @@ const formatTimeWeekdayMem = memoizeOne( hour: useAmPm(locale) ? "numeric" : "2-digit", minute: "2-digit", hour12: useAmPm(locale), + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, } ) ); // 21:15 -export const formatTime24h = (dateObj: Date) => - formatTime24hMem().format(dateObj); +export const formatTime24h = ( + dateObj: Date, + locale: FrontendLocaleData, + config: HassConfig +) => formatTime24hMem(locale, config.time_zone).format(dateObj); const formatTime24hMem = memoizeOne( - () => + (locale: FrontendLocaleData, serverTimeZone: string) => // en-GB to fix Chrome 24:59 to 0:59 https://stackoverflow.com/a/60898146 new Intl.DateTimeFormat("en-GB", { hour: "numeric", minute: "2-digit", hour12: false, + timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, }) ); diff --git a/src/common/entity/compute_attribute_display.ts b/src/common/entity/compute_attribute_display.ts index 5fdfabe7a8..1d5d8af8a4 100644 --- a/src/common/entity/compute_attribute_display.ts +++ b/src/common/entity/compute_attribute_display.ts @@ -1,4 +1,4 @@ -import { HassEntity } from "home-assistant-js-websocket"; +import { HassConfig, HassEntity } from "home-assistant-js-websocket"; import { html, TemplateResult } from "lit"; import { until } from "lit/directives/until"; import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; @@ -20,6 +20,7 @@ export const computeAttributeValueDisplay = ( localize: LocalizeFunc, stateObj: HassEntity, locale: FrontendLocaleData, + config: HassConfig, entities: HomeAssistant["entities"], attribute: string, value?: any @@ -59,14 +60,14 @@ export const computeAttributeValueDisplay = ( if (isTimestamp(attributeValue)) { const date = new Date(attributeValue); if (checkValidDate(date)) { - return formatDateTimeWithSeconds(date, locale); + return formatDateTimeWithSeconds(date, locale, config); } } // Value was not a timestamp, so only do date formatting const date = new Date(attributeValue); if (checkValidDate(date)) { - return formatDate(date, locale); + return formatDate(date, locale, config); } } } @@ -92,6 +93,7 @@ export const computeAttributeValueDisplay = ( localize, stateObj, locale, + config, entities, attribute, item diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index a35b8cf780..34db8700d1 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -1,7 +1,7 @@ -import { HassEntity } from "home-assistant-js-websocket"; +import { HassConfig, HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; -import { FrontendLocaleData } from "../../data/translation"; +import { FrontendLocaleData, TimeZone } from "../../data/translation"; import { updateIsInstallingFromAttributes, UPDATE_SUPPORT_PROGRESS, @@ -28,12 +28,14 @@ export const computeStateDisplaySingleEntity = ( localize: LocalizeFunc, stateObj: HassEntity, locale: FrontendLocaleData, + config: HassConfig, entity: EntityRegistryDisplayEntry | undefined, state?: string ): string => computeStateDisplayFromEntityAttributes( localize, locale, + config, entity, stateObj.entity_id, stateObj.attributes, @@ -44,6 +46,7 @@ export const computeStateDisplay = ( localize: LocalizeFunc, stateObj: HassEntity, locale: FrontendLocaleData, + config: HassConfig, entities: HomeAssistant["entities"], state?: string ): string => { @@ -54,6 +57,7 @@ export const computeStateDisplay = ( return computeStateDisplayFromEntityAttributes( localize, locale, + config, entity, stateObj.entity_id, stateObj.attributes, @@ -64,6 +68,7 @@ export const computeStateDisplay = ( export const computeStateDisplayFromEntityAttributes = ( localize: LocalizeFunc, locale: FrontendLocaleData, + config: HassConfig, entity: EntityRegistryDisplayEntry | undefined, entityId: string, attributes: any, @@ -119,29 +124,40 @@ export const computeStateDisplayFromEntityAttributes = ( if (domain === "datetime") { const time = new Date(state); - return formatDateTime(time, locale); + return formatDateTime(time, locale, config); } if (["date", "input_datetime", "time"].includes(domain)) { // If trying to display an explicit state, need to parse the explicit state to `Date` then format. // Attributes aren't available, we have to use `state`. + + // These are timezone agnostic, so we should NOT use the system timezone here. try { const components = state.split(" "); if (components.length === 2) { // Date and time. - return formatDateTime(new Date(components.join("T")), locale); + return formatDateTime( + new Date(components.join("T")), + { ...locale, time_zone: TimeZone.local }, + config + ); } if (components.length === 1) { if (state.includes("-")) { // Date only. - return formatDate(new Date(`${state}T00:00`), locale); + return formatDate( + new Date(`${state}T00:00`), + { ...locale, time_zone: TimeZone.local }, + config + ); } if (state.includes(":")) { // Time only. const now = new Date(); return formatTime( new Date(`${now.toISOString().split("T")[0]}T${state}`), - locale + { ...locale, time_zone: TimeZone.local }, + config ); } } @@ -179,7 +195,7 @@ export const computeStateDisplayFromEntityAttributes = ( (domain === "sensor" && attributes.device_class === "timestamp") ) { try { - return formatDateTime(new Date(state), locale); + return formatDateTime(new Date(state), locale, config); } catch (_err) { return state; } diff --git a/src/common/translations/day_names.ts b/src/common/translations/day_names.ts index 34b18fdb4a..885cd84029 100644 --- a/src/common/translations/day_names.ts +++ b/src/common/translations/day_names.ts @@ -1,10 +1,12 @@ import { addDays, startOfWeek } from "date-fns"; +import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; import { formatDateWeekday } from "../datetime/format_date"; -export const dayNames = memoizeOne((locale: FrontendLocaleData): string[] => - Array.from({ length: 7 }, (_, d) => - formatDateWeekday(addDays(startOfWeek(new Date()), d), locale) - ) +export const dayNames = memoizeOne( + (locale: FrontendLocaleData, config: HassConfig): string[] => + Array.from({ length: 7 }, (_, d) => + formatDateWeekday(addDays(startOfWeek(new Date()), d), locale, config) + ) ); diff --git a/src/common/translations/month_names.ts b/src/common/translations/month_names.ts index 2e456b1855..4df4236d28 100644 --- a/src/common/translations/month_names.ts +++ b/src/common/translations/month_names.ts @@ -1,10 +1,12 @@ import { addMonths, startOfYear } from "date-fns"; +import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; import { formatDateMonth } from "../datetime/format_date"; -export const monthNames = memoizeOne((locale: FrontendLocaleData): string[] => - Array.from({ length: 12 }, (_, m) => - formatDateMonth(addMonths(startOfYear(new Date()), m), locale) - ) +export const monthNames = memoizeOne( + (locale: FrontendLocaleData, config: HassConfig): string[] => + Array.from({ length: 12 }, (_, m) => + formatDateMonth(addMonths(startOfYear(new Date()), m), locale, config) + ) ); diff --git a/src/components/chart/chart-date-adapter.ts b/src/components/chart/chart-date-adapter.ts index 462ee52dc1..f348823a29 100644 --- a/src/components/chart/chart-date-adapter.ts +++ b/src/components/chart/chart-date-adapter.ts @@ -80,33 +80,89 @@ _adapters._date.override({ format: function (time, fmt: keyof typeof FORMATS) { switch (fmt) { case "datetime": - return formatDateTime(new Date(time), this.options.locale); + return formatDateTime( + new Date(time), + this.options.locale, + this.options.config + ); case "datetimeseconds": - return formatDateTimeWithSeconds(new Date(time), this.options.locale); + return formatDateTimeWithSeconds( + new Date(time), + this.options.locale, + this.options.config + ); case "millisecond": - return formatTimeWithSeconds(new Date(time), this.options.locale); + return formatTimeWithSeconds( + new Date(time), + this.options.locale, + this.options.config + ); case "second": - return formatTimeWithSeconds(new Date(time), this.options.locale); + return formatTimeWithSeconds( + new Date(time), + this.options.locale, + this.options.config + ); case "minute": - return formatTime(new Date(time), this.options.locale); + return formatTime( + new Date(time), + this.options.locale, + this.options.config + ); case "hour": - return formatTime(new Date(time), this.options.locale); + return formatTime( + new Date(time), + this.options.locale, + this.options.config + ); case "weekday": - return formatDateWeekdayDay(new Date(time), this.options.locale); + return formatDateWeekdayDay( + new Date(time), + this.options.locale, + this.options.config + ); case "date": - return formatDate(new Date(time), this.options.locale); + return formatDate( + new Date(time), + this.options.locale, + this.options.config + ); case "day": - return formatDateShort(new Date(time), this.options.locale); + return formatDateShort( + new Date(time), + this.options.locale, + this.options.config + ); case "week": - return formatDate(new Date(time), this.options.locale); + return formatDate( + new Date(time), + this.options.locale, + this.options.config + ); case "month": - return formatDateMonth(new Date(time), this.options.locale); + return formatDateMonth( + new Date(time), + this.options.locale, + this.options.config + ); case "monthyear": - return formatDateMonthYear(new Date(time), this.options.locale); + return formatDateMonthYear( + new Date(time), + this.options.locale, + this.options.config + ); case "quarter": - return formatDate(new Date(time), this.options.locale); + return formatDate( + new Date(time), + this.options.locale, + this.options.config + ); case "year": - return formatDateYear(new Date(time), this.options.locale); + return formatDateYear( + new Date(time), + this.options.locale, + this.options.config + ); default: return ""; } diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index 3ce2235320..50f164c094 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -71,6 +71,7 @@ class StateHistoryChartLine extends LitElement { adapters: { date: { locale: this.hass.locale, + config: this.hass.config, }, }, suggestedMax: this.endTime, diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index 2d9b93b413..b1686aa2ab 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -98,6 +98,7 @@ export class StateHistoryChartTimeline extends LitElement { adapters: { date: { locale: this.hass.locale, + config: this.hass.config, }, }, suggestedMin: this.startTime, @@ -181,8 +182,16 @@ export class StateHistoryChartTimeline extends LitElement { return [ d.label || "", - formatDateTimeWithSeconds(d.start, this.hass.locale), - formatDateTimeWithSeconds(d.end, this.hass.locale), + formatDateTimeWithSeconds( + d.start, + this.hass.locale, + this.hass.config + ), + formatDateTimeWithSeconds( + d.end, + this.hass.locale, + this.hass.config + ), formattedDuration, ]; }, diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 014e658417..d9f30d9d67 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -146,6 +146,7 @@ class StatisticsChart extends LitElement { adapters: { date: { locale: this.hass.locale, + config: this.hass.config, }, }, ticks: { diff --git a/src/components/entity/ha-entity-state-picker.ts b/src/components/entity/ha-entity-state-picker.ts index 7b92f63aa2..688b38e6db 100644 --- a/src/components/entity/ha-entity-state-picker.ts +++ b/src/components/entity/ha-entity-state-picker.ts @@ -62,6 +62,7 @@ class HaEntityStatePicker extends LitElement { this.hass.localize, state, this.hass.locale, + this.hass.config, this.hass.entities, key ) @@ -69,6 +70,7 @@ class HaEntityStatePicker extends LitElement { this.hass.localize, state, this.hass.locale, + this.hass.config, this.hass.entities, this.attribute, key diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index b5d60d9bb1..519dbfc107 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -192,6 +192,7 @@ export class HaStateLabelBadge extends LitElement { this.hass!.localize, entityState, this.hass!.locale, + this.hass!.config, this.hass!.entities ); } diff --git a/src/components/ha-absolute-time.ts b/src/components/ha-absolute-time.ts index f782b32317..528c658514 100644 --- a/src/components/ha-absolute-time.ts +++ b/src/components/ha-absolute-time.ts @@ -64,7 +64,11 @@ class HaAbsoluteTime extends ReactiveElement { if (!this.datetime) { this.innerHTML = this.hass.localize("ui.components.absolute_time.never"); } else { - this.innerHTML = absoluteTime(new Date(this.datetime), this.hass.locale); + this.innerHTML = absoluteTime( + new Date(this.datetime), + this.hass.locale, + this.hass.config + ); } } } diff --git a/src/components/ha-attributes.ts b/src/components/ha-attributes.ts index c1092cb96f..dce1fd4c07 100644 --- a/src/components/ha-attributes.ts +++ b/src/components/ha-attributes.ts @@ -62,6 +62,7 @@ class HaAttributes extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, attribute )} diff --git a/src/components/ha-climate-state.ts b/src/components/ha-climate-state.ts index 7c8be97a6e..7a1617e4ee 100644 --- a/src/components/ha-climate-state.ts +++ b/src/components/ha-climate-state.ts @@ -28,6 +28,7 @@ class HaClimateState extends LitElement { this.hass.localize, this.stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "preset_mode" )}` @@ -136,6 +137,7 @@ class HaClimateState extends LitElement { this.hass.localize, this.stateObj, this.hass.locale, + this.hass.config, this.hass.entities ); @@ -144,6 +146,7 @@ class HaClimateState extends LitElement { this.hass.localize, this.stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "hvac_action" )} (${stateString})` diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index 2b1cc6fe22..d3b5a0607e 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -1,9 +1,11 @@ import { mdiCalendar } from "@mdi/js"; +import { HassConfig } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import { formatDateNumeric } from "../common/datetime/format_date"; import { firstWeekdayIndex } from "../common/datetime/first_weekday"; +import { formatDateNumeric } from "../common/datetime/format_date"; import { fireEvent } from "../common/dom/fire_event"; +import { TimeZone } from "../data/translation"; import { HomeAssistant } from "../types"; import "./ha-svg-icon"; import "./ha-textfield"; @@ -59,7 +61,11 @@ export class HaDateInput extends LitElement { .value=${this.value ? formatDateNumeric( new Date(`${this.value.split("T")[0]}T00:00:00`), - this.locale + { + ...this.locale, + time_zone: TimeZone.local, + }, + {} as HassConfig ) : ""} .required=${this.required} diff --git a/src/components/ha-date-range-picker.ts b/src/components/ha-date-range-picker.ts index 5c870215a7..557d139ae3 100644 --- a/src/components/ha-date-range-picker.ts +++ b/src/components/ha-date-range-picker.ts @@ -3,6 +3,13 @@ import "@material/mwc-list/mwc-list"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiCalendar } from "@mdi/js"; +import { + addDays, + endOfDay, + endOfWeek, + startOfDay, + startOfWeek, +} from "date-fns"; import { css, CSSResultGroup, @@ -12,10 +19,11 @@ import { TemplateResult, } from "lit"; import { customElement, property } from "lit/decorators"; -import { formatDateTime } from "../common/datetime/format_date_time"; -import { formatDate } from "../common/datetime/format_date"; -import { useAmPm } from "../common/datetime/use_am_pm"; +import { calcDate } from "../common/datetime/calc_date"; import { firstWeekdayIndex } from "../common/datetime/first_weekday"; +import { formatDate } from "../common/datetime/format_date"; +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"; @@ -34,7 +42,7 @@ export class HaDateRangePicker extends LitElement { @property() public endDate!: Date; - @property() public ranges?: DateRangePickerRanges; + @property() public ranges?: DateRangePickerRanges | false; @property() public autoApply = false; @@ -46,6 +54,70 @@ export class HaDateRangePicker extends LitElement { @property({ type: String }) private _rtlDirection = "ltr"; + protected willUpdate() { + if (!this.hasUpdated && this.ranges === undefined) { + const today = new Date(); + const weekStartsOn = firstWeekdayIndex(this.hass.locale); + const weekStart = calcDate( + today, + startOfWeek, + this.hass.locale, + this.hass.config, + { + weekStartsOn, + } + ); + const weekEnd = calcDate( + today, + endOfWeek, + this.hass.locale, + this.hass.config, + { + weekStartsOn, + } + ); + + this.ranges = { + [this.hass.localize("ui.components.date-range-picker.ranges.today")]: [ + calcDate(today, startOfDay, this.hass.locale, this.hass.config, { + weekStartsOn, + }), + calcDate(today, endOfDay, this.hass.locale, this.hass.config, { + weekStartsOn, + }), + ], + [this.hass.localize( + "ui.components.date-range-picker.ranges.yesterday" + )]: [ + calcDate( + addDays(today, -1), + startOfDay, + this.hass.locale, + this.hass.config, + { + weekStartsOn, + } + ), + calcDate( + addDays(today, -1), + endOfDay, + this.hass.locale, + this.hass.config, + { + weekStartsOn, + } + ), + ], + [this.hass.localize( + "ui.components.date-range-picker.ranges.this_week" + )]: [weekStart, weekEnd], + [this.hass.localize( + "ui.components.date-range-picker.ranges.last_week" + )]: [addDays(weekStart, -7), addDays(weekEnd, -7)], + }; + } + } + protected updated(changedProps: PropertyValues) { if (changedProps.has("hass")) { const oldHass = changedProps.get("hass") as HomeAssistant | undefined; @@ -65,15 +137,19 @@ export class HaDateRangePicker extends LitElement { twentyfour-hours=${this._hour24format} start-date=${this.startDate} end-date=${this.endDate} - ?ranges=${this.ranges !== undefined} + ?ranges=${this.ranges !== false} first-day=${firstWeekdayIndex(this.hass.locale)} >
${result ? html`Result: diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 7bd01cd431..2396c65879 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -335,7 +335,8 @@ class ActionRenderer { } at ${formatDateTimeWithSeconds( new Date(triggerStep.timestamp), - this.hass.locale + this.hass.locale, + this.hass.config )}`, mdiCircle ); @@ -632,7 +633,8 @@ export class HaAutomationTracer extends LitElement { const renderFinishedAt = () => formatDateTimeWithSeconds( new Date(this.trace!.timestamp.finish!), - this.hass.locale + this.hass.locale, + this.hass.config ); const renderRuntime = () => `(runtime: ${( diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index d989b8617f..ab4c0056ea 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -1,26 +1,27 @@ +import { HassConfig } from "home-assistant-js-websocket"; +import { ensureArray } from "../common/array/ensure-array"; import { formatDuration } from "../common/datetime/format_duration"; import { formatTime, formatTimeWithSeconds, } from "../common/datetime/format_time"; -import { FrontendLocaleData } from "./translation"; import secondsToDuration from "../common/datetime/seconds_to_duration"; -import { ensureArray } from "../common/array/ensure-array"; +import { + computeAttributeNameDisplay, + computeAttributeValueDisplay, +} from "../common/entity/compute_attribute_display"; +import { computeStateDisplay } from "../common/entity/compute_state_display"; import { computeStateName } from "../common/entity/compute_state_name"; import type { HomeAssistant } from "../types"; -import { Condition, Trigger, ForDict } from "./automation"; +import { Condition, ForDict, Trigger } from "./automation"; import { DeviceCondition, DeviceTrigger, localizeDeviceAutomationCondition, localizeDeviceAutomationTrigger, } from "./device_automation"; -import { - computeAttributeNameDisplay, - computeAttributeValueDisplay, -} from "../common/entity/compute_attribute_display"; -import { computeStateDisplay } from "../common/entity/compute_state_display"; import { EntityRegistryEntry } from "./entity_registry"; +import { FrontendLocaleData } from "./translation"; const describeDuration = (forTime: number | string | ForDict) => { let duration: string | null; @@ -34,7 +35,11 @@ const describeDuration = (forTime: number | string | ForDict) => { return duration; }; -const localizeTimeString = (time: string, locale: FrontendLocaleData) => { +const localizeTimeString = ( + time: string, + locale: FrontendLocaleData, + config: HassConfig +) => { const chunks = time.split(":"); if (chunks.length < 2 || chunks.length > 3) { return time; @@ -42,9 +47,9 @@ const localizeTimeString = (time: string, locale: FrontendLocaleData) => { try { const dt = new Date("1970-01-01T" + time); if (chunks.length === 2 || Number(chunks[2]) === 0) { - return formatTime(dt, locale); + return formatTime(dt, locale, config); } - return formatTimeWithSeconds(dt, locale); + return formatTimeWithSeconds(dt, locale, config); } catch { return time; } @@ -209,6 +214,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, trigger.attribute, state @@ -217,6 +223,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, state ) @@ -232,6 +239,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, trigger.attribute, trigger.from @@ -240,6 +248,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, trigger.from.toString() ).toString() @@ -263,6 +272,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, trigger.attribute, state @@ -271,6 +281,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, state ).toString() @@ -286,6 +297,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, trigger.attribute, trigger.to @@ -294,6 +306,7 @@ export const describeTrigger = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, trigger.to.toString() ).toString() @@ -353,7 +366,7 @@ export const describeTrigger = ( ? at : at.includes(".") ? `entity ${hass.states[at] ? computeStateName(hass.states[at]) : at}` - : localizeTimeString(at, hass.locale) + : localizeTimeString(at, hass.locale, hass.config) ); const last = result.splice(-1, 1)[0]; @@ -738,6 +751,7 @@ export const describeCondition = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, condition.attribute, state @@ -746,6 +760,7 @@ export const describeCondition = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, state ) @@ -758,6 +773,7 @@ export const describeCondition = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, condition.attribute, condition.state @@ -766,6 +782,7 @@ export const describeCondition = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities, condition.state.toString() ).toString() @@ -830,7 +847,7 @@ export const describeCondition = ( ? computeStateName(hass.states[condition.before]) : condition.before }` - : localizeTimeString(condition.before, hass.locale); + : localizeTimeString(condition.before, hass.locale, hass.config); const after = typeof condition.after !== "string" @@ -841,7 +858,7 @@ export const describeCondition = ( ? computeStateName(hass.states[condition.after]) : condition.after }` - : localizeTimeString(condition.after, hass.locale); + : localizeTimeString(condition.after, hass.locale, hass.config); let result = "Confirm the "; if (after || before) { diff --git a/src/data/context.ts b/src/data/context.ts index f62e666ebe..7fbff9fd06 100644 --- a/src/data/context.ts +++ b/src/data/context.ts @@ -1,4 +1,5 @@ import { createContext } from "@lit-labs/context"; +import { HassConfig } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; import { EntityRegistryEntry } from "./entity_registry"; @@ -11,7 +12,7 @@ export const areasContext = createContext("areas"); export const localizeContext = createContext("localize"); export const localeContext = createContext("locale"); -export const configContext = createContext("config"); +export const configContext = createContext("config"); export const themesContext = createContext("themes"); export const selectedThemeContext = createContext("selectedTheme"); diff --git a/src/data/energy.ts b/src/data/energy.ts index 151089d929..1e1fbd3c5e 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -4,12 +4,12 @@ import { addMilliseconds, addMonths, differenceInDays, - endOfToday, - endOfYesterday, - startOfToday, - startOfYesterday, + endOfDay, + startOfDay, } from "date-fns/esm"; import { Collection, getCollection } from "home-assistant-js-websocket"; +import { calcDate } from "../common/datetime/calc_date"; +import { formatTime24h } from "../common/datetime/format_time"; import { groupBy } from "../common/util/group-by"; import { HomeAssistant } from "../types"; import { ConfigEntry, getConfigEntries } from "./config_entries"; @@ -626,18 +626,40 @@ export const getEnergyDataCollection = ( collection._active = 0; collection.prefs = options.prefs; const now = new Date(); + const hour = formatTime24h(now, hass.locale, hass.config).split(":")[0]; // Set start to start of today if we have data for today, otherwise yesterday - collection.start = now.getHours() > 0 ? startOfToday() : startOfYesterday(); - collection.end = now.getHours() > 0 ? endOfToday() : endOfYesterday(); + collection.start = calcDate( + hour === "0" ? addDays(now, -1) : now, + startOfDay, + hass.locale, + hass.config + ); + collection.end = calcDate( + hour === "0" ? addDays(now, -1) : now, + endOfDay, + hass.locale, + hass.config + ); const scheduleUpdatePeriod = () => { collection._updatePeriodTimeout = window.setTimeout( () => { - collection.start = startOfToday(); - collection.end = endOfToday(); + collection.start = calcDate( + new Date(), + startOfDay, + hass.locale, + hass.config + ); + collection.end = calcDate( + new Date(), + endOfDay, + hass.locale, + hass.config + ); scheduleUpdatePeriod(); }, - addHours(endOfToday(), 1).getTime() - Date.now() // Switch to next day an hour after the day changed + addHours(calcDate(now, endOfDay, hass.locale, hass.config), 1).getTime() - + Date.now() // Switch to next day an hour after the day changed ); }; scheduleUpdatePeriod(); @@ -649,8 +671,10 @@ export const getEnergyDataCollection = ( collection.start = newStart; collection.end = newEnd; if ( - collection.start.getTime() === startOfToday().getTime() && - collection.end?.getTime() === endOfToday().getTime() && + collection.start.getTime() === + calcDate(new Date(), startOfDay, hass.locale, hass.config).getTime() && + collection.end?.getTime() === + calcDate(new Date(), endOfDay, hass.locale, hass.config).getTime() && !collection._updatePeriodTimeout ) { scheduleUpdatePeriod(); diff --git a/src/data/history.ts b/src/data/history.ts index dcc37a45a3..8202f44167 100644 --- a/src/data/history.ts +++ b/src/data/history.ts @@ -1,4 +1,5 @@ import { + HassConfig, HassEntities, HassEntity, HassEntityAttributeBase, @@ -269,7 +270,8 @@ const equalState = (obj1: LineChartState, obj2: LineChartState) => const processTimelineEntity = ( localize: LocalizeFunc, - language: FrontendLocaleData, + locale: FrontendLocaleData, + config: HassConfig, entities: HomeAssistant["entities"], entityId: string, states: EntityHistoryState[], @@ -290,7 +292,8 @@ const processTimelineEntity = ( data.push({ state_localize: computeStateDisplayFromEntityAttributes( localize, - language, + locale, + config, entities[entityId], entityId, { @@ -441,6 +444,7 @@ export const computeHistory = ( processTimelineEntity( localize, hass.locale, + hass.config, hass.entities, entityId, stateInfo, diff --git a/src/data/logbook.ts b/src/data/logbook.ts index c191ed8535..a30b9cde02 100644 --- a/src/data/logbook.ts +++ b/src/data/logbook.ts @@ -439,6 +439,7 @@ export const localizeStateMessage = ( localize, stateObj, hass.locale, + hass.config, hass.entities, state ) diff --git a/src/data/timer.ts b/src/data/timer.ts index a06a865bf3..5401175d41 100644 --- a/src/data/timer.ts +++ b/src/data/timer.ts @@ -94,6 +94,7 @@ export const computeDisplayTimer = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities ); } @@ -105,6 +106,7 @@ export const computeDisplayTimer = ( hass.localize, stateObj, hass.locale, + hass.config, hass.entities )})`; } diff --git a/src/data/translation.ts b/src/data/translation.ts index fb6fdaeeb9..b64e557b6b 100644 --- a/src/data/translation.ts +++ b/src/data/translation.ts @@ -17,6 +17,11 @@ export enum TimeFormat { twenty_four = "24", } +export enum TimeZone { + local = "local", + server = "server", +} + export enum DateFormat { language = "language", system = "system", @@ -42,6 +47,7 @@ export interface FrontendLocaleData { time_format: TimeFormat; date_format: DateFormat; first_weekday: FirstWeekday; + time_zone: TimeZone; } declare global { diff --git a/src/dialogs/more-info/components/fan/ha-more-info-fan-speed.ts b/src/dialogs/more-info/components/fan/ha-more-info-fan-speed.ts index d40e88816e..72333bd2a2 100644 --- a/src/dialogs/more-info/components/fan/ha-more-info-fan-speed.ts +++ b/src/dialogs/more-info/components/fan/ha-more-info-fan-speed.ts @@ -72,6 +72,7 @@ export class HaMoreInfoFanSpeed extends LitElement { this.hass.localize, this.stateObj, this.hass.locale, + this.hass.config, this.hass.entities, speed ); diff --git a/src/dialogs/more-info/components/ha-more-info-state-header.ts b/src/dialogs/more-info/components/ha-more-info-state-header.ts index 1e7430cb23..150058e89b 100644 --- a/src/dialogs/more-info/components/ha-more-info-state-header.ts +++ b/src/dialogs/more-info/components/ha-more-info-state-header.ts @@ -39,6 +39,7 @@ export class HaMoreInfoStateHeader extends LitElement { this.hass!.localize, stateObj, this.hass!.locale, + this.hass!.config, this.hass!.entities ); diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts index 52a4b0aa14..9294e06855 100644 --- a/src/dialogs/more-info/controls/more-info-climate.ts +++ b/src/dialogs/more-info/controls/more-info-climate.ts @@ -203,6 +203,7 @@ class MoreInfoClimate extends LitElement { hass.localize, stateObj, hass.locale, + this.hass.config, hass.entities, mode )} @@ -236,6 +237,7 @@ class MoreInfoClimate extends LitElement { hass.localize, stateObj, hass.locale, + hass.config, hass.entities, "preset_mode", mode @@ -270,6 +272,7 @@ class MoreInfoClimate extends LitElement { hass.localize, stateObj, hass.locale, + this.hass.config, hass.entities, "fan_mode", mode @@ -304,6 +307,7 @@ class MoreInfoClimate extends LitElement { hass.localize, stateObj, hass.locale, + this.hass.config, hass.entities, "swing_mode", mode diff --git a/src/dialogs/more-info/controls/more-info-cover.ts b/src/dialogs/more-info/controls/more-info-cover.ts index b4fe8114ba..7fd9d9a8ac 100644 --- a/src/dialogs/more-info/controls/more-info-cover.ts +++ b/src/dialogs/more-info/controls/more-info-cover.ts @@ -83,6 +83,7 @@ class MoreInfoCover extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, forcedState ); diff --git a/src/dialogs/more-info/controls/more-info-fan.ts b/src/dialogs/more-info/controls/more-info-fan.ts index af32733d77..588397c5ce 100644 --- a/src/dialogs/more-info/controls/more-info-fan.ts +++ b/src/dialogs/more-info/controls/more-info-fan.ts @@ -119,6 +119,7 @@ class MoreInfoFan extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, forcedState ); @@ -281,6 +282,7 @@ class MoreInfoFan extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, "preset_mode", this._presetMode @@ -307,6 +309,7 @@ class MoreInfoFan extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, "preset_mode", mode diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index 1e6d270d9d..527c82461f 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -83,6 +83,7 @@ class MoreInfoHumidifier extends LitElement { hass.localize, stateObj, hass.locale, + this.hass.config, hass.entities, "mode", mode diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index f40b41977b..45597b228d 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -240,6 +240,7 @@ class MoreInfoLight extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, "effect", this._effect @@ -261,6 +262,7 @@ class MoreInfoLight extends LitElement { this.hass.localize, this.stateObj!, this.hass.locale, + this.hass.config, this.hass.entities, "effect", effect diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index c6b4d34840..b02fc75e9d 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -163,6 +163,7 @@ class MoreInfoMediaPlayer extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "source", source @@ -196,6 +197,7 @@ class MoreInfoMediaPlayer extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "sound_mode", mode diff --git a/src/dialogs/more-info/controls/more-info-remote.ts b/src/dialogs/more-info/controls/more-info-remote.ts index 4d486fb5d4..9080897c13 100644 --- a/src/dialogs/more-info/controls/more-info-remote.ts +++ b/src/dialogs/more-info/controls/more-info-remote.ts @@ -44,6 +44,7 @@ class MoreInfoRemote extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "activity", activity diff --git a/src/dialogs/more-info/controls/more-info-sun.ts b/src/dialogs/more-info/controls/more-info-sun.ts index 537fb95f78..28f5727e43 100644 --- a/src/dialogs/more-info/controls/more-info-sun.ts +++ b/src/dialogs/more-info/controls/more-info-sun.ts @@ -44,7 +44,8 @@ class MoreInfoSun extends LitElement {
${formatTime( item === "ris" ? risingDate : settingDate, - this.hass.locale + this.hass.locale, + this.hass.config )}
diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 652a3eb785..dfa940e31f 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -119,6 +119,7 @@ class MoreInfoVacuum extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "status" ) || @@ -126,6 +127,7 @@ class MoreInfoVacuum extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )} @@ -201,6 +203,7 @@ class MoreInfoVacuum extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "fan_speed", mode @@ -218,6 +221,7 @@ class MoreInfoVacuum extends LitElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "fan_speed" )} diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index 0b4a80ad93..628886339d 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -164,7 +164,8 @@ class MoreInfoWeather extends LitElement {
${formatTimeWeekday( new Date(item.datetime), - this.hass.locale + this.hass.locale, + this.hass.config )}
` @@ -172,7 +173,8 @@ class MoreInfoWeather extends LitElement {
${formatDateWeekdayDay( new Date(item.datetime), - this.hass.locale + this.hass.locale, + this.hass.config )}
`} diff --git a/src/dialogs/notifications/configurator-notification-item.ts b/src/dialogs/notifications/configurator-notification-item.ts index 47c7be0750..96174e02a1 100644 --- a/src/dialogs/notifications/configurator-notification-item.ts +++ b/src/dialogs/notifications/configurator-notification-item.ts @@ -38,6 +38,7 @@ export class HuiConfiguratorNotificationItem extends LitElement { this.hass.localize, this.notification, this.hass.locale, + this.hass.config, this.hass.entities )} diff --git a/src/dialogs/notifications/persistent-notification-item.ts b/src/dialogs/notifications/persistent-notification-item.ts index 310a6b93b9..40ce7e6b2f 100644 --- a/src/dialogs/notifications/persistent-notification-item.ts +++ b/src/dialogs/notifications/persistent-notification-item.ts @@ -82,7 +82,7 @@ export class HuiPersistentNotificationItem extends LitElement { } const d = new Date(notification.created_at!); - return formatDateTime(d, hass.locale); + return formatDateTime(d, hass.locale, hass.config); } } diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 6116fbc015..3da61ed4fe 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -11,6 +11,7 @@ import { NumberFormat, DateFormat, TimeFormat, + TimeZone, } from "../data/translation"; import { translationMetadata } from "../resources/translations-metadata"; import { HomeAssistant } from "../types"; @@ -230,6 +231,7 @@ export const provideHass = ( number_format: NumberFormat.language, time_format: TimeFormat.language, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.language, }, resources: null as any, diff --git a/src/panels/calendar/dialog-calendar-event-detail.ts b/src/panels/calendar/dialog-calendar-event-detail.ts index aba011143a..6172e79b4c 100644 --- a/src/panels/calendar/dialog-calendar-event-detail.ts +++ b/src/panels/calendar/dialog-calendar-event-detail.ts @@ -154,23 +154,28 @@ class DialogCalendarEventDetail extends LitElement { if (isSameDay(start, end)) { if (isDate(this._data.dtstart)) { // Single date string only - return formatDate(start, this.hass.locale); + return formatDate(start, this.hass.locale, this.hass.config); } // Single day with a start/end time range - return `${formatDate(start, this.hass.locale)} ${formatTime( + return `${formatDate( start, - this.hass.locale - )} - ${formatTime(end, this.hass.locale)}`; + this.hass.locale, + this.hass.config + )} ${formatTime( + start, + this.hass.locale, + this.hass.config + )} - ${formatTime(end, this.hass.locale, this.hass.config)}`; } // An event across multiple dates, optionally with a time range return `${ isDate(this._data.dtstart) - ? formatDate(start, this.hass.locale) - : formatDateTime(start, this.hass.locale) + ? formatDate(start, this.hass.locale, this.hass.config) + : formatDateTime(start, this.hass.locale, this.hass.config) } - ${ isDate(this._data.dtend) - ? formatDate(end, this.hass.locale) - : formatDateTime(end, this.hass.locale) + ? formatDate(end, this.hass.locale, this.hass.config) + : formatDateTime(end, this.hass.locale, this.hass.config) }`; } diff --git a/src/panels/calendar/recurrence.ts b/src/panels/calendar/recurrence.ts index ddc1d45729..1a502c9678 100644 --- a/src/panels/calendar/recurrence.ts +++ b/src/panels/calendar/recurrence.ts @@ -247,8 +247,8 @@ export function renderRRuleAsText(hass: HomeAssistant, value: string) { return ""; }, { - dayNames: dayNames(hass.locale), - monthNames: monthNames(hass.locale), + dayNames: dayNames(hass.locale, hass.config), + monthNames: monthNames(hass.locale, hass.config), tokens: {}, }, // Format the date @@ -263,9 +263,9 @@ export function renderRRuleAsText(hass: HomeAssistant, value: string) { // need to convert it back to something Date can work with. The already localized // months names are a must in the RRule.Language structure (an empty string[] would // mean we get undefined months input in this method here). - date.setMonth(monthNames(hass.locale).indexOf(month)); + date.setMonth(monthNames(hass.locale, hass.config).indexOf(month)); date.setDate(day); - return formatDate(date, hass.locale); + return formatDate(date, hass.locale, hass.config); } ) ); diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index cb89fd8af5..cdf33e2b50 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -128,7 +128,11 @@ class HaAutomationPicker extends LitElement { ${this.hass.localize("ui.card.automation.last_triggered")}: ${automation.attributes.last_triggered ? dayDifference > 3 - ? formatShortDateTime(date, this.hass.locale) + ? formatShortDateTime( + date, + this.hass.locale, + this.hass.config + ) : relativeTime(date, this.hass.locale) : this.hass.localize("ui.components.relative_time.never")} @@ -149,7 +153,11 @@ class HaAutomationPicker extends LitElement { return html` ${last_triggered ? dayDifference > 3 - ? formatShortDateTime(date, this.hass.locale) + ? formatShortDateTime( + date, + this.hass.locale, + this.hass.config + ) : relativeTime(date, this.hass.locale) : this.hass.localize("ui.components.relative_time.never")} `; diff --git a/src/panels/config/automation/ha-automation-trace.ts b/src/panels/config/automation/ha-automation-trace.ts index bfebbadb0b..fbf52c6974 100644 --- a/src/panels/config/automation/ha-automation-trace.ts +++ b/src/panels/config/automation/ha-automation-trace.ts @@ -192,7 +192,8 @@ export class HaAutomationTrace extends LitElement { html`` )} diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts index 2be0ccffbd..2f0f3e791d 100644 --- a/src/panels/config/cloud/account/cloud-account.ts +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -79,7 +79,8 @@ export class CloudAccount extends SubscribeMixin(LitElement) { new Date( this._subscription.plan_renewal_date * 1000 ), - this.hass.locale + this.hass.locale, + this.hass.config ) : "" ) diff --git a/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts b/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts index d999a162af..0e170e539b 100644 --- a/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts +++ b/src/panels/config/cloud/dialog-cloud-certificate/dialog-cloud-certificate.ts @@ -49,7 +49,8 @@ class DialogCloudCertificate extends LitElement { )} ${formatDateTime( new Date(certificateInfo.expire_date), - this.hass!.locale + this.hass!.locale, + this.hass!.config )}
(${this.hass!.localize( "ui.panel.config.cloud.dialog_certificate.will_be_auto_renewed" diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts index 90bfb086c0..2d3b5acd1f 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts @@ -55,7 +55,8 @@ class MQTTMessages extends LitElement { ${this.direction} ${formatTimeWithSeconds( new Date(message.time), - this.hass.locale + this.hass.locale, + this.hass.config )} ${this._renderSingleMessage(message)} diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index ee910d622e..a6498ab9bb 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -170,6 +170,7 @@ class HaConfigHardware extends SubscribeMixin(LitElement) { adapters: { date: { locale: this.hass.locale, + config: this.hass.config, }, }, gridLines: { diff --git a/src/panels/config/helpers/forms/ha-schedule-form.ts b/src/panels/config/helpers/forms/ha-schedule-form.ts index 1568452504..e3116d5b21 100644 --- a/src/panels/config/helpers/forms/ha-schedule-form.ts +++ b/src/panels/config/helpers/forms/ha-schedule-form.ts @@ -286,9 +286,9 @@ class HaScheduleForm extends LitElement { const value = [...this[`_${day}`]]; const newValue = { ...this._item }; - const endFormatted = formatTime24h(end); + const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config); value.push({ - from: formatTime24h(start), + from: formatTime24h(start, this.hass.locale, this.hass.config), to: !isSameDay(start, end) || endFormatted === "0:00" ? "24:00" @@ -313,7 +313,7 @@ class HaScheduleForm extends LitElement { const value = this[`_${day}`][parseInt(index)]; const newValue = { ...this._item }; - const endFormatted = formatTime24h(end); + const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config); newValue[day][index] = { from: value.from, to: @@ -338,9 +338,9 @@ class HaScheduleForm extends LitElement { const newDay = weekdays[start.getDay()]; const newValue = { ...this._item }; - const endFormatted = formatTime24h(end); + const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config); const event = { - from: formatTime24h(start), + from: formatTime24h(start, this.hass.locale, this.hass.config), to: !isSameDay(start, end) || endFormatted === "0:00" ? "24:00" diff --git a/src/panels/config/integrations/dialog-add-integration.ts b/src/panels/config/integrations/dialog-add-integration.ts index 1798614a68..02935336c0 100644 --- a/src/panels/config/integrations/dialog-add-integration.ts +++ b/src/panels/config/integrations/dialog-add-integration.ts @@ -1,6 +1,7 @@ import "@material/mwc-button"; import "@material/mwc-list/mwc-list"; import Fuse from "fuse.js"; +import { HassConfig } from "home-assistant-js-websocket"; import { css, html, @@ -158,7 +159,7 @@ class AddIntegrationDialog extends LitElement { ( i: Brands, h: Integrations, - components: HomeAssistant["config"]["components"], + components: HassConfig["components"], localize: LocalizeFunc, filter?: string ): IntegrationListItem[] => { diff --git a/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts b/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts index ebc0e1a123..bb04eb4f78 100644 --- a/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts +++ b/src/panels/config/integrations/integration-panels/mqtt/mqtt-subscribe-card.ts @@ -105,7 +105,7 @@ class MqttSubscribeCard extends LitElement { "topic", msg.message.topic, "time", - formatTime(msg.time, this.hass!.locale) + formatTime(msg.time, this.hass!.locale, this.hass!.config) )}
${msg.payload}
diff --git a/src/panels/config/logs/dialog-system-log-detail.ts b/src/panels/config/logs/dialog-system-log-detail.ts index 49891035ef..4856efdb98 100644 --- a/src/panels/config/logs/dialog-system-log-detail.ts +++ b/src/panels/config/logs/dialog-system-log-detail.ts @@ -145,12 +145,20 @@ class DialogSystemLogDetail extends LitElement { ${item.count > 0 ? html` First occurred: - ${formatSystemLogTime(item.first_occurred, this.hass!.locale)} + ${formatSystemLogTime( + item.first_occurred, + this.hass!.locale, + this.hass!.config + )} (${item.count} occurrences)
` : ""} Last logged: - ${formatSystemLogTime(item.timestamp, this.hass!.locale)} + ${formatSystemLogTime( + item.timestamp, + this.hass!.locale, + this.hass!.config + )}

${item.message.length > 1 ? html` diff --git a/src/panels/config/logs/system-log-card.ts b/src/panels/config/logs/system-log-card.ts index beec07abaf..e05da8a75d 100644 --- a/src/panels/config/logs/system-log-card.ts +++ b/src/panels/config/logs/system-log-card.ts @@ -38,14 +38,22 @@ export class SystemLogCard extends LitElement { } private _timestamp(item: LoggedError): string { - return formatSystemLogTime(item.timestamp, this.hass!.locale); + return formatSystemLogTime( + item.timestamp, + this.hass.locale, + this.hass.config + ); } private _multipleMessages(item: LoggedError): string { return this.hass.localize( "ui.panel.config.logs.multiple_messages", "time", - formatSystemLogTime(item.first_occurred, this.hass!.locale), + formatSystemLogTime( + item.first_occurred, + this.hass.locale, + this.hass.config + ), "counter", item.count ); diff --git a/src/panels/config/logs/util.ts b/src/panels/config/logs/util.ts index 3eb92fcd96..4cf0d7f704 100644 --- a/src/panels/config/logs/util.ts +++ b/src/panels/config/logs/util.ts @@ -1,13 +1,18 @@ +import { HassConfig } from "home-assistant-js-websocket"; import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time"; import { formatTimeWithSeconds } from "../../../common/datetime/format_time"; import { FrontendLocaleData } from "../../../data/translation"; -export const formatSystemLogTime = (date, locale: FrontendLocaleData) => { +export const formatSystemLogTime = ( + date, + locale: FrontendLocaleData, + config: HassConfig +) => { const today = new Date().setHours(0, 0, 0, 0); const dateTime = new Date(date * 1000); const dateTimeDay = new Date(date * 1000).setHours(0, 0, 0, 0); return dateTimeDay < today - ? formatDateTimeWithSeconds(dateTime, locale) - : formatTimeWithSeconds(dateTime, locale); + ? formatDateTimeWithSeconds(dateTime, locale, config) + : formatTimeWithSeconds(dateTime, locale, config); }; diff --git a/src/panels/config/repairs/dialog-repairs-issue.ts b/src/panels/config/repairs/dialog-repairs-issue.ts index 3aae980d29..4e628ccbd0 100644 --- a/src/panels/config/repairs/dialog-repairs-issue.ts +++ b/src/panels/config/repairs/dialog-repairs-issue.ts @@ -100,7 +100,8 @@ class DialogRepairsIssue extends LitElement { ${this._issue.created ? formatDateNumeric( new Date(this._issue.created), - this.hass.locale + this.hass.locale, + this.hass.config ) : ""}
diff --git a/src/panels/config/repairs/dialog-system-information.ts b/src/panels/config/repairs/dialog-system-information.ts index ce2bfefd5a..bc3b1e0181 100644 --- a/src/panels/config/repairs/dialog-system-information.ts +++ b/src/panels/config/repairs/dialog-system-information.ts @@ -349,7 +349,11 @@ class DialogSystemInformation extends LitElement { `} `; } else if (info.type === "date") { - value = formatDateTime(new Date(info.value), this.hass.locale); + value = formatDateTime( + new Date(info.value), + this.hass.locale, + this.hass.config + ); } } else { value = domainInfo.info[key]; @@ -425,7 +429,11 @@ class DialogSystemInformation extends LitElement { } else if (info.type === "failed") { value = `failed to load: ${info.error}`; } else if (info.type === "date") { - value = formatDateTime(new Date(info.value), this.hass.locale); + value = formatDateTime( + new Date(info.value), + this.hass.locale, + this.hass.config + ); } } else { value = domainInfo.info[key]; diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 11762b419b..df9f6be040 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -118,7 +118,11 @@ class HaSceneDashboard extends LitElement { return html` ${last_activated && !isUnavailableState(last_activated) ? dayDifference > 3 - ? formatShortDateTime(date, this.hass.locale) + ? formatShortDateTime( + date, + this.hass.locale, + this.hass.config + ) : relativeTime(date, this.hass.locale) : this.hass.localize("ui.components.relative_time.never")} `; diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index f5d80a60be..61ac52dc3f 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -118,7 +118,11 @@ class HaScriptPicker extends LitElement { ${this.hass.localize("ui.card.automation.last_triggered")}: ${script.attributes.last_triggered ? dayDifference > 3 - ? formatShortDateTime(date, this.hass.locale) + ? formatShortDateTime( + date, + this.hass.locale, + this.hass.config + ) : relativeTime(date, this.hass.locale) : this.hass.localize("ui.components.relative_time.never")} @@ -139,7 +143,7 @@ class HaScriptPicker extends LitElement { return html` ${last_triggered ? dayDifference > 3 - ? formatShortDateTime(date, this.hass.locale) + ? formatShortDateTime(date, this.hass.locale, this.hass.config) : relativeTime(date, this.hass.locale) : this.hass.localize("ui.components.relative_time.never")} `; diff --git a/src/panels/config/script/ha-script-trace.ts b/src/panels/config/script/ha-script-trace.ts index 54bb1542d2..dd410b745a 100644 --- a/src/panels/config/script/ha-script-trace.ts +++ b/src/panels/config/script/ha-script-trace.ts @@ -191,7 +191,8 @@ export class HaScriptTrace extends LitElement { html`` )} diff --git a/src/panels/config/voice-assistants/debug/assist-pipeline-debug.ts b/src/panels/config/voice-assistants/debug/assist-pipeline-debug.ts index 638fad5c3c..e75f3a230f 100644 --- a/src/panels/config/voice-assistants/debug/assist-pipeline-debug.ts +++ b/src/panels/config/voice-assistants/debug/assist-pipeline-debug.ts @@ -64,7 +64,8 @@ export class AssistPipelineDebug extends LitElement { html`` )} diff --git a/src/panels/developer-tools/event/event-subscribe-card.ts b/src/panels/developer-tools/event/event-subscribe-card.ts index 0b0ce881b6..d1d7afc0aa 100644 --- a/src/panels/developer-tools/event/event-subscribe-card.ts +++ b/src/panels/developer-tools/event/event-subscribe-card.ts @@ -80,7 +80,8 @@ class EventSubscribeCard extends LitElement { )} ${formatTime( new Date(event.event.time_fired), - this.hass!.locale + this.hass!.locale, + this.hass!.config )}: ${growth} ${unit} - ${formatDateTime(new Date(stat.start), this.hass.locale)} + ${formatDateTime( + new Date(stat.start), + this.hass.locale, + this.hass.config + )} @@ -213,7 +217,8 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { ${formatDateTime( new Date(this._chosenStat!.start), - this.hass.locale + this.hass.locale, + this.hass.config )} @@ -223,7 +228,8 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { ${formatDateTime( new Date(this._chosenStat!.end), - this.hass.locale + this.hass.locale, + this.hass.config )} diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 08b8f0d90c..b88861d44c 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -1,14 +1,5 @@ import { mdiFilterRemove, mdiRefresh } from "@mdi/js"; -import { - addDays, - differenceInHours, - endOfToday, - endOfWeek, - endOfYesterday, - startOfToday, - startOfWeek, - startOfYesterday, -} from "date-fns/esm"; +import { differenceInHours } from "date-fns/esm"; import { HassServiceTarget, UnsubscribeFunc, @@ -16,7 +7,6 @@ import { import { css, html, LitElement, PropertyValues } from "lit"; import { property, query, state } from "lit/decorators"; import { ensureArray } from "../../common/array/ensure-array"; -import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; import { LocalStorage } from "../../common/decorators/local-storage"; import { navigate } from "../../common/navigate"; import { constructUrlCurrentPath } from "../../common/url/construct-url"; @@ -31,10 +21,11 @@ import "../../components/chart/state-history-charts"; import type { StateHistoryCharts } from "../../components/chart/state-history-charts"; import "../../components/ha-circular-progress"; import "../../components/ha-date-range-picker"; -import type { DateRangePickerRanges } from "../../components/ha-date-range-picker"; import "../../components/ha-icon-button"; +import "../../components/ha-icon-button-arrow-prev"; import "../../components/ha-menu-button"; import "../../components/ha-target-picker"; +import "../../components/ha-top-app-bar-fixed"; import { AreaDeviceLookup, AreaEntityLookup, @@ -55,8 +46,6 @@ import { import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { haStyle } from "../../resources/styles"; import { HomeAssistant } from "../../types"; -import "../../components/ha-top-app-bar-fixed"; -import "../../components/ha-icon-button-arrow-prev"; class HaPanelHistory extends SubscribeMixin(LitElement) { @property({ attribute: false }) hass!: HomeAssistant; @@ -76,8 +65,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { @state() private _stateHistory?: HistoryResult; - @state() private _ranges?: DateRangePickerRanges; - @state() private _deviceEntityLookup?: DeviceEntityLookup; @state() private _areaEntityLookup?: AreaEntityLookup; @@ -178,7 +165,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { ?disabled=${this._isLoading} .startDate=${this._startDate} .endDate=${this._endDate} - .ranges=${this._ranges} @change=${this._dateRangeChanged} > - ${formatDate(new Date(item.when * 1000), this.hass.locale)} + ${formatDate( + new Date(item.when * 1000), + this.hass.locale, + this.hass.config + )} ` : nothing} @@ -229,7 +233,8 @@ class HaLogbookRenderer extends LitElement { ${formatTimeWithSeconds( new Date(item.when * 1000), - this.hass.locale + this.hass.locale, + this.hass.config )} - diff --git a/src/panels/logbook/ha-panel-logbook.ts b/src/panels/logbook/ha-panel-logbook.ts index aba47b77d4..9a93813793 100644 --- a/src/panels/logbook/ha-panel-logbook.ts +++ b/src/panels/logbook/ha-panel-logbook.ts @@ -1,16 +1,6 @@ import { mdiRefresh } from "@mdi/js"; -import { - addDays, - endOfToday, - endOfWeek, - endOfYesterday, - startOfToday, - startOfWeek, - startOfYesterday, -} from "date-fns/esm"; import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; import { navigate } from "../../common/navigate"; import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { @@ -20,7 +10,6 @@ import { } from "../../common/url/search-params"; import "../../components/entity/ha-entity-picker"; import "../../components/ha-date-range-picker"; -import type { DateRangePickerRanges } from "../../components/ha-date-range-picker"; import "../../components/ha-icon-button"; import "../../components/ha-icon-button-arrow-prev"; import "../../components/ha-menu-button"; @@ -40,8 +29,6 @@ export class HaPanelLogbook extends LitElement { @state() _entityIds?: string[]; - @state() private _ranges?: DateRangePickerRanges; - @state() private _showBack?: boolean; @@ -91,7 +78,6 @@ export class HaPanelLogbook extends LitElement { .hass=${this.hass} .startDate=${this._time.range[0]} .endDate=${this._time.range[1]} - .ranges=${this._ranges} @change=${this._dateRangeChanged} > @@ -123,24 +109,6 @@ export class HaPanelLogbook extends LitElement { return; } - const today = new Date(); - const weekStartsOn = firstWeekdayIndex(this.hass.locale); - const weekStart = startOfWeek(today, { weekStartsOn }); - const weekEnd = endOfWeek(today, { weekStartsOn }); - - this._ranges = { - [this.hass.localize("ui.components.date-range-picker.ranges.today")]: [ - startOfToday(), - endOfToday(), - ], - [this.hass.localize("ui.components.date-range-picker.ranges.yesterday")]: - [startOfYesterday(), endOfYesterday()], - [this.hass.localize("ui.components.date-range-picker.ranges.this_week")]: - [weekStart, weekEnd], - [this.hass.localize("ui.components.date-range-picker.ranges.last_week")]: - [addDays(weekStart, -7), addDays(weekEnd, -7)], - }; - this._applyURLParams(); } diff --git a/src/panels/lovelace/cards/energy/hui-energy-compare-card.ts b/src/panels/lovelace/cards/energy/hui-energy-compare-card.ts index 552680e2e9..4c545668fe 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-compare-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-compare-card.ts @@ -60,18 +60,27 @@ export class HuiEnergyCompareCard ${this.hass.localize("ui.panel.energy.compare.info", { start: html`${formatDate(this._start!, this.hass.locale)}${dayDifference > 0 + >${formatDate( + this._start!, + this.hass.locale, + this.hass.config + )}${dayDifference > 0 ? ` - - ${formatDate(this._end || endOfDay(new Date()), this.hass.locale)}` + ${formatDate( + this._end || endOfDay(new Date()), + this.hass.locale, + this.hass.config + )}` : ""}`, end: html`${formatDate( this._startCompare, - this.hass.locale + this.hass.locale, + this.hass.config )}${dayDifference > 0 ? ` - - ${formatDate(this._endCompare, this.hass.locale)}` + ${formatDate(this._endCompare, this.hass.locale, this.hass.config)}` : ""}`, })} diff --git a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts index 8fa32d49b1..432dada500 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-gas-graph-card.ts @@ -12,7 +12,7 @@ import { isToday, startOfToday, } from "date-fns"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -112,6 +112,7 @@ export class HuiEnergyGasGraphCard this._start, this._end, this.hass.locale, + this.hass.config, this._unit, this._compareStart, this._compareEnd @@ -137,6 +138,7 @@ export class HuiEnergyGasGraphCard start: Date, end: Date, locale: FrontendLocaleData, + config: HassConfig, unit?: string, compareStart?: Date, compareEnd?: Date @@ -167,7 +169,8 @@ export class HuiEnergyGasGraphCard suggestedMax: end.getTime(), adapters: { date: { - locale: locale, + locale, + config, }, }, ticks: { @@ -221,10 +224,11 @@ export class HuiEnergyGasGraphCard } const date = new Date(datasets[0].parsed.x); return `${ - compare ? `${formatDateShort(date, locale)}: ` : "" - }${formatTime(date, locale)} – ${formatTime( + compare ? `${formatDateShort(date, locale, config)}: ` : "" + }${formatTime(date, locale, config)} – ${formatTime( addHours(date, 1), - locale + locale, + config )}`; }, label: (context) => diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts index 6c693b4b46..49290b5647 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-graph-card.ts @@ -12,7 +12,7 @@ import { isToday, startOfToday, } from "date-fns/esm"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -111,6 +111,7 @@ export class HuiEnergySolarGraphCard this._start, this._end, this.hass.locale, + this.hass.config, this._compareStart, this._compareEnd )} @@ -135,6 +136,7 @@ export class HuiEnergySolarGraphCard start: Date, end: Date, locale: FrontendLocaleData, + config: HassConfig, compareStart?: Date, compareEnd?: Date ): ChartOptions => { @@ -164,7 +166,8 @@ export class HuiEnergySolarGraphCard suggestedMax: end.getTime(), adapters: { date: { - locale: locale, + locale, + config, }, }, ticks: { @@ -217,10 +220,11 @@ export class HuiEnergySolarGraphCard } const date = new Date(datasets[0].parsed.x); return `${ - compare ? `${formatDateShort(date, locale)}: ` : "" - }${formatTime(date, locale)} – ${formatTime( + compare ? `${formatDateShort(date, locale, config)}: ` : "" + }${formatTime(date, locale, config)} – ${formatTime( addHours(date, 1), - locale + locale, + config )}`; }, label: (context) => diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index 693f5deb83..5b97bd290d 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -12,7 +12,7 @@ import { isToday, startOfToday, } from "date-fns/esm"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -105,6 +105,7 @@ export class HuiEnergyUsageGraphCard this._start, this._end, this.hass.locale, + this.hass.config, this._compareStart, this._compareEnd )} @@ -129,6 +130,7 @@ export class HuiEnergyUsageGraphCard start: Date, end: Date, locale: FrontendLocaleData, + config: HassConfig, compareStart?: Date, compareEnd?: Date ): ChartOptions => { @@ -158,7 +160,8 @@ export class HuiEnergyUsageGraphCard suggestedMax: end.getTime(), adapters: { date: { - locale: locale, + locale, + config, }, }, ticks: { @@ -213,10 +216,11 @@ export class HuiEnergyUsageGraphCard } const date = new Date(datasets[0].parsed.x); return `${ - compare ? `${formatDateShort(date, locale)}: ` : "" - }${formatTime(date, locale)} – ${formatTime( + compare ? `${formatDateShort(date, locale, config)}: ` : "" + }${formatTime(date, locale, config)} – ${formatTime( addHours(date, 1), - locale + locale, + config )}`; }, label: (context) => diff --git a/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts index 709921d45c..84f21089c2 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-water-graph-card.ts @@ -12,7 +12,7 @@ import { isToday, startOfToday, } from "date-fns"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -112,6 +112,7 @@ export class HuiEnergyWaterGraphCard this._start, this._end, this.hass.locale, + this.hass.config, this._unit, this._compareStart, this._compareEnd @@ -137,6 +138,7 @@ export class HuiEnergyWaterGraphCard start: Date, end: Date, locale: FrontendLocaleData, + config: HassConfig, unit?: string, compareStart?: Date, compareEnd?: Date @@ -167,7 +169,8 @@ export class HuiEnergyWaterGraphCard suggestedMax: end.getTime(), adapters: { date: { - locale: locale, + locale, + config, }, }, ticks: { @@ -221,10 +224,11 @@ export class HuiEnergyWaterGraphCard } const date = new Date(datasets[0].parsed.x); return `${ - compare ? `${formatDateShort(date, locale)}: ` : "" - }${formatTime(date, locale)} – ${formatTime( + compare ? `${formatDateShort(date, locale, config)}: ` : "" + }${formatTime(date, locale, config)} – ${formatTime( addHours(date, 1), - locale + locale, + config )}`; }, label: (context) => diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index 6f00fc3dfa..1e9167e5a4 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -2,7 +2,11 @@ import { consume } from "@lit-labs/context"; import "@material/mwc-ripple"; import type { Ripple } from "@material/mwc-ripple"; import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers"; -import { HassEntities, HassEntity } from "home-assistant-js-websocket"; +import { + HassConfig, + HassEntities, + HassEntity, +} from "home-assistant-js-websocket"; import { CSSResultGroup, LitElement, @@ -27,6 +31,7 @@ import { iconColorCSS } from "../../../common/style/icon_color_css"; import "../../../components/ha-card"; import { HVAC_ACTION_TO_MODE } from "../../../data/climate"; import { + configContext, entitiesContext, localeContext, localizeContext, @@ -103,6 +108,10 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { @consume({ context: localeContext, subscribe: true }) _locale!: FrontendLocaleData; + @state() + @consume({ context: configContext, subscribe: true }) + _hassConfig!: HassConfig; + @consume({ context: entitiesContext, subscribe: true }) @transform({ transformer: function (this: HuiButtonCard, value) { @@ -223,6 +232,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { this._localize, stateObj, this._locale, + this._hassConfig, this._entity )} ` diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index 4d948cd0d7..548dbd80ec 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -163,6 +163,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, this._config.attribute! ) @@ -180,6 +181,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )}${showUnit diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index f22ac84267..1e42db66c7 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -337,6 +337,7 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { this.hass!.localize, stateObj, this.hass!.locale, + this.hass!.config, this.hass!.entities )} diff --git a/src/panels/lovelace/cards/hui-humidifier-card.ts b/src/panels/lovelace/cards/hui-humidifier-card.ts index a99331e67e..9e24121a29 100644 --- a/src/panels/lovelace/cards/hui-humidifier-card.ts +++ b/src/panels/lovelace/cards/hui-humidifier-card.ts @@ -140,6 +140,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "mode" )} diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index e41aef6afe..9c2041c7ba 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -161,6 +161,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )} diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index d84694d048..ec9dfa6138 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -374,11 +374,19 @@ class HuiMapCard extends LitElement implements LovelaceCard { if ((config.hours_to_show! ?? DEFAULT_HOURS_TO_SHOW) > 144) { // if showing > 6 days in the history trail, show the full // date and time - p.tooltip = formatDateTime(t, this.hass.locale); + p.tooltip = formatDateTime(t, this.hass.locale, this.hass.config); } else if (isToday(t)) { - p.tooltip = formatTimeWithSeconds(t, this.hass.locale); + p.tooltip = formatTimeWithSeconds( + t, + this.hass.locale, + this.hass.config + ); } else { - p.tooltip = formatTimeWeekday(t, this.hass.locale); + p.tooltip = formatTimeWeekday( + t, + this.hass.locale, + this.hass.config + ); } points.push(p); } diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index 665aad7f32..1d6c51fcd7 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -123,6 +123,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { this.hass!.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities ); diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index 298a745026..b307d0f1d0 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -257,6 +257,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { this.hass!.localize, stateObj, this.hass!.locale, + this.hass!.config, this.hass!.entities )}`} > @@ -280,6 +281,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { this.hass!.localize, stateObj, this.hass!.locale, + this.hass!.config, this.hass!.entities )} diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index b186cf8e59..0438374c7a 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -235,6 +235,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "hvac_action" ) @@ -242,6 +243,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities ) } @@ -254,6 +256,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "preset_mode" )} diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 5f2b43b83a..0afb105c2f 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -228,6 +228,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard { this.hass!.localize, stateObj, this.hass!.locale, + this.hass!.config, this.hass!.entities ); diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 066d14fa02..85f29fc0a4 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -39,6 +39,7 @@ import { loadPolyfillIfNeeded } from "../../../resources/resize-observer.polyfil import { createEntityNotFoundWarning } from "../components/hui-warning"; import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { WeatherForecastCardConfig } from "./types"; +import { formatDateWeekdayShort } from "../../../common/datetime/format_date"; const DAY_IN_MILLISECONDS = 86400000; @@ -222,6 +223,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )} @@ -319,13 +321,15 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { ? html` ${formatTime( new Date(item.datetime), - this.hass!.locale + this.hass!.locale, + this.hass!.config )} ` : html` - ${new Date(item.datetime).toLocaleDateString( - this.hass!.language, - { weekday: "short" } + ${formatDateWeekdayShort( + new Date(item.datetime), + this.hass!.locale, + this.hass!.config )} `} diff --git a/src/panels/lovelace/components/hui-energy-period-selector.ts b/src/panels/lovelace/components/hui-energy-period-selector.ts index c4054e7e7c..9cd6666cd3 100644 --- a/src/panels/lovelace/components/hui-energy-period-selector.ts +++ b/src/panels/lovelace/components/hui-energy-period-selector.ts @@ -21,6 +21,7 @@ import { import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { calcDate } from "../../../common/datetime/calc_date"; import { firstWeekdayIndex } from "../../../common/datetime/first_weekday"; import { formatDate, @@ -105,17 +106,27 @@ export class HuiEnergyPeriodSelector extends SubscribeMixin(LitElement) {
${this._period === "day" - ? formatDate(this._startDate, this.hass.locale) + ? formatDate(this._startDate, this.hass.locale, this.hass.config) : this._period === "month" - ? formatDateMonthYear(this._startDate, this.hass.locale) + ? formatDateMonthYear( + this._startDate, + this.hass.locale, + this.hass.config + ) : this._period === "year" - ? formatDateYear(this._startDate, this.hass.locale) + ? formatDateYear( + this._startDate, + this.hass.locale, + this.hass.config + ) : `${formatDateShort( this._startDate, - this.hass.locale + this.hass.locale, + this.hass.config )} – ${formatDateShort( this._endDate || new Date(), - this.hass.locale + this.hass.locale, + this.hass.config )}`} string; + [key: string]: ( + ts: Date, + lang: FrontendLocaleData, + config: HassConfig + ) => string; } = { date: formatDate, datetime: formatDateTime, @@ -63,7 +68,9 @@ class HuiTimestampDisplay extends LitElement { return html` ${this._relative} `; } if (format in FORMATS) { - return html` ${FORMATS[format](this.ts, this.hass.locale)} `; + return html` + ${FORMATS[format](this.ts, this.hass.locale, this.hass.config)} + `; } return html`${this.hass.localize( "ui.panel.lovelace.components.timestamp-display.invalid_format" diff --git a/src/panels/lovelace/elements/hui-state-label-element.ts b/src/panels/lovelace/elements/hui-state-label-element.ts index 4d52fb81f4..d7f3ce9872 100644 --- a/src/panels/lovelace/elements/hui-state-label-element.ts +++ b/src/panels/lovelace/elements/hui-state-label-element.ts @@ -87,6 +87,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities ) : stateObj.attributes[this._config.attribute]}${this._config.suffix} diff --git a/src/panels/lovelace/entity-rows/hui-group-entity-row.ts b/src/panels/lovelace/entity-rows/hui-group-entity-row.ts index 9dad7f8e6d..146943251e 100644 --- a/src/panels/lovelace/entity-rows/hui-group-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-group-entity-row.ts @@ -70,6 +70,7 @@ class HuiGroupEntityRow extends LitElement implements LovelaceRow { this.hass!.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )}
diff --git a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts index f2666cfe99..4aa7738253 100644 --- a/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-humidifier-entity-row.ts @@ -54,6 +54,7 @@ class HuiHumidifierEntityRow extends LitElement implements LovelaceRow { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, "mode" )})` diff --git a/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts index ab81ff7eb1..6488d33843 100644 --- a/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts @@ -100,6 +100,7 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, stateObj.state )} diff --git a/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts b/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts index 2dc020a493..f2f481ad46 100644 --- a/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts @@ -193,6 +193,7 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )} > diff --git a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts index 0046379e3f..bd55652798 100644 --- a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts @@ -104,6 +104,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, stateObj.state )} diff --git a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts index c263519425..2d7d3c51e1 100644 --- a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts @@ -82,6 +82,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { this.hass!.localize, stateObj, this.hass!.locale, + this.hass!.config, this.hass!.entities, option )} diff --git a/src/panels/lovelace/entity-rows/hui-sensor-entity-row.ts b/src/panels/lovelace/entity-rows/hui-sensor-entity-row.ts index ce291b44cc..0d536ca9f9 100644 --- a/src/panels/lovelace/entity-rows/hui-sensor-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-sensor-entity-row.ts @@ -83,6 +83,7 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow { this.hass!.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )}
diff --git a/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts b/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts index 98e1b44a43..23b4cb8720 100644 --- a/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-simple-entity-row.ts @@ -53,6 +53,7 @@ class HuiSimpleEntityRow extends LitElement implements LovelaceRow { this.hass!.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities )} diff --git a/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts b/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts index 7ea096e9ad..611fd2bdd5 100644 --- a/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-toggle-entity-row.ts @@ -65,6 +65,7 @@ class HuiToggleEntityRow extends LitElement implements LovelaceRow { this.hass!.localize, stateObj, this.hass!.locale, + this.hass.config, this.hass!.entities )} diff --git a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts index 088869be37..f8badf7b10 100644 --- a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts @@ -148,6 +148,7 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities ) : html` diff --git a/src/panels/lovelace/special-rows/hui-attribute-row.ts b/src/panels/lovelace/special-rows/hui-attribute-row.ts index b0e95de02f..b8db4ebf56 100644 --- a/src/panels/lovelace/special-rows/hui-attribute-row.ts +++ b/src/panels/lovelace/special-rows/hui-attribute-row.ts @@ -75,6 +75,7 @@ class HuiAttributeRow extends LitElement implements LovelaceRow { this.hass.localize, stateObj, this.hass.locale, + this.hass.config, this.hass.entities, this._config.attribute, attribute diff --git a/src/panels/lovelace/tile-features/hui-fan-speed-tile-feature.ts b/src/panels/lovelace/tile-features/hui-fan-speed-tile-feature.ts index fb0f23bf29..da69431ad0 100644 --- a/src/panels/lovelace/tile-features/hui-fan-speed-tile-feature.ts +++ b/src/panels/lovelace/tile-features/hui-fan-speed-tile-feature.ts @@ -59,6 +59,7 @@ class HuiFanSpeedTileFeature extends LitElement implements LovelaceTileFeature { this.hass!.localize, this.stateObj!, this.hass!.locale, + this.hass!.config, this.hass!.entities, speed ); diff --git a/src/panels/profile/ha-panel-profile.ts b/src/panels/profile/ha-panel-profile.ts index 628c44ef38..47bccf93ea 100644 --- a/src/panels/profile/ha-panel-profile.ts +++ b/src/panels/profile/ha-panel-profile.ts @@ -28,6 +28,7 @@ import "./ha-pick-number-format-row"; import "./ha-pick-theme-row"; import "./ha-pick-time-format-row"; import "./ha-pick-date-format-row"; +import "./ha-pick-time-zone-row"; import "./ha-push-notifications-row"; import "./ha-refresh-tokens-card"; import "./ha-set-suspend-row"; @@ -101,6 +102,10 @@ class HaPanelProfile extends LitElement { .narrow=${this.narrow} .hass=${this.hass} > + ${Object.values(DateFormat).map((format) => { - const formattedDate = formatDateNumeric(date, { - ...this.hass.locale, - date_format: format, - }); + const formattedDate = formatDateNumeric( + date, + { + ...this.hass.locale, + date_format: format, + }, + this.hass.config + ); const value = this.hass.localize( `ui.panel.profile.date_format.formats.${format}` ); diff --git a/src/panels/profile/ha-pick-time-format-row.ts b/src/panels/profile/ha-pick-time-format-row.ts index e890e10474..b9d24d597f 100644 --- a/src/panels/profile/ha-pick-time-format-row.ts +++ b/src/panels/profile/ha-pick-time-format-row.ts @@ -35,10 +35,14 @@ class TimeFormatRow extends LitElement { naturalMenuWidth > ${Object.values(TimeFormat).map((format) => { - const formattedTime = formatTime(date, { - ...this.hass.locale, - time_format: format, - }); + const formattedTime = formatTime( + date, + { + ...this.hass.locale, + time_format: format, + }, + this.hass.config + ); const value = this.hass.localize( `ui.panel.profile.time_format.formats.${format}` ); diff --git a/src/panels/profile/ha-pick-time-zone-row.ts b/src/panels/profile/ha-pick-time-zone-row.ts new file mode 100644 index 0000000000..c1bd070406 --- /dev/null +++ b/src/panels/profile/ha-pick-time-zone-row.ts @@ -0,0 +1,76 @@ +import "@material/mwc-list/mwc-list-item"; +import { html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { formatDateTimeNumeric } from "../../common/datetime/format_date_time"; +import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-card"; +import "../../components/ha-select"; +import "../../components/ha-settings-row"; +import { TimeZone } from "../../data/translation"; +import { HomeAssistant } from "../../types"; + +@customElement("ha-pick-time-zone-row") +class TimeZoneRow extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public narrow!: boolean; + + protected render(): TemplateResult { + const date = new Date(); + return html` + + + ${this.hass.localize("ui.panel.profile.time_zone.header")} + + + ${this.hass.localize("ui.panel.profile.time_zone.description")} + + + ${Object.values(TimeZone).map((format) => { + const formattedTime = formatDateTimeNumeric( + date, + { + ...this.hass.locale, + time_zone: format, + }, + this.hass.config + ); + return html` + ${this.hass.localize( + `ui.panel.profile.time_zone.options.${format}`, + { + timezone: (format === "server" + ? this.hass.config.time_zone + : Intl.DateTimeFormat?.().resolvedOptions?.().timeZone || + "" + ).replace("_", " "), + } + )} + ${formattedTime} + `; + })} + + + `; + } + + private async _handleFormatSelection(ev) { + fireEvent(this, "hass-time-zone-select", ev.target.value); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-pick-time-zone-row": TimeZoneRow; + } +} diff --git a/src/state-summary/state-card-display.ts b/src/state-summary/state-card-display.ts index 9214ecc1fb..a168d05711 100755 --- a/src/state-summary/state-card-display.ts +++ b/src/state-summary/state-card-display.ts @@ -42,7 +42,7 @@ export class StateCardDisplay extends LitElement { this.stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP && !isUnavailableState(this.stateObj.state) - ? html` diff --git a/src/state-summary/state-card-input_number.ts b/src/state-summary/state-card-input_number.ts index d455a5a862..6cd218fb03 100644 --- a/src/state-summary/state-card-input_number.ts +++ b/src/state-summary/state-card-input_number.ts @@ -72,6 +72,7 @@ class StateCardInputNumber extends LitElement { this.hass.localize, this.stateObj, this.hass.locale, + this.hass.config, this.hass.entities, this.stateObj.state )} diff --git a/src/state-summary/state-card-select.ts b/src/state-summary/state-card-select.ts index 78c749c48e..354136c67e 100644 --- a/src/state-summary/state-card-select.ts +++ b/src/state-summary/state-card-select.ts @@ -36,6 +36,7 @@ class StateCardSelect extends LitElement { this.hass.localize, this.stateObj, this.hass.locale, + this.hass.config, this.hass.entities, option )} diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index b02e26d837..c2c81cc87f 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -23,6 +23,7 @@ import { NumberFormat, DateFormat, TimeFormat, + TimeZone, } from "../data/translation"; import { subscribePanels } from "../data/ws-panels"; import { translationMetadata } from "../resources/translations-metadata"; @@ -63,6 +64,7 @@ export const connectionMixin = >( number_format: NumberFormat.language, time_format: TimeFormat.language, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.language, }, resources: null as any, diff --git a/src/state/translations-mixin.ts b/src/state/translations-mixin.ts index 149c478925..66b86a140a 100644 --- a/src/state/translations-mixin.ts +++ b/src/state/translations-mixin.ts @@ -15,6 +15,7 @@ import { TimeFormat, DateFormat, TranslationCategory, + TimeZone, } from "../data/translation"; import { translationMetadata } from "../resources/translations-metadata"; import { Constructor, HomeAssistant } from "../types"; @@ -41,6 +42,9 @@ declare global { "hass-date-format-select": { date_format: DateFormat; }; + "hass-time-zone-select": { + time_zone: TimeZone; + }; "hass-first-weekday-select": { first_weekday: FirstWeekday; }; @@ -89,6 +93,9 @@ export default >(superClass: T) => this.addEventListener("hass-date-format-select", (e) => { this._selectDateFormat((e as CustomEvent).detail, true); }); + this.addEventListener("hass-time-zone-select", (e) => { + this._selectTimeZone((e as CustomEvent).detail, true); + }); this.addEventListener("hass-first-weekday-select", (e) => { this._selectFirstWeekday((e as CustomEvent).detail, true); }); @@ -137,6 +144,13 @@ export default >(superClass: T) => // We just got date_format from backend, no need to save back this._selectDateFormat(locale.date_format, false); } + if ( + locale?.time_zone && + this.hass!.locale.time_zone !== locale.time_zone + ) { + // We just got time_zone from backend, no need to save back + this._selectTimeZone(locale.time_zone, false); + } if ( locale?.first_weekday && this.hass!.locale.first_weekday !== locale.first_weekday @@ -203,6 +217,15 @@ export default >(superClass: T) => } } + private _selectTimeZone(time_zone: TimeZone, saveToBackend: boolean) { + this._updateHass({ + locale: { ...this.hass!.locale, time_zone }, + }); + if (saveToBackend) { + saveTranslationPreferences(this.hass!, this.hass!.locale); + } + } + private _selectFirstWeekday( first_weekday: FirstWeekday, saveToBackend: boolean diff --git a/src/translations/en.json b/src/translations/en.json index a9f9608190..f03055696a 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4928,6 +4928,15 @@ "24": "24 hours" } }, + "time_zone": { + "header": "Time Zone", + "dropdown_label": "Time zone", + "description": "Choose the time zone to use for displaying times.", + "options": { + "local": "Use your local time zone ({timezone})", + "server": "Use server time zone ({timezone})" + } + }, "date_format": { "header": "Date Format", "dropdown_label": "Date format", diff --git a/src/util/common-translation.ts b/src/util/common-translation.ts index be14097dd8..041d1fa537 100644 --- a/src/util/common-translation.ts +++ b/src/util/common-translation.ts @@ -77,6 +77,7 @@ export async function getUserLocale( const number_format = result?.number_format; const time_format = result?.time_format; const date_format = result?.date_format; + const time_zone = result?.time_zone; const first_weekday = result?.first_weekday; if (language) { const availableLanguage = findAvailableLanguage(language); @@ -85,7 +86,8 @@ export async function getUserLocale( language: availableLanguage, number_format, time_format, - date_format: date_format, + date_format, + time_zone, first_weekday, }; } @@ -93,7 +95,8 @@ export async function getUserLocale( return { number_format, time_format, - date_format: date_format, + date_format, + time_zone, first_weekday, }; } diff --git a/test/common/datetime/format_date.ts b/test/common/datetime/format_date.ts index 9d6841ea7d..d399b58e25 100644 --- a/test/common/datetime/format_date.ts +++ b/test/common/datetime/format_date.ts @@ -6,20 +6,27 @@ import { TimeFormat, FirstWeekday, DateFormat, + TimeZone, } from "../../../src/data/translation"; +import { demoConfig } from "../../../src/fake_data/demo_config"; describe("formatDate", () => { const dateObj = new Date(2017, 10, 18, 11, 12, 13, 1400); it("Formats English dates", () => { assert.strictEqual( - formatDate(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.language, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatDate( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.language, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "November 18, 2017" ); }); diff --git a/test/common/datetime/format_date_time.ts b/test/common/datetime/format_date_time.ts index 5632d60615..2f7988e5d3 100644 --- a/test/common/datetime/format_date_time.ts +++ b/test/common/datetime/format_date_time.ts @@ -9,30 +9,42 @@ import { TimeFormat, FirstWeekday, DateFormat, + TimeZone, } from "../../../src/data/translation"; +import { demoConfig } from "../../../src/fake_data/demo_config"; describe("formatDateTime", () => { const dateObj = new Date(2017, 10, 18, 23, 12, 13, 400); it("Formats English date times", () => { assert.strictEqual( - formatDateTime(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.am_pm, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatDateTime( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.am_pm, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "November 18, 2017 at 11:12 PM" ); assert.strictEqual( - formatDateTime(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.twenty_four, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatDateTime( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.twenty_four, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "November 18, 2017 at 23:12" ); }); @@ -43,23 +55,34 @@ describe("formatDateTimeWithSeconds", () => { it("Formats English date times with seconds", () => { assert.strictEqual( - formatDateTimeWithSeconds(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.am_pm, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatDateTimeWithSeconds( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.am_pm, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "November 18, 2017 at 11:12:13 PM" ); assert.strictEqual( - formatDateTimeWithSeconds(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.twenty_four, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatDateTimeWithSeconds( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.twenty_four, + date_format: DateFormat.language, + time_zone: TimeZone.local, + + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "November 18, 2017 at 23:12:13" ); }); diff --git a/test/common/datetime/format_time.ts b/test/common/datetime/format_time.ts index 3c7e7899c7..3dbbcfd0a8 100644 --- a/test/common/datetime/format_time.ts +++ b/test/common/datetime/format_time.ts @@ -10,30 +10,42 @@ import { TimeFormat, FirstWeekday, DateFormat, + TimeZone, } from "../../../src/data/translation"; +import { demoConfig } from "../../../src/fake_data/demo_config"; describe("formatTime", () => { const dateObj = new Date(2017, 10, 18, 23, 12, 13, 1400); it("Formats English times", () => { assert.strictEqual( - formatTime(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.am_pm, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatTime( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.am_pm, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "11:12 PM" ); assert.strictEqual( - formatTime(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.twenty_four, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatTime( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.twenty_four, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "23:12" ); }); @@ -44,23 +56,33 @@ describe("formatTimeWithSeconds", () => { it("Formats English times with seconds", () => { assert.strictEqual( - formatTimeWithSeconds(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.am_pm, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatTimeWithSeconds( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.am_pm, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "11:12:13 PM" ); assert.strictEqual( - formatTimeWithSeconds(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.twenty_four, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatTimeWithSeconds( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.twenty_four, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "23:12:13" ); }); @@ -71,23 +93,33 @@ describe("formatTimeWeekday", () => { it("Formats English times", () => { assert.strictEqual( - formatTimeWeekday(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.am_pm, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatTimeWeekday( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.am_pm, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "Saturday 11:12 PM" ); assert.strictEqual( - formatTimeWeekday(dateObj, { - language: "en", - number_format: NumberFormat.language, - time_format: TimeFormat.twenty_four, - date_format: DateFormat.language, - first_weekday: FirstWeekday.language, - }), + formatTimeWeekday( + dateObj, + { + language: "en", + number_format: NumberFormat.language, + time_format: TimeFormat.twenty_four, + date_format: DateFormat.language, + time_zone: TimeZone.local, + first_weekday: FirstWeekday.language, + }, + demoConfig + ), "Saturday 23:12" ); }); diff --git a/test/common/datetime/relative_time.ts b/test/common/datetime/relative_time.ts index ebdf39185b..a99ed08765 100644 --- a/test/common/datetime/relative_time.ts +++ b/test/common/datetime/relative_time.ts @@ -6,6 +6,7 @@ import { TimeFormat, FirstWeekday, DateFormat, + TimeZone, } from "../../../src/data/translation"; describe("relativeTime", () => { @@ -14,6 +15,7 @@ describe("relativeTime", () => { number_format: NumberFormat.language, time_format: TimeFormat.language, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.language, }; @@ -22,6 +24,7 @@ describe("relativeTime", () => { number_format: NumberFormat.language, time_format: TimeFormat.language, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.monday, }; diff --git a/test/common/entity/compute_state_display.ts b/test/common/entity/compute_state_display.ts index f09df55c5d..2cfc130a56 100644 --- a/test/common/entity/compute_state_display.ts +++ b/test/common/entity/compute_state_display.ts @@ -7,7 +7,9 @@ import { TimeFormat, FirstWeekday, DateFormat, + TimeZone, } from "../../../src/data/translation"; +import { demoConfig } from "../../../src/fake_data/demo_config"; let localeData: FrontendLocaleData; @@ -22,6 +24,7 @@ describe("computeStateDisplay", () => { number_format: NumberFormat.comma_decimal, time_format: TimeFormat.am_pm, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.language, }; }); @@ -33,7 +36,7 @@ describe("computeStateDisplay", () => { attributes: {}, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "component.binary_sensor.entity_component._.state.off" ); }); @@ -47,7 +50,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "component.binary_sensor.state.moisture.off" ); }); @@ -67,7 +70,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(altLocalize, stateObj, localeData, {}), + computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}), "component.binary_sensor.state.invalid_device_class.off" ); }); @@ -81,7 +84,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "123 m" ); }); @@ -95,7 +98,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "1,234.5 m" ); }); @@ -109,7 +112,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "1,234.5" ); }); @@ -129,7 +132,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(altLocalize, stateObj, localeData, {}), + computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}), "state.default.unknown" ); }); @@ -149,7 +152,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(altLocalize, stateObj, localeData, {}), + computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}), "state.default.unavailable" ); }); @@ -169,7 +172,7 @@ describe("computeStateDisplay", () => { attributes: {}, }; assert.strictEqual( - computeStateDisplay(altLocalize, stateObj, localeData, {}), + computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}), "component.sensor.entity_component._.state.custom_state" ); }); @@ -191,14 +194,14 @@ describe("computeStateDisplay", () => { }; it("Uses am/pm time format", () => { assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "November 18, 2017 at 11:12 PM" ); }); it("Uses 24h time format", () => { localeData.time_format = TimeFormat.twenty_four; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "November 18, 2017 at 23:12" ); }); @@ -220,7 +223,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "November 18, 2017" ); }); @@ -243,14 +246,14 @@ describe("computeStateDisplay", () => { it("Uses am/pm time format", () => { localeData.time_format = TimeFormat.am_pm; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "11:12 PM" ); }); it("Uses 24h time format", () => { localeData.time_format = TimeFormat.twenty_four; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}), + computeStateDisplay(localize, stateObj, localeData, demoConfig, {}), "23:12" ); }); @@ -277,6 +280,7 @@ describe("computeStateDisplay", () => { localize, stateObj, localeData, + demoConfig, {}, "2021-07-04 15:40:03" ), @@ -290,6 +294,7 @@ describe("computeStateDisplay", () => { localize, stateObj, localeData, + demoConfig, {}, "2021-07-04 15:40:03" ), @@ -314,7 +319,14 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}, "2021-07-04"), + computeStateDisplay( + localize, + stateObj, + localeData, + demoConfig, + {}, + "2021-07-04" + ), "July 4, 2021" ); }); @@ -337,14 +349,28 @@ describe("computeStateDisplay", () => { it("Uses am/pm time format", () => { localeData.time_format = TimeFormat.am_pm; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}, "17:05:07"), + computeStateDisplay( + localize, + stateObj, + localeData, + demoConfig, + {}, + "17:05:07" + ), "5:05 PM" ); }); it("Uses 24h time format", () => { localeData.time_format = TimeFormat.twenty_four; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, {}, "17:05:07"), + computeStateDisplay( + localize, + stateObj, + localeData, + demoConfig, + {}, + "17:05:07" + ), "17:05" ); }); @@ -363,7 +389,7 @@ describe("computeStateDisplay", () => { attributes: {}, }; assert.strictEqual( - computeStateDisplay(altLocalize, stateObj, localeData, {}), + computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}), "state.default.unavailable" ); }); @@ -378,7 +404,7 @@ describe("computeStateDisplay", () => { attributes: {}, }; assert.strictEqual( - computeStateDisplay(altLocalize, stateObj, localeData, {}), + computeStateDisplay(altLocalize, stateObj, localeData, demoConfig, {}), "My Custom State" ); }); @@ -396,7 +422,7 @@ describe("computeStateDisplay", () => { }, }; assert.strictEqual( - computeStateDisplay(localize, stateObj, localeData, entities), + computeStateDisplay(localize, stateObj, localeData, demoConfig, entities), "component.custom_integration.entity.sensor.custom_translation.state.custom_state" ); }); diff --git a/test/common/string/format_number.ts b/test/common/string/format_number.ts index 986ab9a01b..2004b06234 100644 --- a/test/common/string/format_number.ts +++ b/test/common/string/format_number.ts @@ -12,6 +12,7 @@ import { TimeFormat, FirstWeekday, DateFormat, + TimeZone, } from "../../../src/data/translation"; describe("formatNumber", () => { @@ -21,6 +22,7 @@ describe("formatNumber", () => { number_format: NumberFormat.language, time_format: TimeFormat.language, date_format: DateFormat.language, + time_zone: TimeZone.local, first_weekday: FirstWeekday.language, };