Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 121 additions & 1 deletion packages/localization/src/DateFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,135 @@
const DateFormatWrapped = DateFormatNative as typeof DateFormatT;

class DateFormat extends DateFormatWrapped {
/**
* Central cache for all DateFormat instances across the entire library.
* Shared by Calendar, DatePicker, TimePicker, and all other date components.
* Key format: "type:JSON.stringify(options):locale"
* @private
*/
private static _cache = new Map<string, DateFormat>();

private static _stats = {
totalCalls: 0,
cacheHits: 0,
cacheMisses: 0,
uniqueInstances: 0,
};

static logCacheStats() {
console.log("===== DateFormat Cache Statistics =====");

Check failure on line 47 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
console.log(`Total calls: ${DateFormat._stats.totalCalls}`);

Check failure on line 48 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
console.log(`Cache hits: ${DateFormat._stats.cacheHits} (${DateFormat._stats.totalCalls > 0 ? ((DateFormat._stats.cacheHits / DateFormat._stats.totalCalls) * 100).toFixed(1) : 0}%)`);

Check failure on line 49 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
console.log(`Cache misses: ${DateFormat._stats.cacheMisses} (${DateFormat._stats.totalCalls > 0 ? ((DateFormat._stats.cacheMisses / DateFormat._stats.totalCalls) * 100).toFixed(1) : 0}%)`);

Check failure on line 50 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
console.log(`Unique instances: ${DateFormat._cache.size}`);

Check failure on line 51 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
console.log(`Total instances: ${(window as any).dateformatinstances?.size || 0}`);

Check failure on line 52 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
console.log("Cache keys:");

Check failure on line 53 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
Array.from(DateFormat._cache.keys()).forEach((key, index) => {
console.log(` ${index + 1}. ${key}`);

Check failure on line 55 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
});
console.log("=========================================\n");

Check failure on line 57 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement

return {
totalCalls: DateFormat._stats.totalCalls,
cacheHits: DateFormat._stats.cacheHits,
cacheMisses: DateFormat._stats.cacheMisses,
uniqueInstances: DateFormat._cache.size,
totalInstances: (window as any).dateformatinstances?.size || 0,
};
}

static getDateInstance(oFormatOptions?: DateFormatOptions, oLocale?: LocaleWrapped): DateFormat;
static getDateInstance(oLocale?: LocaleWrapped): DateFormat;
static getDateInstance(oFormatOptionsOrLocale?: DateFormatOptions | LocaleWrapped, oLocale?: LocaleWrapped): DateFormat {
DateFormat._stats.totalCalls++;

if (oFormatOptionsOrLocale instanceof LocaleWrapped) {
return DateFormatWrapped.getDateInstance(undefined, oFormatOptionsOrLocale);
}

const nativeLocale = oLocale ?? new LocaleWrapped(getLocale().toString());
const cacheKey = `date:${JSON.stringify(oFormatOptionsOrLocale || {})}:${nativeLocale.toString()}`;

Check failure on line 78 in packages/localization/src/DateFormat.ts

View workflow job for this annotation

GitHub Actions / check

'nativeLocale' will evaluate to '[object Object]' when stringified

if (!DateFormat._cache.has(cacheKey)) {
DateFormat._stats.cacheMisses++;
console.log(`[DateFormat CACHE MISS #${DateFormat._stats.cacheMisses}] Creating NEW date instance | Total instances: ${DateFormat._cache.size + 1}`);
console.log(` Key: ${cacheKey}`);
const date = DateFormatWrapped.getDateInstance(oFormatOptionsOrLocale, nativeLocale);
DateFormat._cache.set(cacheKey, date);
} else {
DateFormat._stats.cacheHits++;
console.log(`[DateFormat CACHE HIT #${DateFormat._stats.cacheHits}] Reusing CACHED date instance`);
}

return DateFormat._cache.get(cacheKey)!;
}

static getTimeInstance(oFormatOptions?: DateFormatOptions, oLocale?: LocaleWrapped): DateFormat;
static getTimeInstance(oLocale?: LocaleWrapped): DateFormat;
static getTimeInstance(oFormatOptionsOrLocale?: DateFormatOptions | LocaleWrapped, oLocale?: LocaleWrapped): DateFormat {
DateFormat._stats.totalCalls++;

if (oFormatOptionsOrLocale instanceof LocaleWrapped) {
return DateFormatWrapped.getTimeInstance(undefined, oFormatOptionsOrLocale);
}

const nativeLocale = oLocale ?? new LocaleWrapped(getLocale().toString());
const cacheKey = `time:${JSON.stringify(oFormatOptionsOrLocale || {})}:${nativeLocale.toString()}`;

if (!DateFormat._cache.has(cacheKey)) {
DateFormat._stats.cacheMisses++;
console.log(`[DateFormat CACHE MISS #${DateFormat._stats.cacheMisses}] Creating NEW time instance | Total instances: ${DateFormat._cache.size + 1}`);
console.log(` Key: ${cacheKey}`);
const time = DateFormatWrapped.getTimeInstance(oFormatOptionsOrLocale, nativeLocale);
DateFormat._cache.set(cacheKey, time);
} else {
DateFormat._stats.cacheHits++;
console.log(`[DateFormat CACHE HIT #${DateFormat._stats.cacheHits}] Reusing CACHED time instance`);
}

return DateFormat._cache.get(cacheKey)!;
}

static getDateTimeInstance(oFormatOptions?: DateFormatOptions, oLocale?: LocaleWrapped): DateFormat;
static getDateTimeInstance(oLocale?: LocaleWrapped): DateFormat;
static getDateTimeInstance(oFormatOptionsOrLocale?: DateFormatOptions | LocaleWrapped, oLocale?: LocaleWrapped): DateFormat {
DateFormat._stats.totalCalls++;

if (oFormatOptionsOrLocale instanceof LocaleWrapped) {
return DateFormatWrapped.getDateTimeInstance(undefined, oFormatOptionsOrLocale);
}

const nativeLocale = oLocale ?? new LocaleWrapped(getLocale().toString());
return DateFormatWrapped.getDateInstance(oFormatOptionsOrLocale, nativeLocale);
const cacheKey = `datetime:${JSON.stringify(oFormatOptionsOrLocale || {})}:${nativeLocale.toString()}`;

if (!DateFormat._cache.has(cacheKey)) {
DateFormat._stats.cacheMisses++;
console.log(`[DateFormat CACHE MISS #${DateFormat._stats.cacheMisses}] Creating NEW datetime instance | Total instances: ${DateFormat._cache.size + 1}`);
console.log(` Key: ${cacheKey}`);
const datetime = DateFormatWrapped.getDateTimeInstance(oFormatOptionsOrLocale, nativeLocale);
DateFormat._cache.set(cacheKey, datetime);
} else {
DateFormat._stats.cacheHits++;
console.log(`[DateFormat CACHE HIT #${DateFormat._stats.cacheHits}] Reusing CACHED datetime instance`);
}

return DateFormat._cache.get(cacheKey)!;
}
}

