@openui5/sap.ui.unified 1.147.1 → 1.149.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/THIRDPARTY.txt +3 -3
  2. package/package.json +2 -2
  3. package/src/sap/ui/unified/.library +1 -1
  4. package/src/sap/ui/unified/Calendar.js +9 -4
  5. package/src/sap/ui/unified/CalendarAppointment.js +1 -1
  6. package/src/sap/ui/unified/CalendarDateInterval.js +1 -1
  7. package/src/sap/ui/unified/CalendarLegend.js +1 -1
  8. package/src/sap/ui/unified/CalendarLegendItem.js +1 -1
  9. package/src/sap/ui/unified/CalendarMonthInterval.js +1 -1
  10. package/src/sap/ui/unified/CalendarOneMonthInterval.js +1 -1
  11. package/src/sap/ui/unified/CalendarRow.js +1 -1
  12. package/src/sap/ui/unified/CalendarRowRenderer.js +23 -18
  13. package/src/sap/ui/unified/CalendarTimeInterval.js +10 -13
  14. package/src/sap/ui/unified/CalendarWeekInterval.js +1 -1
  15. package/src/sap/ui/unified/ColorPicker.js +1 -1
  16. package/src/sap/ui/unified/ColorPickerPopover.js +1 -1
  17. package/src/sap/ui/unified/ContentSwitcher.js +1 -1
  18. package/src/sap/ui/unified/Currency.js +1 -1
  19. package/src/sap/ui/unified/CurrencyRenderer.js +1 -1
  20. package/src/sap/ui/unified/DateRange.js +1 -1
  21. package/src/sap/ui/unified/DateTypeRange.js +1 -1
  22. package/src/sap/ui/unified/FileUploader.js +19 -6
  23. package/src/sap/ui/unified/FileUploaderParameter.js +1 -1
  24. package/src/sap/ui/unified/FileUploaderXHRSettings.js +1 -1
  25. package/src/sap/ui/unified/Menu.js +1 -1
  26. package/src/sap/ui/unified/MenuItem.js +1 -1
  27. package/src/sap/ui/unified/MenuItemBase.js +1 -1
  28. package/src/sap/ui/unified/MenuItemGroup.js +1 -1
  29. package/src/sap/ui/unified/MenuRenderer.js +1 -1
  30. package/src/sap/ui/unified/MenuTextFieldItem.js +1 -1
  31. package/src/sap/ui/unified/MonthlyRecurrenceRule.js +58 -0
  32. package/src/sap/ui/unified/NonWorkingPeriod.js +1 -1
  33. package/src/sap/ui/unified/RecurrenceRule.js +112 -0
  34. package/src/sap/ui/unified/RecurringCalendarAppointment.js +222 -0
  35. package/src/sap/ui/unified/RecurringNonWorkingPeriod.js +112 -6
  36. package/src/sap/ui/unified/Shell.js +1 -1
  37. package/src/sap/ui/unified/ShellHeadItem.js +1 -1
  38. package/src/sap/ui/unified/ShellHeadUserItem.js +1 -1
  39. package/src/sap/ui/unified/ShellLayout.js +1 -1
  40. package/src/sap/ui/unified/ShellOverlay.js +1 -1
  41. package/src/sap/ui/unified/SplitContainer.js +1 -1
  42. package/src/sap/ui/unified/TimeRange.js +1 -1
  43. package/src/sap/ui/unified/WeeklyRecurrenceRule.js +39 -0
  44. package/src/sap/ui/unified/YearlyRecurrenceRule.js +53 -0
  45. package/src/sap/ui/unified/calendar/DatesRow.js +1 -1
  46. package/src/sap/ui/unified/calendar/Header.js +14 -2
  47. package/src/sap/ui/unified/calendar/HeaderRenderer.js +124 -50
  48. package/src/sap/ui/unified/calendar/IndexPicker.js +1 -1
  49. package/src/sap/ui/unified/calendar/Month.js +6 -7
  50. package/src/sap/ui/unified/calendar/MonthPicker.js +1 -1
  51. package/src/sap/ui/unified/calendar/MonthsRow.js +1 -1
  52. package/src/sap/ui/unified/calendar/OneMonthDatesRow.js +1 -1
  53. package/src/sap/ui/unified/calendar/RecurrenceUtils.js +653 -113
  54. package/src/sap/ui/unified/calendar/TimesRow.js +1 -1
  55. package/src/sap/ui/unified/calendar/WeeksRow.js +1 -1
  56. package/src/sap/ui/unified/calendar/YearPicker.js +1 -1
  57. package/src/sap/ui/unified/calendar/YearRangePicker.js +1 -1
  58. package/src/sap/ui/unified/library.js +73 -2
  59. package/src/sap/ui/unified/messagebundle_ko.properties +2 -2
  60. package/src/sap/ui/unified/messagebundle_zh_CN.properties +4 -4
  61. package/src/sap/ui/unified/messagebundle_zh_TW.properties +6 -6
  62. package/src/sap/ui/unified/themes/base/Calendar.less +76 -205
  63. package/src/sap/ui/unified/themes/base/FileUploader.less +2 -2
  64. package/src/sap/ui/unified/themes/sap_hcb/Calendar.less +3 -10
  65. package/src/sap/ui/unified/themes/sap_hcb/base_Calendar.less +54 -112
@@ -4,140 +4,680 @@
4
4
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5
5
  */
6
6
 
