Fix UNTIL values to be inclusive of last event and bug in populating recurrence rules when editing calendar events (#15024)

* Fix bug in populating recurrence rules when editing calendar events

* Set UNTIL value to be inclusive of the last instance

* Fix lint errors
This commit is contained in:
Allen Porter 2023-01-09 03:28:31 -08:00 committed by GitHub
parent 5094e8f428
commit 8fac5f6d75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,4 +1,5 @@
import type { SelectedDetail } from "@material/mwc-list"; import type { SelectedDetail } from "@material/mwc-list";
import { formatInTimeZone, toDate } from "date-fns-tz";
import { css, html, LitElement, PropertyValues } from "lit"; import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
@ -64,7 +65,7 @@ export class RecurrenceRuleEditor extends LitElement {
@state() private _count?: number; @state() private _count?: number;
@state() private _until?: Date; @state() private _untilDay?: Date;
@query("#monthly") private _monthlyRepeatSelect!: HaSelect; @query("#monthly") private _monthlyRepeatSelect!: HaSelect;
@ -97,15 +98,17 @@ export class RecurrenceRuleEditor extends LitElement {
} }
if ( if (
changedProps.has("timezone") || !changedProps.has("value") &&
changedProps.has("_freq") || (changedProps.has("dtstart") ||
changedProps.has("_interval") || changedProps.has("timezone") ||
changedProps.has("_weekday") || changedProps.has("_freq") ||
changedProps.has("_monthlyRepeatWeekday") || changedProps.has("_interval") ||
changedProps.has("_monthday") || changedProps.has("_weekday") ||
changedProps.has("_end") || changedProps.has("_monthlyRepeatWeekday") ||
changedProps.has("_count") || changedProps.has("_monthday") ||
changedProps.has("_until") changedProps.has("_end") ||
changedProps.has("_count") ||
changedProps.has("_untilDay"))
) { ) {
this._updateRule(); this._updateRule();
return; return;
@ -122,7 +125,7 @@ export class RecurrenceRuleEditor extends LitElement {
this._monthlyRepeatWeekday = undefined; this._monthlyRepeatWeekday = undefined;
this._end = "never"; this._end = "never";
this._count = undefined; this._count = undefined;
this._until = undefined; this._untilDay = undefined;
this._computedRRule = this.value; this._computedRRule = this.value;
if (this.value === "") { if (this.value === "") {
@ -162,7 +165,7 @@ export class RecurrenceRuleEditor extends LitElement {
} }
if (rrule.until) { if (rrule.until) {
this._end = "on"; this._end = "on";
this._until = rrule.until; this._untilDay = toDate(rrule.until, { timeZone: this.timezone });
} else if (rrule.count) { } else if (rrule.count) {
this._end = "after"; this._end = "after";
this._count = rrule.count; this._count = rrule.count;
@ -327,7 +330,7 @@ export class RecurrenceRuleEditor extends LitElement {
"ui.components.calendar.event.repeat.end_on.label" "ui.components.calendar.event.repeat.end_on.label"
)} )}
.locale=${this.locale} .locale=${this.locale}
.value=${this._until!.toISOString()} .value=${this._formatDate(this._untilDay!)}
@value-changed=${this._onUntilChange} @value-changed=${this._onUntilChange}
></ha-date-input> ></ha-date-input>
` `
@ -394,15 +397,15 @@ export class RecurrenceRuleEditor extends LitElement {
switch (this._end) { switch (this._end) {
case "after": case "after":
this._count = DEFAULT_COUNT[this._freq!]; this._count = DEFAULT_COUNT[this._freq!];
this._until = undefined; this._untilDay = undefined;
break; break;
case "on": case "on":
this._count = undefined; this._count = undefined;
this._until = untilValue(this._freq!); this._untilDay = untilValue(this._freq!);
break; break;
default: default:
this._count = undefined; this._count = undefined;
this._until = undefined; this._untilDay = undefined;
} }
e.stopPropagation(); e.stopPropagation();
} }
@ -413,7 +416,9 @@ export class RecurrenceRuleEditor extends LitElement {
private _onUntilChange(e: CustomEvent) { private _onUntilChange(e: CustomEvent) {
e.stopPropagation(); e.stopPropagation();
this._until = new Date(e.detail.value); this._untilDay = toDate(e.detail.value + "T00:00:00", {
timeZone: this.timezone,
});
} }
// Reset the weekday selected when there is only a single value // Reset the weekday selected when there is only a single value
@ -442,18 +447,27 @@ export class RecurrenceRuleEditor extends LitElement {
freq: convertRepeatFrequency(this._freq!)!, freq: convertRepeatFrequency(this._freq!)!,
interval: this._interval > 1 ? this._interval : undefined, interval: this._interval > 1 ? this._interval : undefined,
count: this._count, count: this._count,
until: this._until,
tzid: this.timezone,
byweekday: byweekday, byweekday: byweekday,
bymonthday: bymonthday, bymonthday: bymonthday,
}; };
let contentline = RRule.optionsToString(options); let contentline = RRule.optionsToString(options);
if (this._until && this.allDay) { if (this._untilDay) {
// rrule.js only computes UNTIL values as DATE-TIME however rfc5545 says // The UNTIL value should be inclusive of the last event instance
// The value of the UNTIL rule part MUST have the same value type as the const until = toDate(
// "DTSTART" property. If needed, strip off any time values as a workaround this._formatDate(this._untilDay!) +
// This converts "UNTIL=20220512T060000" to "UNTIL=20220512" "T" +
contentline = contentline.replace(/(UNTIL=\d{8})T\d{6}Z?/, "$1"); this._formatTime(this.dtstart!),
{ timeZone: this.timezone }
);
// rrule.js can't compute some UNTIL variations so we compute that ourself. Must be
// in the same format as dtstart.
const format = this.allDay ? "yyyyMMdd" : "yyyyMMdd'T'HHmmss";
const newUntilValue = formatInTimeZone(
until,
this.hass.config.time_zone,
format
);
contentline += `;UNTIL=${newUntilValue}`;
} }
return contentline.slice(6); // Strip "RRULE:" prefix return contentline.slice(6); // Strip "RRULE:" prefix
} }
@ -473,6 +487,16 @@ export class RecurrenceRuleEditor extends LitElement {
); );
} }
// Formats a date in browser display timezone
private _formatDate(date: Date): string {
return formatInTimeZone(date, this.timezone!, "yyyy-MM-dd");
}
// Formats a time in browser display timezone
private _formatTime(date: Date): string {
return formatInTimeZone(date, this.timezone!, "HH:mm:ss");
}
static styles = css` static styles = css`
ha-textfield, ha-textfield,
ha-select { ha-select {