// @ts-ignore
window.DateFormatStats = {
log: () => DateFormat.logCacheStats(),
get: () => ({
// @ts-ignore
totalCalls: DateFormat._stats.totalCalls,
// @ts-ignore
cacheHits: DateFormat._stats.cacheHits,
// @ts-ignore
cacheMisses: DateFormat._stats.cacheMisses,
// @ts-ignore
uniqueInstances: DateFormat._cache.size,
}),
};

export default DateFormat;
21 changes: 15 additions & 6 deletions packages/main/src/Calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ class Calendar extends CalendarPart {
_getHeaderTextForMonth(monthTimestamp: number): { monthText: string, yearText: string, secondMonthText?: string, secondYearText?: string } {
const calendarDate = CalendarDateComponent.fromTimestamp(monthTimestamp * 1000, this._primaryCalendarType);
const localeData = getCachedLocaleDataInstance(getLocale());
const yearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this._primaryCalendarType });
const yearFormat = this._primaryYearFormat;

const monthText = localeData.getMonthsStandAlone("wide", this._primaryCalendarType)[calendarDate.getMonth()];
const localDate = calendarDate.toLocalJSDate();
Expand All @@ -463,7 +463,7 @@ class Calendar extends CalendarPart {
const secondaryCalendarDate = secondaryDate.firstDate || secondaryDate.lastDate;
const secondaryLocaleData = getCachedLocaleDataInstance(getLocale());
result.secondMonthText = secondaryLocaleData.getMonthsStandAlone("wide", this._secondaryCalendarType)[secondaryCalendarDate.getMonth()];
const secondaryYearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this._secondaryCalendarType });
const secondaryYearFormat = this._secondaryYearFormat;
result.secondYearText = String(secondaryYearFormat.format(secondaryCalendarDate.toLocalJSDate(), true));
}