7
- // Provides class sap.ui.unified.calendar.RecurrenceUtils
8
7
  sap.ui.define([
9
- 'sap/ui/core/date/UI5Date'
8
+ 'sap/ui/core/date/UI5Date',
9
+ 'sap/ui/unified/library'
10
10
  ],
11
- function(
12
- UI5Date
13
- ) {
14
- "use strict";
15
-
16
- // Static class
17
-
18
- /**
19
- * Collection of utility functions to handle recurrence related operations.
20
- * @alias sap.ui.unified.calendar.RecurrenceUtils
21
- * @namespace
22
- * @private
23
- */
24
- var RecurrenceUtils = {};
25
-
26
- const PERIOD_TYPE = {
27
- "WORKING_PERIOD" : "working",
28
- "NON_WORKING_PERIOD": "non-working"
29
- };
30
-
31
- /**
32
- * Evaluates whether there is an occurrence for a given date.
33
- * The method will be used by <code>RecurringNonWorkingPeriod</code> by changing
34
- * the context to that of <code>RecurringNonWorkingPeriod</code>
35
- * @param {Date|module:sap/ui/core/date/UI5Date} oDate A date instance
36
- * @return {boolean} True if there is an occurrence for this day
37
- * @private
38
- * @ui5-restricted sap.ui.unified.RecurringNonWorkingPeriod
39
- */
40
- RecurrenceUtils.hasOccurrenceOnDate = function (oDate) {
41
- if (this.getRecurrenceType() !== "Daily") {
11
+ function(
12
+ UI5Date,
13
+ library
14
+ ) {
15
+ "use strict";
16
+
17
+ const RecurrenceType = library.RecurrenceType;
18
+ const RecurrenceRuleType = library.RecurrenceRuleType;
19
+ const WeekOfMonth = library.WeekOfMonth;
20
+
21
+ const RecurrenceUtils = {};
22
+
23
+ const TIME_PERIOD = {
24
+ ONE_DAY_IN_MS: 86400000, // 24 * 60 * 60 * 1000
25
+ ONE_WEEK_IN_MS: 604800000 // 7 * 24 * 60 * 60 * 1000
26
+ };
27
+
28
+ const PERIOD_TYPE = {
29
+ "WORKING_PERIOD": "working",
30
+ "NON_WORKING_PERIOD": "non-working"
31
+ };
32
+
33
+ /**
34
+ * Normalize recurrenceDay input to canonical array of JS day indices (0..6, 0 = Sunday).
35
+ * Accepts number or array of numbers in JS-style (0=Sunday..6=Saturday).
36
+ * Returns sorted unique array of numbers in range 0..6.
37
+ * @param {number|Array<number>} vDays
38
+ * @returns {number[]}
39
+ * @private
40
+ */
41
+ RecurrenceUtils._normalizeRecurrenceDays = function(vDays) {
42
+ if (vDays == null) {
43
+ return [];
44
+ }
45
+ const aDays = Array.isArray(vDays) ? vDays.slice() : [vDays];
46
+ const oUniqueDays = new Set();
47
+ aDays.forEach((vDay) => {
48
+ const iDay = Number(vDay);
49
+ if (!isNaN(iDay) && iDay >= 0 && iDay <= 6) {
50
+ oUniqueDays.add(iDay);
51
+ }
52
+ });
53
+ return Array.from(oUniqueDays).sort((a, b) => a - b);
54
+ };
55
+
56
+ /**
57
+ * Evaluates whether there is an occurrence for a given date.
58
+ * @param {Date|module:sap/ui/core/date/UI5Date} oDate A date instance
59
+ * @return {boolean} True if there is an occurrence for this day
60
+ * @private
61
+ */
62
+ RecurrenceUtils.hasOccurrenceOnDate = function(oDate) {
63
+ const oStartDate = UI5Date.getInstance(this.getStartDate());
64
+ const oCurrentDate = UI5Date.getInstance(oDate);
65
+ const oEndDate = this.getRecurrenceEndDate();
66
+ const sRecurrenceType = this.getRecurrenceType();
67
+ const iPattern = this.getRecurrencePattern();
68
+
69
+ // Normalize dates
70
+ oStartDate.setHours(0, 0, 0, 0);
71
+ oCurrentDate.setHours(0, 0, 0, 0);
72
+ const oEndDateNorm = oEndDate ? UI5Date.getInstance(oEndDate) : null;
73
+ if (oEndDateNorm) {
74
+ oEndDateNorm.setHours(23, 59, 59, 999);
75
+ }
76
+
77
+ const oStartDateUTC = Date.UTC(oStartDate.getFullYear(), oStartDate.getMonth(), oStartDate.getDate());
78
+ const oCurrentDateUTC = Date.UTC(oCurrentDate.getFullYear(), oCurrentDate.getMonth(), oCurrentDate.getDate());
79
+
80
+ // Check if date is in valid range
81
+ const isDateInRange = oCurrentDate >= oStartDate && (!oEndDateNorm || oCurrentDate <= oEndDateNorm);
82
+
83
+ if (!isDateInRange) {
84
+ return false;
85
+ }
86
+
87
+ // Daily
88
+ if (sRecurrenceType === RecurrenceType.Daily) {
89
+ const iDaysDiff = Math.floor((oCurrentDateUTC - oStartDateUTC) / TIME_PERIOD.ONE_DAY_IN_MS);
90
+ return iDaysDiff >= 0 && iDaysDiff % iPattern === 0;
91
+ }
92
+
93
+ // Weekly
94
+ if (sRecurrenceType === RecurrenceType.Weekly) {
95
+ const oRule = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
96
+ const aRecurrenceDays = oRule ? oRule.getDays() : [];
97
+ const iCurrentDayOfWeek = oCurrentDate.getDay();
98
+
99
+ if (aRecurrenceDays.length > 0 && !aRecurrenceDays.includes(iCurrentDayOfWeek)) {
100
+ return false;
101
+ }
102
+
103
+ if (aRecurrenceDays.length === 0 && iCurrentDayOfWeek !== oStartDate.getDay()) {
104
+ return false;
105
+ }
106
+
107
+ // Align both dates to their ISO week Monday (Mon=1..Sun=0 → offset 1..6,0)
108
+ const iStartDaysToMonday = oStartDate.getDay() === 0 ? 6 : oStartDate.getDay() - 1;
109
+ const oStartWeekMondayUTC = Date.UTC(
110
+ oStartDate.getFullYear(), oStartDate.getMonth(), oStartDate.getDate() - iStartDaysToMonday
111
+ );
112
+ const iCurrentDaysToMonday = oCurrentDate.getDay() === 0 ? 6 : oCurrentDate.getDay() - 1;
113
+ const oCurrentWeekMondayUTC = Date.UTC(
114
+ oCurrentDate.getFullYear(), oCurrentDate.getMonth(), oCurrentDate.getDate() - iCurrentDaysToMonday
115
+ );
116
+ const iWeeksDiff = Math.round((oCurrentWeekMondayUTC - oStartWeekMondayUTC) / TIME_PERIOD.ONE_WEEK_IN_MS);
117
+ return iWeeksDiff >= 0 && iWeeksDiff % iPattern === 0;
118
+ }
119
+
120
+ // Monthly
121
+ if (sRecurrenceType === RecurrenceType.Monthly) {
122
+ const oRule = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
123
+ const iMonthsDiff = (oCurrentDate.getFullYear() - oStartDate.getFullYear()) * 12 +
124
+ (oCurrentDate.getMonth() - oStartDate.getMonth());
125
+
126
+ if (iMonthsDiff < 0 || iMonthsDiff % iPattern !== 0) {
127
+ return false;
128
+ }
129
+
130
+ if (!oRule || (oRule.getType() === RecurrenceRuleType.DayOfMonth && !oRule.getDayOfMonth())) {
131
+ return oCurrentDate.getDate() === oStartDate.getDate();
132
+ }
133
+
134
+ if (oRule.getType() === RecurrenceRuleType.DayOfMonth) {
135
+ return oCurrentDate.getDate() === oRule.getDayOfMonth();
136
+ }
137
+
138
+ return RecurrenceUtils._matchesWeekOrderInMonth(oCurrentDate, oRule);
139
+ }
140
+
141
+ // Yearly
142
+ if (sRecurrenceType === RecurrenceType.Yearly) {
143
+ const oRule = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
144
+ const iYearsDiff = oCurrentDate.getFullYear() - oStartDate.getFullYear();
145
+
146
+ if (iYearsDiff < 0 || iYearsDiff % iPattern !== 0) {
147
+ return false;
148
+ }
149
+
150
+ if (!oRule || (oRule.getType() === RecurrenceRuleType.DayOfMonth && !oRule.getDayOfMonth())) {
151
+ return oCurrentDate.getMonth() === oStartDate.getMonth() &&
152
+ oCurrentDate.getDate() === oStartDate.getDate();
153
+ }
154
+
155
+ const iExpectedMonth = oRule.getMonth() >= 0 ? oRule.getMonth() : oStartDate.getMonth();
156
+ if (oCurrentDate.getMonth() !== iExpectedMonth) {
42
157
  return false;
43
158
  }
44
159
 
45
- const oStartDate = UI5Date.getInstance(this.getStartDate());
46
- const oCurrentDate = UI5Date.getInstance(oDate);
47
- const oStartDateUTC = Date.UTC(oStartDate.getFullYear(), oStartDate.getMonth(), oStartDate.getDate());
48
- const oCurrentDateUTC = Date.UTC(oCurrentDate.getFullYear(), oCurrentDate.getMonth(), oCurrentDate.getDate());
49
- oStartDate.setHours(0,0,0,0);
50
- oCurrentDate.setHours(0,0,0,0);
160
+ if (oRule.getType() === RecurrenceRuleType.DayOfMonth) {
161
+ return oCurrentDate.getDate() === oRule.getDayOfMonth();
162
+ }
163
+
164
+ return RecurrenceUtils._matchesWeekOrderInMonth(oCurrentDate, oRule);
165
+ }
166
+
167
+ return false;
168
+ };
169
+
170
+ /**
171
+ * Checks if date matches week order in month (e.g., "second Tuesday")
172
+ * @param {Date} oDate - Date to check
173
+ * @param {object} oAdvanced - Advanced recurrence config
174
+ * @returns {boolean} True if matches
175
+ * @private
176
+ */
177
+ RecurrenceUtils._matchesWeekOrderInMonth = function(oDate, oAdvanced) {
178
+ const iDayOfWeek = oAdvanced.getDayOfWeek();
179
+ const sWeekOfMonth = oAdvanced.getWeekOfMonth();
180
+
181
+ // Check if the day of week matches
182
+ if (oDate.getDay() !== iDayOfWeek) {
183
+ return false;
184
+ }
185
+
186
+ if (sWeekOfMonth === WeekOfMonth.Last) {
187
+ // Check if this is the last occurrence in the month
188
+ const oNextWeek = UI5Date.getInstance(oDate.getFullYear(), oDate.getMonth(), oDate.getDate() + 7);
189
+ return oNextWeek.getMonth() !== oDate.getMonth();
190
+ } else {
191
+ // Count how many times this day of week has occurred in the month up to this date
192
+ const iOccurrence = Math.floor((oDate.getDate() - 1) / 7) + 1;
193
+ const mWeekOfMonthMap = {
194
+ [WeekOfMonth.First]: 1,
195
+ [WeekOfMonth.Second]: 2,
196
+ [WeekOfMonth.Third]: 3,
197
+ [WeekOfMonth.Fourth]: 4
198
+ };
199
+ return iOccurrence === mWeekOfMonthMap[sWeekOfMonth];
200
+ }
201
+ };
51
202
 
52
- const iDayInMilliseconds = 24 * 60 * 60 * 1000;
53
- const isDateInRange = oCurrentDate >= oStartDate && oCurrentDate <= this.getRecurrenceEndDate();
203
+ RecurrenceUtils.calculateDurationInCell = function (oNonWorkingPart, oCellStartDate, iCurrentPointInMinutes){
204
+ const oNonWorkingPartDate = oNonWorkingPart.getStartDate();
205
+ const iMinutesInOneHour = 60;
206
+ let iDuration = oNonWorkingPart.getDurationInMinutes();
54
207
 
55
- const isWithCorrectPattern = Math.floor((oCurrentDateUTC - oStartDateUTC) / iDayInMilliseconds) % this.getRecurrencePattern() === 0;
208
+ if (oNonWorkingPartDate.getHours() < oCellStartDate.getHours()) {
209
+ const iTimeCell = oCellStartDate.getHours() * iMinutesInOneHour + oCellStartDate.getMinutes();
210
+ const iTimePart = oNonWorkingPartDate.getHours() * iMinutesInOneHour + oNonWorkingPartDate.getMinutes();
211
+ iDuration -= (iTimeCell - iTimePart);
212
+ } else if (oNonWorkingPartDate.getHours() === oCellStartDate.getHours() && oNonWorkingPartDate.getMinutes() > 0) {
213
+ iDuration = oNonWorkingPart.getDurationInMinutes() + iCurrentPointInMinutes > 60 ? iMinutesInOneHour - iCurrentPointInMinutes : oNonWorkingPart.getDurationInMinutes();
214
+ } else if (oNonWorkingPartDate.getHours() === oCellStartDate.getHours() && oNonWorkingPart.getEndDate().getHours() <= oCellStartDate.getHours() + 1) {
215
+ iDuration = oNonWorkingPart.getDurationInMinutes();
216
+ } else {
217
+ iDuration = iMinutesInOneHour - iCurrentPointInMinutes;
218
+ }
56
219
 
57
- return isDateInRange && isWithCorrectPattern;
58
- };
220
+ return iDuration;
221
+ };
222
+
223
+ /**
224
+ * Determines what portion of a calendar cell (representing one hour) is filled with non-working time and what portion is filled with working time.
225
+ * @param {Date|module:sap/ui/core/date/UI5Date} oCellStartDate A date instance for the current cell
226
+ * @param {Array<sap.ui.unified.NonWorkingPeriod | sap.ui.unified.RecurringNonWorkingPeriod>} aNonWorkingForCurrentHours An array of non-working periods
227
+ * @return {Array} An array of objects containing information about the type and duration of each part of the cell.
228
+ * @private
229
+ * @ui5-restricted sap.ui.unified.RecurringNonWorkingPeriod
230
+ */
231
+ RecurrenceUtils.getWorkingAndNonWorkingSegments = function (oCellStartDate, aNonWorkingForCurrentHours) {
232
+ const aCellInfo = [];
233
+ const iMinutesInOneHour = 60;
234
+
235
+ let iCurrentPointInMinutes = 0;
236
+ let index = 0;
237
+
238
+ while (iCurrentPointInMinutes < iMinutesInOneHour) {
239
+ const oNonWorkingPart = aNonWorkingForCurrentHours[index];
240
+ const oNonWorkingPartDate = oNonWorkingPart?.getStartDate();
241
+
242
+ if (!oNonWorkingPart) {
243
+ const iCurrentDuration = iMinutesInOneHour - iCurrentPointInMinutes;
244
+ aCellInfo.push({
245
+ type: PERIOD_TYPE.WORKING_PERIOD,
246
+ duration: iCurrentDuration
247
+ });
248
+
249
+ return aCellInfo;
250
+ }
59
251
 
60
- RecurrenceUtils.calculateDurationInCell = function (oNonWorkingPart, oCellStartDate, iCurrentPointInMinutes){
61
- const oNonWorkingPartDate = oNonWorkingPart.getStartDate();
62
- const iMinutesInOneHours = 60;
63
- let iDuration = oNonWorkingPart.getDurationInMinutes();
252
+ let iDuration = this.calculateDurationInCell(oNonWorkingPart, oCellStartDate, iCurrentPointInMinutes);
253
+ const iStartTimeInMin = oNonWorkingPartDate.getMinutes();
64
254
 
65
- if (oNonWorkingPartDate.getHours() < oCellStartDate.getHours()) {
66
- const iTimeCell = oCellStartDate.getHours() * iMinutesInOneHours + oCellStartDate.getMinutes();
67
- const iTimePart = oNonWorkingPartDate.getHours() * iMinutesInOneHours + oNonWorkingPartDate.getMinutes();
68
- iDuration -= (iTimeCell - iTimePart);
69
- } else if (oNonWorkingPartDate.getHours() === oCellStartDate.getHours() && oNonWorkingPartDate.getMinutes() > 0) {
70
- iDuration = oNonWorkingPart.getDurationInMinutes() + iCurrentPointInMinutes > 60 ? iMinutesInOneHours - iCurrentPointInMinutes : oNonWorkingPart.getDurationInMinutes();
71
- } else if (oNonWorkingPartDate.getHours() === oCellStartDate.getHours() && oNonWorkingPart.getEndDate().getHours() <= oCellStartDate.getHours() + 1) {
72
- iDuration = oNonWorkingPart.getDurationInMinutes();
255
+ if (iDuration > iMinutesInOneHour && oNonWorkingPartDate.getHours() < oCellStartDate.getHours()) {
256
+ aCellInfo.push({
257
+ type: PERIOD_TYPE.NON_WORKING_PERIOD,
258
+ duration: iMinutesInOneHour
259
+ });
260
+
261
+ return aCellInfo;
262
+ }
263
+
264
+ if (iStartTimeInMin === iCurrentPointInMinutes || oNonWorkingPartDate.getHours() < oCellStartDate.getHours()) {
265
+ aCellInfo.push({
266
+ type: PERIOD_TYPE.NON_WORKING_PERIOD,
267
+ duration: iDuration
268
+ });
269
+ index++;
73
270
  } else {
74
- iDuration = iMinutesInOneHours - iCurrentPointInMinutes;
75
- }
76
-
77
- return iDuration;
78
- };
79
-
80
- /**
81
- * Determines what portion of a calendar cell (representing one hour) is filled with non-working time and what portion is filled with working time.
82
- * @param {Date|module:sap/ui/core/date/UI5Date} oCellStartDate A date instance for the current cell
83
- * @param {Array<sap.ui.unified.NonWorkingPeriod | sap.ui.unified.RecurringNonWorkingPeriod>} aNonWorkingForCurrentHours An array of non-working periods
84
- * @return {Array} An array of objects containing information about the type and duration of each part of the cell.
85
- * @private
86
- * @ui5-restricted sap.ui.unified.RecurringNonWorkingPeriod
87
- */
88
- RecurrenceUtils.getWorkingAndNonWorkingSegments = function (oCellStartDate, aNonWorkingForCurrentHours) {
89
- const aCellInfo = [];
90
- const iMinutesInOneHours = 60;
91
-
92
- let iCurrentPointInMinutes = 0;
93
- let index = 0;
94
-
95
- while (iCurrentPointInMinutes < iMinutesInOneHours) {
96
- const oNonWorkingPart = aNonWorkingForCurrentHours[index];
97
- const oNonWorkingPartDate = oNonWorkingPart?.getStartDate();
98
-
99
- if (!oNonWorkingPart) {
100
- const iCurrentDuration = iMinutesInOneHours - iCurrentPointInMinutes;
101
- aCellInfo.push({
102
- type: PERIOD_TYPE.WORKING_PERIOD,
103
- duration: iCurrentDuration
104
- });
105
-
106
- return aCellInfo;
271
+ iDuration = iStartTimeInMin - iCurrentPointInMinutes;
272
+ aCellInfo.push({
273
+ type: PERIOD_TYPE.WORKING_PERIOD,
274
+ duration: iDuration
275
+ });
276
+ }
277
+
278
+ iCurrentPointInMinutes += iDuration;
279
+ }
280
+
281
+ return aCellInfo;
282
+ };
283
+
284
+
285
+ /**
286
+ * Gets all occurrence dates within a given range.
287
+ * PERFORMANCE: Pre-calculates all dates instead of checking each individually.
288
+ * @param {Date} oRangeStart - Start of range
289
+ * @param {Date} oRangeEnd - End of range
290
+ * @returns {Array<Date>} Array of occurrence dates
291
+ * @private
292
+ */
293
+ RecurrenceUtils.getOccurrencesInRange = function(oRangeStart, oRangeEnd) {
294
+ const aOccurrences = [];
295
+ const oRecurrenceStart = this.getStartDate();
296
+ const oRecurrenceEnd = this.getRecurrenceEndDate();
297
+
298
+ // Early exit if ranges don't overlap (normalise to date-only for comparison)
299
+ const oRangeEndNorm = UI5Date.getInstance(oRangeEnd);
300
+ oRangeEndNorm.setHours(23, 59, 59, 999);
301
+ const oRecurrenceStartNorm = UI5Date.getInstance(oRecurrenceStart);
302
+ oRecurrenceStartNorm.setHours(0, 0, 0, 0);
303
+ if (oRangeEndNorm < oRecurrenceStartNorm || (oRecurrenceEnd && oRangeStart > oRecurrenceEnd)) {
304
+ return [];
305
+ }
306
+
307
+ // Find first occurrence in range
308
+ const oFirstOccurrence = RecurrenceUtils._findFirstOccurrenceInRange.call(this, oRangeStart, oRecurrenceStart);
309
+ if (!oFirstOccurrence) {
310
+ return [];
311
+ }
312
+
313
+ let oCurrentDate = oFirstOccurrence;
314
+ const sType = this.getRecurrenceType();
315
+ const iPattern = this.getRecurrencePattern();
316
+ const oEffectiveEnd = oRecurrenceEnd && oRecurrenceEnd < oRangeEnd ? oRecurrenceEnd : oRangeEnd;
317
+
318
+ // Normalize effective end to end-of-day so that the last day is inclusive
319
+ const oEffectiveEndNormalized = UI5Date.getInstance(oEffectiveEnd);
320
+ oEffectiveEndNormalized.setHours(23, 59, 59, 999);
321
+
322
+ // For Monthly/Yearly DayOfMonth rules, track the canonical target day so setMonth
323
+ // rollover (e.g. Jan 31 → Feb 28) doesn't corrupt subsequent jumps.
324
+ // For DayOfWeek rules, iCanonicalDay stays null → scan mode (advance 1 day at a time)
325
+ // so hasOccurrenceOnDate can identify the correct weekday occurrence in each cycle.
326
+ let iCanonicalDay = null;
327
+ if (sType === RecurrenceType.Monthly || sType === RecurrenceType.Yearly) {
328
+ const oRule = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
329
+ const bDayOfWeekRule = oRule?.getType() === RecurrenceRuleType.DayOfWeek;
330
+ if (!bDayOfWeekRule) {
331
+ // DayOfMonth: use explicit day or fall back to start date day
332
+ if (oRule?.getDayOfMonth()) {
333
+ iCanonicalDay = oRule.getDayOfMonth();
334
+ } else {
335
+ iCanonicalDay = oRecurrenceStart.getDate();
107
336
  }
337
+ }
338
+ // DayOfWeek: iCanonicalDay stays null → scan mode
339
+ }
340
+
341
+ // Ensure the iteration cursor starts no earlier than the requested range start
342
+ // (for Weekly the candidate may point to the start of an active cycle before oRangeStart)
343
+ if (oCurrentDate < oRangeStart) {
344
+ const oRangeStartNorm = UI5Date.getInstance(oRangeStart);
345
+ oRangeStartNorm.setHours(0, 0, 0, 0);
346
+ oCurrentDate = oRangeStartNorm;
347
+ }
348
+
349
+ // Collect all occurrences
350
+ while (oCurrentDate <= oEffectiveEndNormalized) {
351
+ if (RecurrenceUtils.hasOccurrenceOnDate.call(this, oCurrentDate)) {
352
+ aOccurrences.push(UI5Date.getInstance(oCurrentDate));
353
+ }
108
354
 
109
- let iDuration = this.calculateDurationInCell(oNonWorkingPart, oCellStartDate, iCurrentPointInMinutes);
110
- const iStartTimeInMin = oNonWorkingPartDate.getMinutes();
355
+ // Jump to next potential occurrence
356
+ oCurrentDate = RecurrenceUtils._getNextPotentialOccurrence(oCurrentDate, sType, iPattern, iCanonicalDay);
357
+ }
111
358
 
112
- if (iDuration > iMinutesInOneHours && oNonWorkingPartDate.getHours() < oCellStartDate.getHours()) {
113
- aCellInfo.push({
114
- type: PERIOD_TYPE.NON_WORKING_PERIOD,
115
- duration: iMinutesInOneHours
116
- });
359
+ return aOccurrences;
360
+ };
117
361
 
118
- return aCellInfo;
362
+ /**
363
+ * Finds first occurrence in range (skip irrelevant dates)
364
+ * @private
365
+ */
366
+ RecurrenceUtils._findFirstOccurrenceInRange = function(oRangeStart, oRecurrenceStart) {
367
+ if (oRangeStart <= oRecurrenceStart) {
368
+ const oRuleInit = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
369
+ const sTypeInit = this.getRecurrenceType();
370
+ const bDayOfMonthInit = oRuleInit?.getType() === RecurrenceRuleType.DayOfMonth &&
371
+ oRuleInit?.getDayOfMonth();
372
+ if (bDayOfMonthInit && (sTypeInit === RecurrenceType.Monthly || sTypeInit === RecurrenceType.Yearly)) {
373
+ const oResult = UI5Date.getInstance(oRecurrenceStart);
374
+ oResult.setDate(1); // prevent month rollover
375
+ if (sTypeInit === RecurrenceType.Yearly) {
376
+ const iRuleMonth = oRuleInit.getMonth() >= 0 ? oRuleInit.getMonth() : oRecurrenceStart.getMonth();
377
+ oResult.setMonth(iRuleMonth);
119
378
  }
379
+ const iDays = UI5Date.getInstance(oResult.getFullYear(), oResult.getMonth() + 1, 0).getDate();
380
+ oResult.setDate(Math.min(oRuleInit.getDayOfMonth(), iDays));
381
+ // For Yearly: if the computed first occurrence falls before the recurrence start
382
+ // (e.g. rule says Jan 12 but startDate is May 4 of the same year), advance one year.
383
+ if (sTypeInit === RecurrenceType.Yearly) {
384
+ const oRecStartNorm = UI5Date.getInstance(oRecurrenceStart);
385
+ oRecStartNorm.setHours(0, 0, 0, 0);
386
+ if (oResult < oRecStartNorm) {
387
+ oResult.setDate(1);
388
+ oResult.setFullYear(oResult.getFullYear() + this.getRecurrencePattern());
389
+ const iDaysNext = UI5Date.getInstance(oResult.getFullYear(), oResult.getMonth() + 1, 0).getDate();
390
+ oResult.setDate(Math.min(oRuleInit.getDayOfMonth(), iDaysNext));
391
+ }
392
+ }
393
+ return oResult;
394
+ }
395
+ return UI5Date.getInstance(oRecurrenceStart);
396
+ }
397
+
398
+ const sType = this.getRecurrenceType();
399
+ const iPattern = this.getRecurrencePattern();
400
+ let oCandidate = UI5Date.getInstance(oRecurrenceStart);
120
401
 
121
- if (iStartTimeInMin === iCurrentPointInMinutes || oNonWorkingPartDate.getHours() < oCellStartDate.getHours()) {
122
- aCellInfo.push({
123
- type: PERIOD_TYPE.NON_WORKING_PERIOD,
124
- duration: iDuration
125
- });
126
- index++;
402
+ switch (sType) {
403
+ case RecurrenceType.Daily: {
404
+ const iDaysDiff = (Date.UTC(oRangeStart.getFullYear(), oRangeStart.getMonth(), oRangeStart.getDate()) -
405
+ Date.UTC(oRecurrenceStart.getFullYear(), oRecurrenceStart.getMonth(), oRecurrenceStart.getDate())) / TIME_PERIOD.ONE_DAY_IN_MS;
406
+ const iCycles = Math.ceil(iDaysDiff / iPattern);
407
+ oCandidate.setDate(oCandidate.getDate() + (iCycles * iPattern));
408
+ break;
409
+ }
410
+
411
+ case RecurrenceType.Weekly: {
412
+ // Align start date to its ISO Monday and do the same for the range start,
413
+ // then compute how many ISO calendar weeks separate them.
414
+ const iStartDay = oRecurrenceStart.getDay();
415
+ const iStartToMon = iStartDay === 0 ? 6 : iStartDay - 1;
416
+ const oStartMon = UI5Date.getInstance(oRecurrenceStart);
417
+ oStartMon.setDate(oStartMon.getDate() - iStartToMon);
418
+ oStartMon.setHours(0, 0, 0, 0);
419
+ const iRangeDay = oRangeStart.getDay();
420
+ const iRangeToMon = iRangeDay === 0 ? 6 : iRangeDay - 1;
421
+ const oRangeMon = UI5Date.getInstance(oRangeStart);
422
+ oRangeMon.setDate(oRangeMon.getDate() - iRangeToMon);
423
+ oRangeMon.setHours(0, 0, 0, 0);
424
+ const iISOWeeksDiff = Math.round((oRangeMon - oStartMon) / TIME_PERIOD.ONE_WEEK_IN_MS);
425
+ const iActiveCycleOffset = Math.floor(iISOWeeksDiff / iPattern) * iPattern;
426
+ // Position candidate at the Monday of the last active cycle before range start
427
+ oCandidate = UI5Date.getInstance(oStartMon);
428
+ oCandidate.setDate(oCandidate.getDate() + (iActiveCycleOffset * 7));
429
+ // Restore original time-of-day from recurrence start
430
+ oCandidate.setHours(oRecurrenceStart.getHours(), oRecurrenceStart.getMinutes(),
431
+ oRecurrenceStart.getSeconds(), oRecurrenceStart.getMilliseconds());
432
+ break;
433
+ }
434
+
435
+ case RecurrenceType.Monthly: {
436
+ const iMonthsDiff = (oRangeStart.getFullYear() - oRecurrenceStart.getFullYear()) * 12 +
437
+ (oRangeStart.getMonth() - oRecurrenceStart.getMonth());
438
+ const iCyclesMonths = Math.floor(iMonthsDiff / iPattern);
439
+ // Use DayOfMonth rule's day if present; fall back to the start date's day
440
+ const oRuleM = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
441
+ const bDayOfWeekM = oRuleM?.getType() === RecurrenceRuleType.DayOfWeek;
442
+ const iOriginalDay = (oRuleM?.getType() === RecurrenceRuleType.DayOfMonth &&
443
+ oRuleM?.getDayOfMonth())
444
+ ? oRuleM.getDayOfMonth()
445
+ : oRecurrenceStart.getDate();
446
+ const iTargetMonth = oRecurrenceStart.getMonth() + (iCyclesMonths * iPattern);
447
+ // Set to day 1 first to avoid rollover, then set the intended month and restore the day
448
+ oCandidate.setDate(1);
449
+ oCandidate.setMonth(iTargetMonth);
450
+ const iDaysInMonth = UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0).getDate();
451
+ oCandidate.setDate(Math.min(iOriginalDay, iDaysInMonth));
452
+ // Determine whether to advance to the next cycle.
453
+ // For DayOfMonth: advance whenever the specific target day is before the range start.
454
+ // For DayOfWeek (scan mode): the candidate is day 1 of the month; the actual occurrence
455
+ // might still be later in that month. Only advance if the ENTIRE month lies before the
456
+ // range start — otherwise leave the candidate as-is and let getOccurrencesInRange bump
457
+ // the cursor to rangeStart, where the day-by-day scan will find the occurrence.
458
+ const oMonthEnd = bDayOfWeekM
459
+ ? UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0)
460
+ : null;
461
+ const bAdvance = bDayOfWeekM
462
+ ? oMonthEnd < oRangeStart
463
+ : oCandidate < oRangeStart;
464
+ if (bAdvance) {
465
+ const iNextTargetMonth = iTargetMonth + iPattern;
466
+ // Re-init from base year so setMonth(iNextTargetMonth) overflow lands in the correct year.
467
+ oCandidate = UI5Date.getInstance(oRecurrenceStart);
468
+ oCandidate.setDate(1);
469
+ oCandidate.setMonth(iNextTargetMonth);
470
+ const iDaysInNextMonth = UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0).getDate();
471
+ oCandidate.setDate(Math.min(iOriginalDay, iDaysInNextMonth));
472
+ }
473
+ break;
474
+ }
475
+
476
+ case RecurrenceType.Yearly: {
477
+ const iYearsDiff = oRangeStart.getFullYear() - oRecurrenceStart.getFullYear();
478
+ const iCyclesYears = Math.floor(iYearsDiff / iPattern);
479
+ const oRuleY = this.getRecurrenceRule ? this.getRecurrenceRule() : null;
480
+ const bDayOfMonthY = oRuleY?.getType() === RecurrenceRuleType.DayOfMonth &&
481
+ oRuleY?.getDayOfMonth();
482
+ const bDayOfWeekY = oRuleY?.getType() === RecurrenceRuleType.DayOfWeek;
483
+
484
+ if (bDayOfMonthY) {
485
+ const iExpectedMonth = oRuleY.getMonth() >= 0 ? oRuleY.getMonth() : oRecurrenceStart.getMonth();
486
+ const iTargetYear = oRecurrenceStart.getFullYear() + (iCyclesYears * iPattern);
487
+ oCandidate.setDate(1);
488
+ oCandidate.setFullYear(iTargetYear);
489
+ oCandidate.setMonth(iExpectedMonth);
490
+ const iDaysInMonth = UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0).getDate();
491
+ oCandidate.setDate(Math.min(oRuleY.getDayOfMonth(), iDaysInMonth));
492
+ // Advance one year if candidate is still before the range start
493
+ if (oCandidate < oRangeStart) {
494
+ oCandidate.setDate(1);
495
+ oCandidate.setFullYear(oCandidate.getFullYear() + iPattern);
496
+ const iDaysInNextYear = UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0).getDate();
497
+ oCandidate.setDate(Math.min(oRuleY.getDayOfMonth(), iDaysInNextYear));
498
+ }
499
+ } else if (bDayOfWeekY) {
500
+ // For DayOfWeek (scan mode): position candidate at day 1 of the rule's target
501
+ // month in the target year — not the start date's month. Only advance one year
502
+ // if the entire target month lies before the range start.
503
+ const iExpectedMonthY = oRuleY.getMonth() >= 0 ? oRuleY.getMonth() : oRecurrenceStart.getMonth();
504
+ oCandidate.setDate(1);
505
+ oCandidate.setFullYear(oCandidate.getFullYear() + (iCyclesYears * iPattern));
506
+ oCandidate.setMonth(iExpectedMonthY);
507
+ const oMonthEndY = UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0);
508
+ if (oMonthEndY < oRangeStart) {
509
+ oCandidate.setDate(1);
510
+ oCandidate.setFullYear(oCandidate.getFullYear() + iPattern);
511
+ oCandidate.setMonth(iExpectedMonthY);
512
+ }
127
513
  } else {
128
- iDuration = iStartTimeInMin - iCurrentPointInMinutes;
129
- aCellInfo.push({
130
- type: PERIOD_TYPE.WORKING_PERIOD,
131
- duration: iDuration
132
- });
514
+ // setDate(1) prevents Feb 29 → Mar 1 overflow when the target year is not a leap year.
515
+ oCandidate.setDate(1);
516
+ oCandidate.setFullYear(oCandidate.getFullYear() + (iCyclesYears * iPattern));
517
+ const iExpectedMonthFallback = oCandidate.getMonth();
518
+ const iExpectedDayFallback = oRecurrenceStart.getDate();
519
+ const iDaysFallback = UI5Date.getInstance(oCandidate.getFullYear(), iExpectedMonthFallback + 1, 0).getDate();
520
+ oCandidate.setDate(Math.min(iExpectedDayFallback, iDaysFallback));
521
+ // Advance one more cycle if candidate is still before range start
522
+ // (e.g. startDate = Feb 29, range starts Mar 1 of same year → candidate lands on
523
+ // Feb 28 of that year, which is before the range; without advancing,
524
+ // _getNextPotentialOccurrence would stay in March every subsequent year).
525
+ if (oCandidate < oRangeStart) {
526
+ oCandidate.setDate(1);
527
+ oCandidate.setFullYear(oCandidate.getFullYear() + iPattern);
528
+ const iDaysNext = UI5Date.getInstance(oCandidate.getFullYear(), oCandidate.getMonth() + 1, 0).getDate();
529
+ oCandidate.setDate(Math.min(iExpectedDayFallback, iDaysNext));
530
+ }
133
531
  }
532
+ break;
533
+ }
534
+ }
134
535
 
135
- iCurrentPointInMinutes += iDuration;
536
+ return oCandidate;
537
+ };
538
+
539
+ /**
540
+ * Gets next potential occurrence date (jump by pattern interval)
541
+ * @private
542
+ */
543
+ RecurrenceUtils._getNextPotentialOccurrence = function(oDate, sType, iPattern, iCanonicalDay) {
544
+ const oNext = UI5Date.getInstance(oDate);
545
+
546
+ switch (sType) {
547
+ case RecurrenceType.Daily:
548
+ oNext.setDate(oNext.getDate() + iPattern);
549
+ break;
550
+ case RecurrenceType.Weekly:
551
+ oNext.setDate(oNext.getDate() + 1); // Check next day (due to recurrenceDay array)
552
+ break;
553
+ case RecurrenceType.Monthly: {
554
+ if (iCanonicalDay === null) {
555
+ // DayOfWeek scan mode: advance one day at a time
556
+ oNext.setDate(oNext.getDate() + 1);
557
+ } else {
558
+ // DayOfMonth: jump to target day in next cycle month (avoid setMonth rollover)
559
+ oNext.setDate(1);
560
+ oNext.setMonth(oNext.getMonth() + iPattern);
561
+ const iDays = UI5Date.getInstance(oNext.getFullYear(), oNext.getMonth() + 1, 0).getDate();
562
+ oNext.setDate(Math.min(iCanonicalDay, iDays));
563
+ }
564
+ break;
136
565
  }
566
+ case RecurrenceType.Yearly:
567
+ if (iCanonicalDay === null) {
568
+ // DayOfWeek scan mode: advance one day at a time
569
+ oNext.setDate(oNext.getDate() + 1);
570
+ } else {
571
+ // setDate(1) first to prevent Feb 29 → Mar 1 overflow in non-leap target years.
572
+ oNext.setDate(1);
573
+ oNext.setFullYear(oNext.getFullYear() + iPattern);
574
+ const iDaysY = UI5Date.getInstance(oNext.getFullYear(), oNext.getMonth() + 1, 0).getDate();
575
+ oNext.setDate(Math.min(iCanonicalDay, iDaysY));
576
+ }
577
+ break;
578
+ }
579
+
580
+ return oNext;
581
+ };
582
+
583
+ /**
584
+ * Initializes the occurrence and range caches on the calling object.
585
+ * @this {sap.ui.unified.RecurringCalendarAppointment|sap.ui.unified.RecurringNonWorkingPeriod}
586
+ * @private
587
+ */
588
+ RecurrenceUtils.initCache = function() {
589
+ this._occurrenceCache = new Map();
590
+ this._rangeCache = new Map();
591
+ };
592
+
593
+ /**
594
+ * Clears the occurrence and range caches on the calling object.
595
+ * @this {sap.ui.unified.RecurringCalendarAppointment|sap.ui.unified.RecurringNonWorkingPeriod}
596
+ * @private
597
+ */
598
+ RecurrenceUtils.invalidateCache = function() {
599
+ if (this._occurrenceCache) {
600
+ this._occurrenceCache.clear();
601
+ }
602
+ if (this._rangeCache) {
603
+ this._rangeCache.clear();
604
+ }
605
+ };
606
+
607
+ /**
608
+ * Returns whether an occurrence exists on the given date, using a per-instance cache.
609
+ * @this {sap.ui.unified.RecurringCalendarAppointment|sap.ui.unified.RecurringNonWorkingPeriod}
610
+ * @param {Date|module:sap/ui/core/date/UI5Date} oDate - Date to check
611
+ * @returns {boolean}
612
+ * @private
613
+ */
614
+ RecurrenceUtils.hasOccurrenceOnDateCached = function(oDate) {
615
+ const sCacheKey = `${oDate.getFullYear()}-${oDate.getMonth()}-${oDate.getDate()}`;
616
+
617
+ if (!this._occurrenceCache) {
618
+ RecurrenceUtils.initCache.call(this);
619
+ }
620
+
621
+ if (this._occurrenceCache.has(sCacheKey)) {
622
+ return this._occurrenceCache.get(sCacheKey);
623
+ }
624
+
625
+ const bResult = RecurrenceUtils.hasOccurrenceOnDate.call(this, oDate);
626
+
627
+ if (this._occurrenceCache.size > 365) {
628
+ this._occurrenceCache.clear();
629
+ }
630
+
631
+ this._occurrenceCache.set(sCacheKey, bResult);
632
+ return bResult;
633
+ };
634
+
635
+ /**
636
+ * Builds a string key for a date range.
637
+ * @param {Date|module:sap/ui/core/date/UI5Date} oStartDate
638
+ * @param {Date|module:sap/ui/core/date/UI5Date} oEndDate
639
+ * @returns {string}
640
+ * @private
641
+ */
642
+ RecurrenceUtils._getRangeKey = function(oStartDate, oEndDate) {
643
+ const sStart = `${oStartDate.getFullYear()}-${oStartDate.getMonth() + 1}-${oStartDate.getDate()}`;
644
+ const sEnd = `${oEndDate.getFullYear()}-${oEndDate.getMonth() + 1}-${oEndDate.getDate()}`;
645
+ return `${sStart}|${sEnd}`;
646
+ };
647
+
648
+ /**
649
+ * Returns cached occurrences for a date range, or null if not cached.
650
+ * @this {sap.ui.unified.RecurringCalendarAppointment|sap.ui.unified.RecurringNonWorkingPeriod}
651
+ * @param {Date|module:sap/ui/core/date/UI5Date} oStartDate
652
+ * @param {Date|module:sap/ui/core/date/UI5Date} oEndDate
653
+ * @returns {Array|null}
654
+ * @private
655
+ */
656
+ RecurrenceUtils.getCachedOccurrences = function(oStartDate, oEndDate) {
657
+ if (!this._rangeCache) {
658
+ RecurrenceUtils.initCache.call(this);
659
+ }
660
+ return this._rangeCache.get(RecurrenceUtils._getRangeKey(oStartDate, oEndDate)) || null;
661
+ };
137
662
 
138
- return aCellInfo;
139
- };
663
+ /**
664
+ * Stores occurrences for a date range in the cache.
665
+ * @this {sap.ui.unified.RecurringCalendarAppointment|sap.ui.unified.RecurringNonWorkingPeriod}
666
+ * @param {Date|module:sap/ui/core/date/UI5Date} oStartDate
667
+ * @param {Date|module:sap/ui/core/date/UI5Date} oEndDate
668
+ * @param {Array} aOccurrences
669
+ * @private
670
+ */
671
+ RecurrenceUtils.setCachedOccurrences = function(oStartDate, oEndDate, aOccurrences) {
672
+ if (!this._rangeCache) {
673
+ RecurrenceUtils.initCache.call(this);
674
+ }
675
+ if (this._rangeCache.size > 12) {
676
+ this._rangeCache.delete(this._rangeCache.keys().next().value);
677
+ }
678
+ this._rangeCache.set(RecurrenceUtils._getRangeKey(oStartDate, oEndDate), aOccurrences);
679
+ };
140
680
 
141
- return RecurrenceUtils;
681
+ return RecurrenceUtils;
142
682
 
143
- }, /* bExport= */ true);
683
+ }, /* bExport= */ true);