@openui5/sap.ui.unified 1.148.0 → 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.
- package/THIRDPARTY.txt +2 -2
- package/package.json +2 -2
- package/src/sap/ui/unified/.library +1 -1
- package/src/sap/ui/unified/Calendar.js +9 -4
- package/src/sap/ui/unified/CalendarAppointment.js +1 -1
- package/src/sap/ui/unified/CalendarDateInterval.js +1 -1
- package/src/sap/ui/unified/CalendarLegend.js +1 -1
- package/src/sap/ui/unified/CalendarLegendItem.js +1 -1
- package/src/sap/ui/unified/CalendarMonthInterval.js +1 -1
- package/src/sap/ui/unified/CalendarOneMonthInterval.js +1 -1
- package/src/sap/ui/unified/CalendarRow.js +1 -1
- package/src/sap/ui/unified/CalendarRowRenderer.js +23 -18
- package/src/sap/ui/unified/CalendarTimeInterval.js +10 -13
- package/src/sap/ui/unified/CalendarWeekInterval.js +1 -1
- package/src/sap/ui/unified/ColorPicker.js +1 -1
- package/src/sap/ui/unified/ColorPickerPopover.js +1 -1
- package/src/sap/ui/unified/ContentSwitcher.js +1 -1
- package/src/sap/ui/unified/Currency.js +1 -1
- package/src/sap/ui/unified/CurrencyRenderer.js +1 -1
- package/src/sap/ui/unified/DateRange.js +1 -1
- package/src/sap/ui/unified/DateTypeRange.js +1 -1
- package/src/sap/ui/unified/FileUploader.js +1 -1
- package/src/sap/ui/unified/FileUploaderParameter.js +1 -1
- package/src/sap/ui/unified/FileUploaderXHRSettings.js +1 -1
- package/src/sap/ui/unified/Menu.js +1 -1
- package/src/sap/ui/unified/MenuItem.js +1 -1
- package/src/sap/ui/unified/MenuItemBase.js +1 -1
- package/src/sap/ui/unified/MenuItemGroup.js +1 -1
- package/src/sap/ui/unified/MenuRenderer.js +1 -1
- package/src/sap/ui/unified/MenuTextFieldItem.js +1 -1
- package/src/sap/ui/unified/MonthlyRecurrenceRule.js +58 -0
- package/src/sap/ui/unified/NonWorkingPeriod.js +1 -1
- package/src/sap/ui/unified/RecurrenceRule.js +112 -0
- package/src/sap/ui/unified/RecurringCalendarAppointment.js +222 -0
- package/src/sap/ui/unified/RecurringNonWorkingPeriod.js +112 -6
- package/src/sap/ui/unified/Shell.js +1 -1
- package/src/sap/ui/unified/ShellHeadItem.js +1 -1
- package/src/sap/ui/unified/ShellHeadUserItem.js +1 -1
- package/src/sap/ui/unified/ShellLayout.js +1 -1
- package/src/sap/ui/unified/ShellOverlay.js +1 -1
- package/src/sap/ui/unified/SplitContainer.js +1 -1
- package/src/sap/ui/unified/TimeRange.js +1 -1
- package/src/sap/ui/unified/WeeklyRecurrenceRule.js +39 -0
- package/src/sap/ui/unified/YearlyRecurrenceRule.js +53 -0
- package/src/sap/ui/unified/calendar/DatesRow.js +1 -1
- package/src/sap/ui/unified/calendar/Header.js +14 -2
- package/src/sap/ui/unified/calendar/HeaderRenderer.js +124 -50
- package/src/sap/ui/unified/calendar/IndexPicker.js +1 -1
- package/src/sap/ui/unified/calendar/Month.js +1 -1
- package/src/sap/ui/unified/calendar/MonthPicker.js +1 -1
- package/src/sap/ui/unified/calendar/MonthsRow.js +1 -1
- package/src/sap/ui/unified/calendar/OneMonthDatesRow.js +1 -1
- package/src/sap/ui/unified/calendar/RecurrenceUtils.js +653 -113
- package/src/sap/ui/unified/calendar/TimesRow.js +1 -1
- package/src/sap/ui/unified/calendar/WeeksRow.js +1 -1
- package/src/sap/ui/unified/calendar/YearPicker.js +1 -1
- package/src/sap/ui/unified/calendar/YearRangePicker.js +1 -1
- package/src/sap/ui/unified/library.js +73 -2
- package/src/sap/ui/unified/themes/base/Calendar.less +76 -205
- package/src/sap/ui/unified/themes/base/FileUploader.less +2 -2
- package/src/sap/ui/unified/themes/sap_hcb/Calendar.less +3 -10
- 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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
203
|
+
RecurrenceUtils.calculateDurationInCell = function (oNonWorkingPart, oCellStartDate, iCurrentPointInMinutes){
|
|
204
|
+
const oNonWorkingPartDate = oNonWorkingPart.getStartDate();
|
|
205
|
+
const iMinutesInOneHour = 60;
|
|
206
|
+
let iDuration = oNonWorkingPart.getDurationInMinutes();
|
|
54
207
|
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
const
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
355
|
+
// Jump to next potential occurrence
|
|
356
|
+
oCurrentDate = RecurrenceUtils._getNextPotentialOccurrence(oCurrentDate, sType, iPattern, iCanonicalDay);
|
|
357
|
+
}
|
|
111
358
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
type: PERIOD_TYPE.NON_WORKING_PERIOD,
|
|
115
|
-
duration: iMinutesInOneHours
|
|
116
|
-
});
|
|
359
|
+
return aOccurrences;
|
|
360
|
+
};
|
|
117
361
|
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
681
|
+
return RecurrenceUtils;
|
|
142
682
|
|
|
143
|
-
|
|
683
|
+
}, /* bExport= */ true);
|