Fix calendar date display and parsing issues (#14817)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
fixes undefined
This commit is contained in:
Allen Porter 2022-12-21 08:07:31 -08:00 committed by GitHub
parent 825008e24a
commit 36e99c3c0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 68 deletions

View File

@ -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",

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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