Remove Intl polyfill on connection and consistently resolve time zone (#19326)

This commit is contained in:
Steve Repsher 2024-01-09 09:06:50 -05:00 committed by GitHub
parent 96a41704ea
commit 88abeada44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 63 additions and 54 deletions

View File

@ -1,7 +1,8 @@
import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { FrontendLocaleData, DateFormat } from "../../data/translation";
import { DateFormat, FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { resolveTimeZone } from "./resolve-time-zone";
// Tuesday, August 10
export const formatDateWeekdayDay = (
@ -16,7 +17,7 @@ const formatDateWeekdayDayMem = memoizeOne(
weekday: "long",
month: "long",
day: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -33,7 +34,7 @@ const formatDateMem = memoizeOne(
year: "numeric",
month: "long",
day: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -50,7 +51,7 @@ const formatDateShortMem = memoizeOne(
year: "numeric",
month: "short",
day: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -105,7 +106,7 @@ const formatDateNumericMem = memoizeOne(
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
}
@ -113,7 +114,7 @@ const formatDateNumericMem = memoizeOne(
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
});
}
);
@ -130,7 +131,7 @@ const formatDateVeryShortMem = memoizeOne(
new Intl.DateTimeFormat(locale.language, {
day: "numeric",
month: "short",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -146,7 +147,7 @@ const formatDateMonthYearMem = memoizeOne(
new Intl.DateTimeFormat(locale.language, {
month: "long",
year: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -161,7 +162,7 @@ const formatDateMonthMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "long",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -176,7 +177,7 @@ const formatDateYearMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
year: "numeric",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -191,7 +192,7 @@ const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -206,6 +207,6 @@ const formatDateWeekdayShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "short",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);

View File

@ -4,6 +4,7 @@ import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { formatDateNumeric } from "./format_date";
import { formatTime } from "./format_time";
import { resolveTimeZone } from "./resolve-time-zone";
import { useAmPm } from "./use_am_pm";
// August 9, 2021, 8:23 AM
@ -22,7 +23,7 @@ const formatDateTimeMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -42,7 +43,7 @@ const formatShortDateTimeWithYearMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -61,7 +62,7 @@ const formatShortDateTimeMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -82,7 +83,7 @@ const formatDateTimeWithSecondsMem = memoizeOne(
minute: "2-digit",
second: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);

View File

@ -2,6 +2,7 @@ import { HassConfig } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import "../../resources/intl-polyfill";
import { resolveTimeZone } from "./resolve-time-zone";
import { useAmPm } from "./use_am_pm";
// 9:15 PM || 21:15
@ -17,7 +18,7 @@ const formatTimeMem = memoizeOne(
hour: "numeric",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -35,7 +36,7 @@ const formatTimeWithSecondsMem = memoizeOne(
minute: "2-digit",
second: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -53,7 +54,7 @@ const formatTimeWeekdayMem = memoizeOne(
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
@ -71,6 +72,6 @@ const formatTime24hMem = memoizeOne(
hour: "numeric",
minute: "2-digit",
hour12: false,
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);

View File

@ -1,4 +1,5 @@
import memoizeOne from "memoize-one";
import "../../resources/intl-polyfill";
export const localizeWeekdays = memoizeOne(
(language: string, short: boolean): string[] => {

View File

@ -0,0 +1,15 @@
import { TimeZone } from "../../data/translation";
// Browser time zone can be determined from Intl, with fallback to UTC for polyfill or no support.
// Alternatively, we could fallback to a fixed offset IANA zone (e.g. "Etc/GMT+5") using
// Date.prototype.getTimeOffset(), but IANA only has whole hour Etc zones, and problems
// might occur with relative time due to DST.
// Use optional chain instead of polyfill import since polyfill will always return UTC
export const LOCAL_TIME_ZONE =
Intl.DateTimeFormat?.().resolvedOptions?.().timeZone ?? "UTC";
// Pick time zone based on user profile option. Core zone is used when local cannot be determined.
export const resolveTimeZone = (option: TimeZone, serverTimeZone: string) =>
option === TimeZone.local && LOCAL_TIME_ZONE !== "UTC"
? LOCAL_TIME_ZONE
: serverTimeZone;

View File

@ -9,6 +9,7 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { LOCAL_TIME_ZONE } from "../common/datetime/resolve-time-zone";
import { fireEvent } from "../common/dom/fire_event";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-alert";
@ -33,8 +34,7 @@ class OnboardingCoreConfig extends LitElement {
private _elevation = "0";
private _timeZone: ConfigUpdateValues["time_zone"] =
Intl.DateTimeFormat?.().resolvedOptions?.().timeZone;
private _timeZone: ConfigUpdateValues["time_zone"] = LOCAL_TIME_ZONE;
private _language: ConfigUpdateValues["language"] = getLocalLanguage();

View File

@ -25,6 +25,7 @@ import { renderRRuleAsText } from "./recurrence";
import { showConfirmEventDialog } from "./show-confirm-event-dialog-box";
import { CalendarEventDetailDialogParams } from "./show-dialog-calendar-event-detail";
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
class DialogCalendarEventDetail extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -138,8 +139,10 @@ class DialogCalendarEventDetail extends LitElement {
}
private _formatDateRange() {
// Parse a dates in the browser timezone
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const timeZone = resolveTimeZone(
this.hass.locale.time_zone,
this.hass.config.time_zone
);
const start = toDate(this._data!.dtstart, { timeZone: timeZone });
const endValue = toDate(this._data!.dtend, { timeZone: timeZone });
// All day events should be displayed as a day earlier

View File

@ -11,6 +11,7 @@ import { HassEntity } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
import { fireEvent } from "../../common/dom/fire_event";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { supportsFeature } from "../../common/entity/supports-feature";
@ -32,7 +33,6 @@ import {
deleteCalendarEvent,
updateCalendarEvent,
} from "../../data/calendar";
import { TimeZone } from "../../data/translation";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import "../lovelace/components/hui-generic-entity-row";
@ -68,7 +68,7 @@ class DialogCalendarEventEditor extends LitElement {
@state() private _submitting = false;
// Dates are manipulated and displayed in the browser timezone
// Dates are displayed in the timezone according to the user's profile
// which may be different from the Home Assistant timezone. When
// events are persisted, they are relative to the Home Assistant
// timezone, but floating without a timezone.
@ -85,10 +85,10 @@ class DialogCalendarEventEditor extends LitElement {
computeStateDomain(stateObj) === "calendar" &&
supportsFeature(stateObj, CalendarEntityFeature.CREATE_EVENT)
)?.entity_id;
this._timeZone =
this.hass.locale.time_zone === TimeZone.local
? Intl.DateTimeFormat().resolvedOptions().timeZone
: this.hass.config.time_zone;
this._timeZone = resolveTimeZone(
this.hass.locale.time_zone,
this.hass.config.time_zone
);
if (params.entry) {
const entry = params.entry!;
this._allDay = isDate(entry.dtstart);

View File

@ -294,10 +294,7 @@ class HaConfigSectionGeneral extends LitElement {
this._country = this.hass.config.country;
this._language = this.hass.config.language;
this._elevation = this.hass.config.elevation;
this._timeZone =
this.hass.config.time_zone ||
Intl.DateTimeFormat?.().resolvedOptions?.().timeZone ||
"Etc/GMT";
this._timeZone = this.hass.config.time_zone || "Etc/GMT";
this._name = this.hass.config.location_name;
this._updateUnits = true;
}

View File

@ -2,6 +2,7 @@ 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 { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-card";
import "../../components/ha-select";
@ -48,10 +49,9 @@ class TimeZoneRow extends LitElement {
>${this.hass.localize(
`ui.panel.profile.time_zone.options.${format}`,
{
timezone: (format === "server"
? this.hass.config.time_zone
: Intl.DateTimeFormat?.().resolvedOptions?.().timeZone ||
""
timezone: resolveTimeZone(
format,
this.hass.config.time_zone
).replace("_", " "),
}
)}</span

View File

@ -3,6 +3,7 @@ import { formatInTimeZone, toDate } from "date-fns-tz";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
import { fireEvent } from "../../common/dom/fire_event";
import { supportsFeature } from "../../common/entity/supports-feature";
import "../../components/ha-alert";
@ -19,7 +20,6 @@ import {
deleteItems,
updateItem,
} from "../../data/todo";
import { TimeZone } from "../../data/translation";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
@ -54,10 +54,10 @@ class DialogTodoItemEditor extends LitElement {
public showDialog(params: TodoItemEditDialogParams): void {
this._error = undefined;
this._params = params;
this._timeZone =
this.hass.locale.time_zone === TimeZone.local
? Intl.DateTimeFormat().resolvedOptions().timeZone
: this.hass.config.time_zone;
this._timeZone = resolveTimeZone(
this.hass.locale.time_zone,
this.hass.config.time_zone
);
if (params.item) {
const entry = params.item;
this._checked = entry.status === TodoItemStatus.Completed;

View File

@ -259,17 +259,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
}
this._updateHass({ areas });
});
subscribeConfig(conn, (config) => {
if (this.hass?.config?.time_zone !== config.time_zone) {
import("../resources/intl-polyfill").then(() => {
if ("__setDefaultTimeZone" in Intl.DateTimeFormat) {
// @ts-ignore
Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone);
}
});
}
this._updateHass({ config });
});
subscribeConfig(conn, (config) => this._updateHass({ config }));
subscribeServices(conn, (services) => this._updateHass({ services }));
subscribePanels(conn, (panels) => this._updateHass({ panels }));
subscribeFrontendUserData(conn, "core", (userData) =>