Expand Down Expand Up @@ -641,11 +641,12 @@ class Calendar extends CalendarPart {
}

async onAfterRendering() {
console.log(`[Calendar ${this._id}] ===== RENDER CYCLE =====`);
await renderFinished(); // Await for the current picker to render and then ask if it has previous/next pages
this._previousButtonDisabled = !this._currentPickerDOM._hasPreviousPage();
this._nextButtonDisabled = !this._currentPickerDOM._hasNextPage();

const yearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this.primaryCalendarType });
const yearFormat = this._primaryYearFormat;
const localeData = getCachedLocaleDataInstance(getLocale());
this._headerMonthButtonText = localeData.getMonthsStandAlone("wide", this.primaryCalendarType)[this._calendarDate.getMonth()];
this._headerYearButtonText = String(yearFormat.format(this._localDate, true));
Expand Down Expand Up @@ -724,6 +725,14 @@ class Calendar extends CalendarPart {
return this.shadowRoot!.querySelector(`[ui5-${this._currentPicker}picker]`)! as unknown as ICalendarPicker;
}

get _primaryYearFormat() {
return DateFormat.getDateInstance({ format: "y", calendarType: this._primaryCalendarType });
}

get _secondaryYearFormat() {
return DateFormat.getDateInstance({ format: "y", calendarType: this._secondaryCalendarType });
}

/**
* Returns the focusable element inside the Calendar (the current picker)
* @override
Expand All @@ -747,7 +756,7 @@ class Calendar extends CalendarPart {
}

_setSecondaryCalendarTypeButtonText() {
const yearFormatSecType = DateFormat.getDateInstance({ format: "y", calendarType: this._secondaryCalendarType });
const yearFormatSecType = this._secondaryYearFormat;
this._headerYearButtonTextSecType = String(yearFormatSecType.format(this._localDate, true));

const currentYearRange = this._currentYearRange;
Expand All @@ -767,7 +776,7 @@ class Calendar extends CalendarPart {
}

const localDate = UI5Date.getInstance(this._timestamp * 1000);
const secondYearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this._secondaryCalendarType });
const secondYearFormat = this._secondaryYearFormat;
const dateInSecType = transformDateToSecondaryType(this._primaryCalendarType, this._secondaryCalendarType, this._timestamp);
const secondMonthInfo = convertMonthNumbersToMonthNames(dateInSecType.firstDate.getMonth(), dateInSecType.lastDate.getMonth(), this._secondaryCalendarType);
const secondYearText = secondYearFormat.format(localDate);
Expand Down Expand Up @@ -1035,7 +1044,7 @@ class Calendar extends CalendarPart {
* @private
*/
_formatYearRangeText(yearRange: CalendarYearRangeT) {
const yearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this.primaryCalendarType });
const yearFormat = this._primaryYearFormat
const { rangeStart, rangeEnd } = this._createYearRangeDates(yearRange, this.primaryCalendarType);

const rangeStartText = yearFormat.format(rangeStart.toLocalJSDate());
Expand Down
Loading