mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Fix calendar date display and parsing issues (#14817)
Co-authored-by: Bram Kragten <mail@bramkragten.nl> fixes undefined
This commit is contained in:
parent
825008e24a
commit
36e99c3c0f
@ -106,6 +106,7 @@
|
||||
"core-js": "^3.15.2",
|
||||
"cropperjs": "^1.5.12",
|
||||
"date-fns": "^2.23.0",
|
||||
"date-fns-tz": "^1.3.7",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fuse.js": "^6.0.0",
|
||||
|
@ -1,6 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
||||
import { addDays, isSameDay } from "date-fns/esm";
|
||||
import { toDate } from "date-fns-tz";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { RRule, Weekday } from "rrule";
|
||||
@ -185,11 +186,12 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
};
|
||||
|
||||
private _formatDateRange() {
|
||||
const start = new Date(this._data!.dtstart);
|
||||
// Parse a dates in the browser timezone
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
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
|
||||
const end = isDate(this._data.dtend)
|
||||
? addDays(new Date(this._data!.dtend), -1)
|
||||
: new Date(this._data!.dtend);
|
||||
const end = isDate(this._data.dtend) ? addDays(endValue, -1) : endValue;
|
||||
// The range can be shortened when the start and end are on the same day.
|
||||
if (isSameDay(start, end)) {
|
||||
if (isDate(this._data.dtstart)) {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
differenceInMilliseconds,
|
||||
startOfHour,
|
||||
} from "date-fns/esm";
|
||||
import { formatInTimeZone, toDate } from "date-fns-tz";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@ -60,6 +61,12 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
// Dates are manipulated and displayed in the browser timezone
|
||||
// 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.
|
||||
private _timeZone?: string;
|
||||
|
||||
public showDialog(params: CalendarEventEditDialogParams): void {
|
||||
this._error = undefined;
|
||||
this._info = undefined;
|
||||
@ -71,6 +78,9 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
computeStateDomain(stateObj) === "calendar" &&
|
||||
supportsFeature(stateObj, CalendarEntityFeature.CREATE_EVENT)
|
||||
)?.entity_id;
|
||||
this._timeZone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone ||
|
||||
this.hass.config.time_zone;
|
||||
if (params.entry) {
|
||||
const entry = params.entry!;
|
||||
this._allDay = isDate(entry.dtstart);
|
||||
@ -281,20 +291,30 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
private _isEditableCalendar = (entityStateObj: HassEntity) =>
|
||||
supportsFeature(entityStateObj, CalendarEntityFeature.CREATE_EVENT);
|
||||
|
||||
private _getLocaleStrings = memoizeOne((startDate?: Date, endDate?: Date) =>
|
||||
// en-CA locale used for date format YYYY-MM-DD
|
||||
// en-GB locale used for 24h time format HH:MM:SS
|
||||
{
|
||||
const timeZone = this.hass.config.time_zone;
|
||||
return {
|
||||
startDate: startDate?.toLocaleDateString("en-CA", { timeZone }),
|
||||
startTime: startDate?.toLocaleTimeString("en-GB", { timeZone }),
|
||||
endDate: endDate?.toLocaleDateString("en-CA", { timeZone }),
|
||||
endTime: endDate?.toLocaleTimeString("en-GB", { timeZone }),
|
||||
};
|
||||
}
|
||||
private _getLocaleStrings = memoizeOne(
|
||||
(startDate?: Date, endDate?: Date) => ({
|
||||
startDate: this._formatDate(startDate!),
|
||||
startTime: this._formatTime(startDate!),
|
||||
endDate: this._formatDate(endDate!),
|
||||
endTime: this._formatTime(endDate!),
|
||||
})
|
||||
);
|
||||
|
||||
// Formats a date in specified timezone, or defaulting to browser display timezone
|
||||
private _formatDate(date: Date, timeZone: string = this._timeZone!): string {
|
||||
return formatInTimeZone(date, timeZone, "yyyy-MM-dd");
|
||||
}
|
||||
|
||||
// Formats a time in specified timezone, or defaulting to browser display timezone
|
||||
private _formatTime(date: Date, timeZone: string = this._timeZone!): string {
|
||||
return formatInTimeZone(date, timeZone, "HH:mm:ss"); // 24 hr
|
||||
}
|
||||
|
||||
// Parse a date in the browser timezone
|
||||
private _parseDate(dateStr: string): Date {
|
||||
return toDate(dateStr, { timeZone: this._timeZone! });
|
||||
}
|
||||
|
||||
private _clearInfo() {
|
||||
this._info = undefined;
|
||||
}
|
||||
@ -319,27 +339,14 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
// Store previous event duration
|
||||
const duration = differenceInMilliseconds(this._dtend!, this._dtstart!);
|
||||
|
||||
this._dtstart = new Date(
|
||||
ev.detail.value +
|
||||
"T" +
|
||||
this._dtstart!.toLocaleTimeString("en-GB", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
})
|
||||
this._dtstart = this._parseDate(
|
||||
`${ev.detail.value}T${this._formatTime(this._dtstart!)}`
|
||||
);
|
||||
|
||||
// Prevent that the end time can be before the start time. Try to keep the
|
||||
// duration the same.
|
||||
if (this._dtend! <= this._dtstart!) {
|
||||
const newEnd = addMilliseconds(this._dtstart, duration);
|
||||
// en-CA locale used for date format YYYY-MM-DD
|
||||
// en-GB locale used for 24h time format HH:MM:SS
|
||||
this._dtend = new Date(
|
||||
`${newEnd.toLocaleDateString("en-CA", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
})}T${newEnd.toLocaleTimeString("en-GB", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
})}`
|
||||
);
|
||||
this._dtend = addMilliseconds(this._dtstart, duration);
|
||||
this._info = this.hass.localize(
|
||||
"ui.components.calendar.event.end_auto_adjusted"
|
||||
);
|
||||
@ -347,12 +354,8 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _endDateChanged(ev: CustomEvent) {
|
||||
this._dtend = new Date(
|
||||
ev.detail.value +
|
||||
"T" +
|
||||
this._dtend!.toLocaleTimeString("en-GB", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
})
|
||||
this._dtend = this._parseDate(
|
||||
`${ev.detail.value}T${this._formatTime(this._dtend!)}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -360,25 +363,14 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
// Store previous event duration
|
||||
const duration = differenceInMilliseconds(this._dtend!, this._dtstart!);
|
||||
|
||||
this._dtstart = new Date(
|
||||
this._dtstart!.toLocaleDateString("en-CA", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
}) +
|
||||
"T" +
|
||||
ev.detail.value
|
||||
this._dtstart = this._parseDate(
|
||||
`${this._formatDate(this._dtstart!)}T${ev.detail.value}`
|
||||
);
|
||||
|
||||
// Prevent that the end time can be before the start time. Try to keep the
|
||||
// duration the same.
|
||||
if (this._dtend! <= this._dtstart!) {
|
||||
const newEnd = addMilliseconds(new Date(this._dtstart), duration);
|
||||
this._dtend = new Date(
|
||||
`${newEnd.toLocaleDateString("en-CA", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
})}T${newEnd.toLocaleTimeString("en-GB", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
})}`
|
||||
);
|
||||
this._dtend = addMilliseconds(new Date(this._dtstart), duration);
|
||||
this._info = this.hass.localize(
|
||||
"ui.components.calendar.event.end_auto_adjusted"
|
||||
);
|
||||
@ -386,20 +378,12 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _endTimeChanged(ev: CustomEvent) {
|
||||
this._dtend = new Date(
|
||||
this._dtend!.toLocaleDateString("en-CA", {
|
||||
timeZone: this.hass.config.time_zone,
|
||||
}) +
|
||||
"T" +
|
||||
ev.detail.value
|
||||
this._dtend = this._parseDate(
|
||||
`${this._formatDate(this._dtend!)}T${ev.detail.value}`
|
||||
);
|
||||
}
|
||||
|
||||
private _calculateData() {
|
||||
const { startDate, startTime, endDate, endTime } = this._getLocaleStrings(
|
||||
this._dtstart,
|
||||
this._dtend
|
||||
);
|
||||
const data: CalendarEventMutableParams = {
|
||||
summary: this._summary,
|
||||
description: this._description,
|
||||
@ -408,14 +392,18 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
dtend: "",
|
||||
};
|
||||
if (this._allDay) {
|
||||
data.dtstart = startDate!;
|
||||
data.dtstart = this._formatDate(this._dtstart!);
|
||||
// End date/time is exclusive when persisted
|
||||
data.dtend = addDays(new Date(this._dtend!), 1).toLocaleDateString(
|
||||
"en-CA"
|
||||
);
|
||||
data.dtend = this._formatDate(addDays(this._dtend!, 1));
|
||||
} else {
|
||||
data.dtstart = `${startDate}T${startTime}`;
|
||||
data.dtend = `${endDate}T${endTime}`;
|
||||
data.dtstart = `${this._formatDate(
|
||||
this._dtstart!,
|
||||
this.hass.config.time_zone
|
||||
)}T${this._formatTime(this._dtstart!, this.hass.config.time_zone)}`;
|
||||
data.dtend = `${this._formatDate(
|
||||
this._dtend!,
|
||||
this.hass.config.time_zone
|
||||
)}T${this._formatTime(this._dtend!, this.hass.config.time_zone)}`;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
10
yarn.lock
10
yarn.lock
@ -6993,6 +6993,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns-tz@npm:^1.3.7":
|
||||
version: 1.3.7
|
||||
resolution: "date-fns-tz@npm:1.3.7"
|
||||
peerDependencies:
|
||||
date-fns: ">=2.0.0"
|
||||
checksum: b749613669223056d5e6d715114c94bec57234b676d0cea0c72ca710626c81e9ea04df6441852a5fec74b42c5f27b2f076e13697ec43da360b67806a3042a10e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:^2.23.0":
|
||||
version: 2.23.0
|
||||
resolution: "date-fns@npm:2.23.0"
|
||||
@ -9427,6 +9436,7 @@ fsevents@^1.2.7:
|
||||
core-js: ^3.15.2
|
||||
cropperjs: ^1.5.12
|
||||
date-fns: ^2.23.0
|
||||
date-fns-tz: ^1.3.7
|
||||
deep-clone-simple: ^1.1.1
|
||||
deep-freeze: ^0.0.1
|
||||
del: ^4.0.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user