Skip to content

Commit d256801

Browse files
authored
fix(ui5-date-picker): performance of selecting is improved when there are min and max date. (#13550)
When minDate/maxDate is set on date-related components (ui5-date-picker, ui5-daterange-picker, ui5-date-time-picker), selecting a date caused ~90 redundant parse() calls per render. The _minDate and _maxDate getters were recomputed from scratch on every access — calling parse() 2–3 times and constructing a new CalendarDate each time — even though the values hadn't changed. Similarly, disabledDates ranges were re-parsed on every cell in the 42-day loop (42 × N times per render). Changes: DateFormat.ts — added a module-level Map cache for getDateInstance(). Repeated calls with the same options return the cached instance instead of constructing a new one. DateComponentBase.ts — _minDate and _maxDate now cache their parsed CalendarDate result. Re-parsing only happens when minDate, maxDate, or primaryCalendarType actually changes. DayPicker.ts — _minDate, _maxDate, and disabled date timestamps are resolved once before the 42-cell render loop and passed into _isDateEnabled().
1 parent cfa6155 commit d256801

3 files changed

Lines changed: 56 additions & 22 deletions

File tree

packages/localization/src/DateFormat.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,25 @@ type DateFormatOptions = {
2727

2828
const DateFormatWrapped = DateFormatNative as typeof DateFormatT;
2929

30+
const _dateFormatCache = new Map<string, DateFormat>();
31+
3032
class DateFormat extends DateFormatWrapped {
3133
static getDateInstance(oFormatOptions?: DateFormatOptions, oLocale?: LocaleWrapped): DateFormat;
3234
static getDateInstance(oLocale?: LocaleWrapped): DateFormat;
3335
static getDateInstance(oFormatOptionsOrLocale?: DateFormatOptions | LocaleWrapped, oLocale?: LocaleWrapped): DateFormat {
36+
const locale = oLocale ?? new LocaleWrapped(getLocale().toString());
37+
3438
if (oFormatOptionsOrLocale instanceof LocaleWrapped) {
3539
return DateFormatWrapped.getDateInstance(undefined, oFormatOptionsOrLocale);
3640
}
37-
const nativeLocale = oLocale ?? new LocaleWrapped(getLocale().toString());
38-
return DateFormatWrapped.getDateInstance(oFormatOptionsOrLocale, nativeLocale);
41+
42+
const cacheKey = `${JSON.stringify(oFormatOptionsOrLocale ?? {})}__${String(locale)}`;
43+
let instance = _dateFormatCache.get(cacheKey);
44+
if (!instance) {
45+
instance = DateFormatWrapped.getDateInstance(oFormatOptionsOrLocale, locale);
46+
_dateFormatCache.set(cacheKey, instance);
47+
}
48+
return instance;
3949
}
4050
}
4151

packages/main/src/DateComponentBase.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ class DateComponentBase extends UI5Element {
121121
*/
122122
_isoFormatInstance?: DateFormat;
123123

124+
_cachedMinDate?: { key: string, value: CalendarDate };
125+
_cachedMaxDate?: { key: string, value: CalendarDate };
126+
124127
constructor() {
125128
super();
126129
}
@@ -135,23 +138,21 @@ class DateComponentBase extends UI5Element {
135138
}
136139

137140
get _minDate() {
138-
let minDate;
139-
140-
if (this.minDate) {
141-
minDate = this._getMinMaxCalendarDateFromString(this.minDate);
141+
const key = `${this.minDate}__${this._primaryCalendarType}`;
142+
if (!this._cachedMinDate || this._cachedMinDate.key !== key) {
143+
const parsed = this.minDate ? this._getMinMaxCalendarDateFromString(this.minDate) : undefined;
144+
this._cachedMinDate = { key, value: parsed || getMinCalendarDate(this._primaryCalendarType) };
142145
}
143-
144-
return minDate || getMinCalendarDate(this._primaryCalendarType);
146+
return this._cachedMinDate.value;
145147
}
146148

147149
get _maxDate() {
148-
let maxDate;
149-
150-
if (this.maxDate) {
151-
maxDate = this._getMinMaxCalendarDateFromString(this.maxDate)!;
150+
const key = `${this.maxDate}__${this._primaryCalendarType}`;
151+
if (!this._cachedMaxDate || this._cachedMaxDate.key !== key) {
152+
const parsed = this.maxDate ? this._getMinMaxCalendarDateFromString(this.maxDate) : undefined;
153+
this._cachedMaxDate = { key, value: parsed || getMaxCalendarDate(this._primaryCalendarType) };
152154
}
153-
154-
return maxDate || getMaxCalendarDate(this._primaryCalendarType);
155+
return this._cachedMaxDate.value;
155156
}
156157

157158
get _formatPattern() {

packages/main/src/DayPicker.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ class DayPicker extends CalendarPart implements ICalendarPicker {
241241
const todayDate = CalendarDate.fromLocalJSDate(UI5Date.getInstance(), this._primaryCalendarType); // current day date - calculate once
242242
const calendarDate = this._calendarDate; // store the _calendarDate value as this getter is expensive and degrades IE11 perf
243243

244+
const minDate = this._minDate;
245+
const maxDate = this._maxDate;
246+
const precomputedDisabledDates = this._precomputeDisabledDates();
247+
244248
const tempSecondDate = this.hasSecondaryCalendarType ? this._getSecondaryDay(tempDate) : undefined;
245249

246250
let week: Week = [];
@@ -262,7 +266,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker {
262266
const isSelectedBetween = this._isDayInsideSelectionRange(timestamp);
263267
const isOtherMonth = tempDate.getMonth() !== calendarDate.getMonth();
264268
const isWeekend = this._isWeekend(tempDate);
265-
const isDisabled = !this._isDateEnabled(tempDate);
269+
const isDisabled = !this._isDateEnabled(tempDate, minDate, maxDate, precomputedDisabledDates);
266270
const isToday = tempDate.isSame(todayDate);
267271
const isFirstDayOfWeek = tempDate.getDay() === firstDayOfWeek;
268272

@@ -825,28 +829,47 @@ class DayPicker extends CalendarPart implements ICalendarPicker {
825829
|| (iWeekendEnd < iWeekendStart && (iWeekDay >= iWeekendStart || iWeekDay <= iWeekendEnd));
826830
}
827831

832+
/**
833+
* Pre-computes disabled date range timestamps once before the rendering loop.
834+
* Avoids repeated date string parsing inside the per-cell _isDateEnabled check.
835+
* @private
836+
*/
837+
_precomputeDisabledDates(): Array<{ startTimestamp: number, endTimestamp: number }> {
838+
return this.disabledDates.map(range => ({
839+
startTimestamp: this._getTimestampFromDateValue(range.startValue),
840+
endTimestamp: this._getTimestampFromDateValue(range.endValue),
841+
}));
842+
}
843+
828844
/**
829845
* Checks if a given date is enabled (selectable).
830846
* A date is considered disabled if:
831847
* - It falls outside the min/max date range defined by the component
832848
* - It matches a single disabled date
833849
* - It falls within a disabled date range (exclusive of start and end dates)
834850
* @param date - The date to check
851+
* @param minDate - Pre-resolved min calendar date
852+
* @param maxDate - Pre-resolved max calendar date
853+
* @param precomputedDisabledDates - Pre-parsed disabled date range timestamps
835854
* @returns `true` if the date is enabled (selectable), `false` if disabled
836855
* @private
837856
*/
838-
_isDateEnabled(date: CalendarDate): boolean {
839-
if ((this._minDate && date.isBefore(this._minDate))
840-
|| (this._maxDate && date.isAfter(this._maxDate))) {
857+
_isDateEnabled(date: CalendarDate, minDate?: CalendarDate, maxDate?: CalendarDate, precomputedDisabledDates?: Array<{ startTimestamp: number, endTimestamp: number }>): boolean {
858+
const resolvedMin = minDate ?? this._minDate;
859+
const resolvedMax = maxDate ?? this._maxDate;
860+
861+
if ((resolvedMin && date.isBefore(resolvedMin))
862+
|| (resolvedMax && date.isAfter(resolvedMax))) {
841863
return false;
842864
}
843865

844866
const dateTimestamp = date.valueOf() / 1000;
867+
const disabledRanges = precomputedDisabledDates ?? this.disabledDates.map(range => ({
868+
startTimestamp: this._getTimestampFromDateValue(range.startValue),
869+
endTimestamp: this._getTimestampFromDateValue(range.endValue),
870+
}));
845871

846-
return !this.disabledDates.some(range => {
847-
const startTimestamp = this._getTimestampFromDateValue(range.startValue);
848-
const endTimestamp = this._getTimestampFromDateValue(range.endValue);
849-
872+
return !disabledRanges.some(({ startTimestamp, endTimestamp }) => {
850873
if (endTimestamp) {
851874
return dateTimestamp > startTimestamp && dateTimestamp < endTimestamp;
852875
}

0 commit comments

Comments
 (0)