mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Fix timezone issues calendar (#14501)
This commit is contained in:
parent
faa57e4c02
commit
241645fe8d
@ -54,6 +54,7 @@ export class HaDateInput extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
iconTrailing
|
iconTrailing
|
||||||
helperPersistent
|
helperPersistent
|
||||||
|
readonly
|
||||||
@click=${this._openDialog}
|
@click=${this._openDialog}
|
||||||
.value=${this.value
|
.value=${this.value
|
||||||
? formatDateNumeric(new Date(this.value), this.locale)
|
? formatDateNumeric(new Date(this.value), this.locale)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
||||||
import { isSameDay } from "date-fns/esm";
|
import { addDays, isSameDay } from "date-fns/esm";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { RRule } from "rrule";
|
import { RRule } from "rrule";
|
||||||
@ -134,7 +134,10 @@ class DialogCalendarEventDetail extends LitElement {
|
|||||||
|
|
||||||
private _formatDateRange() {
|
private _formatDateRange() {
|
||||||
const start = new Date(this._data!.dtstart);
|
const start = new Date(this._data!.dtstart);
|
||||||
const end = new Date(this._data!.dtend);
|
// 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);
|
||||||
// The range can be shortened when the start and end are on the same day.
|
// The range can be shortened when the start and end are on the same day.
|
||||||
if (isSameDay(start, end)) {
|
if (isSameDay(start, end)) {
|
||||||
if (isDate(this._data.dtstart)) {
|
if (isDate(this._data.dtstart)) {
|
||||||
@ -148,10 +151,15 @@ class DialogCalendarEventDetail extends LitElement {
|
|||||||
)} - ${formatTime(end, this.hass.locale)}`;
|
)} - ${formatTime(end, this.hass.locale)}`;
|
||||||
}
|
}
|
||||||
// An event across multiple dates, optionally with a time range
|
// An event across multiple dates, optionally with a time range
|
||||||
return `${formatDateTime(start, this.hass.locale)} - ${formatDateTime(
|
return `${
|
||||||
end,
|
isDate(this._data.dtstart)
|
||||||
this.hass.locale
|
? formatDate(start, this.hass.locale)
|
||||||
)}`;
|
: formatDateTime(start, this.hass.locale)
|
||||||
|
} - ${
|
||||||
|
isDate(this._data.dtend)
|
||||||
|
? formatDate(end, this.hass.locale)
|
||||||
|
: formatDateTime(end, this.hass.locale)
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _editEvent() {
|
private async _editEvent() {
|
||||||
|
@ -4,6 +4,7 @@ import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
|||||||
import { addDays, addHours, startOfHour } from "date-fns/esm";
|
import { addDays, addHours, startOfHour } from "date-fns/esm";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { isDate } from "../../common/string/is_date";
|
import { isDate } from "../../common/string/is_date";
|
||||||
import "../../components/ha-date-input";
|
import "../../components/ha-date-input";
|
||||||
import "../../components/ha-time-input";
|
import "../../components/ha-time-input";
|
||||||
@ -39,7 +40,9 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
|
|
||||||
@state() private _calendarId?: string;
|
@state() private _calendarId?: string;
|
||||||
|
|
||||||
@state() private _data?: CalendarEventMutableParams;
|
@state() private _summary = "";
|
||||||
|
|
||||||
|
@state() private _rrule?: string;
|
||||||
|
|
||||||
@state() private _allDay = false;
|
@state() private _allDay = false;
|
||||||
|
|
||||||
@ -49,40 +52,30 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
|
|
||||||
@state() private _submitting = false;
|
@state() private _submitting = false;
|
||||||
|
|
||||||
public async showDialog(
|
public showDialog(params: CalendarEventEditDialogParams): void {
|
||||||
params: CalendarEventEditDialogParams
|
|
||||||
): Promise<void> {
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._calendars = params.calendars;
|
this._calendars = params.calendars;
|
||||||
this._calendarId = params.calendarId || this._calendars[0].entity_id;
|
this._calendarId = params.calendarId || this._calendars[0].entity_id;
|
||||||
if (params.entry) {
|
if (params.entry) {
|
||||||
const entry = params.entry!;
|
const entry = params.entry!;
|
||||||
this._data = entry;
|
|
||||||
this._allDay = isDate(entry.dtstart);
|
this._allDay = isDate(entry.dtstart);
|
||||||
|
this._summary = entry.summary;
|
||||||
|
this._rrule = entry.rrule;
|
||||||
if (this._allDay) {
|
if (this._allDay) {
|
||||||
this._dtstart = new Date(entry.dtstart);
|
this._dtstart = new Date(entry.dtstart);
|
||||||
// Calendar event end dates are exclusive, but not shown that way in the UI. The
|
// Calendar event end dates are exclusive, but not shown that way in the UI. The
|
||||||
// reverse happens when persisting the event.
|
// reverse happens when persisting the event.
|
||||||
this._dtend = new Date(entry.dtend);
|
this._dtend = addDays(new Date(entry.dtend), -1);
|
||||||
this._dtend.setDate(this._dtend.getDate() - 1);
|
|
||||||
} else {
|
} else {
|
||||||
this._dtstart = new Date(entry.dtstart);
|
this._dtstart = new Date(entry.dtstart);
|
||||||
this._dtend = new Date(entry.dtend);
|
this._dtend = new Date(entry.dtend);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._data = {
|
|
||||||
summary: "",
|
|
||||||
// Dates are set in _dateChanged()
|
|
||||||
dtstart: "",
|
|
||||||
dtend: "",
|
|
||||||
};
|
|
||||||
this._allDay = false;
|
this._allDay = false;
|
||||||
this._dtstart = startOfHour(new Date());
|
this._dtstart = startOfHour(new Date());
|
||||||
this._dtend = addHours(this._dtstart, 1);
|
this._dtend = addHours(this._dtstart, 1);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
await this.updateComplete;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -90,6 +83,12 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const isCreate = this._params.entry === undefined;
|
const isCreate = this._params.entry === undefined;
|
||||||
|
|
||||||
|
const { startDate, startTime, endDate, endTime } = this._getLocaleStrings(
|
||||||
|
this._dtstart,
|
||||||
|
this._dtend
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
@ -100,7 +99,7 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
<div class="header_title">
|
<div class="header_title">
|
||||||
${isCreate
|
${isCreate
|
||||||
? this.hass.localize("ui.components.calendar.event.add")
|
? this.hass.localize("ui.components.calendar.event.add")
|
||||||
: this._data!.summary}
|
: this._summary}
|
||||||
</div>
|
</div>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
@ -155,13 +154,13 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.value=${this._data!.dtstart}
|
.value=${startDate}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._startDateChanged}
|
@value-changed=${this._startDateChanged}
|
||||||
></ha-date-input>
|
></ha-date-input>
|
||||||
${!this._allDay
|
${!this._allDay
|
||||||
? html`<ha-time-input
|
? html`<ha-time-input
|
||||||
.value=${this._data!.dtstart.split("T")[1]}
|
.value=${startTime}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._startTimeChanged}
|
@value-changed=${this._startTimeChanged}
|
||||||
></ha-time-input>`
|
></ha-time-input>`
|
||||||
@ -174,14 +173,14 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.value=${this._data!.dtend}
|
.value=${endDate}
|
||||||
.min=${this._data!.dtstart}
|
.min=${startDate}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._endDateChanged}
|
@value-changed=${this._endDateChanged}
|
||||||
></ha-date-input>
|
></ha-date-input>
|
||||||
${!this._allDay
|
${!this._allDay
|
||||||
? html`<ha-time-input
|
? html`<ha-time-input
|
||||||
.value=${this._data!.dtend.split("T")[1]}
|
.value=${endTime}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@value-changed=${this._endTimeChanged}
|
@value-changed=${this._endTimeChanged}
|
||||||
></ha-time-input>`
|
></ha-time-input>`
|
||||||
@ -190,7 +189,7 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<ha-recurrence-rule-editor
|
<ha-recurrence-rule-editor
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.value=${this._data!.rrule || ""}
|
.value=${this._rrule || ""}
|
||||||
@value-changed=${this._handleRRuleChanged}
|
@value-changed=${this._handleRRuleChanged}
|
||||||
>
|
>
|
||||||
</ha-recurrence-rule-editor>
|
</ha-recurrence-rule-editor>
|
||||||
@ -230,57 +229,78 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _handleSummaryChanged(ev) {
|
private _handleSummaryChanged(ev) {
|
||||||
this._data!.summary = ev.target.value;
|
this._summary = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRRuleChanged(ev) {
|
private _handleRRuleChanged(ev) {
|
||||||
this._data!.rrule = ev.detail.value;
|
this._rrule = ev.detail.value;
|
||||||
this.requestUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _allDayToggleChanged(ev) {
|
private _allDayToggleChanged(ev) {
|
||||||
this._allDay = ev.target.checked;
|
this._allDay = ev.target.checked;
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startDateChanged(ev: CustomEvent) {
|
private _startDateChanged(ev: CustomEvent) {
|
||||||
this._dtstart = new Date(
|
this._dtstart = new Date(
|
||||||
ev.detail.value + "T" + this._dtstart!.toISOString().split("T")[1]
|
ev.detail.value + "T" + this._dtstart!.toISOString().split("T")[1]
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endDateChanged(ev: CustomEvent) {
|
private _endDateChanged(ev: CustomEvent) {
|
||||||
this._dtend = new Date(
|
this._dtend = new Date(
|
||||||
ev.detail.value + "T" + this._dtend!.toISOString().split("T")[1]
|
ev.detail.value + "T" + this._dtend!.toISOString().split("T")[1]
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _startTimeChanged(ev: CustomEvent) {
|
private _startTimeChanged(ev: CustomEvent) {
|
||||||
this._dtstart = new Date(
|
this._dtstart = new Date(
|
||||||
this._dtstart!.toISOString().split("T")[0] + "T" + ev.detail.value
|
this._dtstart!.toISOString().split("T")[0] + "T" + ev.detail.value
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endTimeChanged(ev: CustomEvent) {
|
private _endTimeChanged(ev: CustomEvent) {
|
||||||
this._dtend = new Date(
|
this._dtend = new Date(
|
||||||
this._dtend!.toISOString().split("T")[0] + "T" + ev.detail.value
|
this._dtend!.toISOString().split("T")[0] + "T" + ev.detail.value
|
||||||
);
|
);
|
||||||
this._dateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dateChanged() {
|
private _calculateData() {
|
||||||
|
const { startDate, startTime, endDate, endTime } = this._getLocaleStrings(
|
||||||
|
this._dtstart,
|
||||||
|
this._dtend
|
||||||
|
);
|
||||||
|
const data: CalendarEventMutableParams = {
|
||||||
|
summary: this._summary,
|
||||||
|
rrule: this._rrule,
|
||||||
|
dtstart: "",
|
||||||
|
dtend: "",
|
||||||
|
};
|
||||||
if (this._allDay) {
|
if (this._allDay) {
|
||||||
this._data!.dtstart = this._dtstart!.toISOString();
|
data.dtstart = startDate!;
|
||||||
// End date/time is exclusive when persisted
|
// End date/time is exclusive when persisted
|
||||||
this._data!.dtend = addDays(new Date(this._dtend!), 1).toISOString();
|
data.dtend = addDays(new Date(this._dtend!), 1).toLocaleDateString(
|
||||||
|
"en-CA"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this._data!.dtstart = this._dtstart!.toISOString();
|
data.dtstart = `${startDate}T${startTime}`;
|
||||||
this._data!.dtend = this._dtend!.toISOString();
|
data.dtend = `${endDate}T${endTime}`;
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleCalendarChanged(ev: CustomEvent) {
|
private _handleCalendarChanged(ev: CustomEvent) {
|
||||||
@ -290,7 +310,11 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
private async _createEvent() {
|
private async _createEvent() {
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
await createCalendarEvent(this.hass!, this._calendarId!, this._data!);
|
await createCalendarEvent(
|
||||||
|
this.hass!,
|
||||||
|
this._calendarId!,
|
||||||
|
this._calculateData()
|
||||||
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = err ? err.message : "Unknown error";
|
this._error = err ? err.message : "Unknown error";
|
||||||
} finally {
|
} finally {
|
||||||
@ -358,9 +382,10 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
this._calendars = [];
|
this._calendars = [];
|
||||||
this._calendarId = undefined;
|
this._calendarId = undefined;
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
this._data = undefined;
|
|
||||||
this._dtstart = undefined;
|
this._dtstart = undefined;
|
||||||
this._dtend = undefined;
|
this._dtend = undefined;
|
||||||
|
this._summary = "";
|
||||||
|
this._rrule = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -53,19 +53,22 @@ export class RecurrenceRuleEditor extends LitElement {
|
|||||||
protected willUpdate(changedProps: PropertyValues) {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
if (!changedProps.has("value") && !changedProps.has("locale")) {
|
if (changedProps.has("locale")) {
|
||||||
|
this._allWeekdays = getWeekdays(firstWeekdayIndex(this.locale)).map(
|
||||||
|
(day: Weekday) => day.toString() as WeekdayStr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changedProps.has("value") || this._computedRRule === this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._interval = 1;
|
this._interval = 1;
|
||||||
this._weekday.clear();
|
this._weekday.clear();
|
||||||
this._end = "never";
|
this._end = "never";
|
||||||
this._count = undefined;
|
this._count = undefined;
|
||||||
this._until = undefined;
|
this._until = undefined;
|
||||||
|
|
||||||
this._allWeekdays = getWeekdays(firstWeekdayIndex(this.locale)).map(
|
|
||||||
(day: Weekday) => day.toString() as WeekdayStr
|
|
||||||
);
|
|
||||||
|
|
||||||
this._computedRRule = this.value;
|
this._computedRRule = this.value;
|
||||||
if (this.value === "") {
|
if (this.value === "") {
|
||||||
this._freq = "none";
|
this._freq = "none";
|
||||||
@ -274,6 +277,7 @@ export class RecurrenceRuleEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onUntilChange(e: CustomEvent) {
|
private _onUntilChange(e: CustomEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
this._until = new Date(e.detail.value);
|
this._until = new Date(e.detail.value);
|
||||||
this._updateRule();
|
this._updateRule();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// and the values defined by rrule.js.
|
// and the values defined by rrule.js.
|
||||||
import { RRule, Frequency, Weekday } from "rrule";
|
import { RRule, Frequency, Weekday } from "rrule";
|
||||||
import type { WeekdayStr } from "rrule";
|
import type { WeekdayStr } from "rrule";
|
||||||
|
import { addDays, addMonths, addWeeks, addYears } from "date-fns";
|
||||||
|
|
||||||
export type RepeatFrequency =
|
export type RepeatFrequency =
|
||||||
| "none"
|
| "none"
|
||||||
@ -35,14 +36,14 @@ export function untilValue(freq: RepeatFrequency): Date {
|
|||||||
const increment = DEFAULT_COUNT[freq];
|
const increment = DEFAULT_COUNT[freq];
|
||||||
switch (freq) {
|
switch (freq) {
|
||||||
case "yearly":
|
case "yearly":
|
||||||
return new Date(new Date().setFullYear(today.getFullYear() + increment));
|
return addYears(today, increment);
|
||||||
case "monthly":
|
case "monthly":
|
||||||
return new Date(new Date().setMonth(today.getMonth() + increment));
|
return addMonths(today, increment);
|
||||||
case "weekly":
|
case "weekly":
|
||||||
return new Date(new Date().setDate(today.getDate() + 7 * increment));
|
return addWeeks(today, increment);
|
||||||
case "daily":
|
case "daily":
|
||||||
default:
|
default:
|
||||||
return new Date(new Date().setDate(today.getDate() + increment));
|
return addDays(today, increment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user