Skip to content

Commit

Permalink
Fix UNTIL values to be inclusive of last event and bug in populating …
Browse files Browse the repository at this point in the history
…recurrence rules when editing calendar events (home-assistant#15024)

* Fix bug in populating recurrence rules when editing calendar events

* Set UNTIL value to be inclusive of the last instance

* Fix lint errors
  • Loading branch information
allenporter authored Jan 9, 2023
1 parent 5094e8f commit 8fac5f6
Showing 1 changed file with 49 additions and 25 deletions.
74 changes: 49 additions & 25 deletions src/panels/calendar/ha-recurrence-rule-editor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SelectedDetail } from "@material/mwc-list";
import { formatInTimeZone, toDate } from "date-fns-tz";
import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
Expand Down Expand Up @@ -64,7 +65,7 @@ export class RecurrenceRuleEditor extends LitElement {

@state() private _count?: number;

@state() private _until?: Date;
@state() private _untilDay?: Date;

@query("#monthly") private _monthlyRepeatSelect!: HaSelect;

Expand Down Expand Up @@ -97,15 +98,17 @@ export class RecurrenceRuleEditor extends LitElement {
}

if (
changedProps.has("timezone") ||
changedProps.has("_freq") ||
changedProps.has("_interval") ||
changedProps.has("_weekday") ||
changedProps.has("_monthlyRepeatWeekday") ||
changedProps.has("_monthday") ||
changedProps.has("_end") ||
changedProps.has("_count") ||
changedProps.has("_until")
!changedProps.has("value") &&
(changedProps.has("dtstart") ||
changedProps.has("timezone") ||
changedProps.has("_freq") ||
changedProps.has("_interval") ||
changedProps.has("_weekday") ||
changedProps.has("_monthlyRepeatWeekday") ||
changedProps.has("_monthday") ||
changedProps.has("_end") ||
changedProps.has("_count") ||
changedProps.has("_untilDay"))
) {
this._updateRule();
return;
Expand All @@ -122,7 +125,7 @@ export class RecurrenceRuleEditor extends LitElement {
this._monthlyRepeatWeekday = undefined;
this._end = "never";
this._count = undefined;
this._until = undefined;
this._untilDay = undefined;

this._computedRRule = this.value;
if (this.value === "") {
Expand Down Expand Up @@ -162,7 +165,7 @@ export class RecurrenceRuleEditor extends LitElement {
}
if (rrule.until) {
this._end = "on";
this._until = rrule.until;
this._untilDay = toDate(rrule.until, { timeZone: this.timezone });
} else if (rrule.count) {
this._end = "after";
this._count = rrule.count;
Expand Down Expand Up @@ -327,7 +330,7 @@ export class RecurrenceRuleEditor extends LitElement {
"ui.components.calendar.event.repeat.end_on.label"
)}
.locale=${this.locale}
.value=${this._until!.toISOString()}
.value=${this._formatDate(this._untilDay!)}
@value-changed=${this._onUntilChange}
></ha-date-input>
`
Expand Down Expand Up @@ -394,15 +397,15 @@ export class RecurrenceRuleEditor extends LitElement {
switch (this._end) {
case "after":
this._count = DEFAULT_COUNT[this._freq!];
this._until = undefined;
this._untilDay = undefined;
break;
case "on":
this._count = undefined;
this._until = untilValue(this._freq!);
this._untilDay = untilValue(this._freq!);
break;
default:
this._count = undefined;
this._until = undefined;
this._untilDay = undefined;
}
e.stopPropagation();
}
Expand All @@ -413,7 +416,9 @@ export class RecurrenceRuleEditor extends LitElement {

private _onUntilChange(e: CustomEvent) {
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
Expand Down Expand Up @@ -442,18 +447,27 @@ export class RecurrenceRuleEditor extends LitElement {
freq: convertRepeatFrequency(this._freq!)!,
interval: this._interval > 1 ? this._interval : undefined,
count: this._count,
until: this._until,
tzid: this.timezone,
byweekday: byweekday,
bymonthday: bymonthday,
};
let contentline = RRule.optionsToString(options);
if (this._until && this.allDay) {
// rrule.js only computes UNTIL values as DATE-TIME however rfc5545 says
// The value of the UNTIL rule part MUST have the same value type as the
// "DTSTART" property. If needed, strip off any time values as a workaround
// This converts "UNTIL=20220512T060000" to "UNTIL=20220512"
contentline = contentline.replace(/(UNTIL=\d{8})T\d{6}Z?/, "$1");
if (this._untilDay) {
// The UNTIL value should be inclusive of the last event instance
const until = toDate(
this._formatDate(this._untilDay!) +
"T" +
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
}
Expand All @@ -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`
ha-textfield,
ha-select {
Expand Down

0 comments on commit 8fac5f6

Please sign in to comment.