@primeui/scheduler-core 0.0.1-alpha.1
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/LICENSE +23 -0
- package/README.md +1 -0
- package/dist/calendar/index.d.mts +57 -0
- package/dist/calendar/index.mjs +1 -0
- package/dist/chunk-2B3YLWHA.mjs +196 -0
- package/dist/chunk-2THQAZ26.mjs +1669 -0
- package/dist/chunk-5KORIWDT.mjs +41 -0
- package/dist/chunk-5N4ZOBJV.mjs +866 -0
- package/dist/chunk-6OZAPQZ5.mjs +229 -0
- package/dist/chunk-6PK5WSKT.mjs +369 -0
- package/dist/chunk-6VYWVIGM.mjs +1170 -0
- package/dist/chunk-AAVM7UCG.mjs +100 -0
- package/dist/chunk-C7ADJGNV.mjs +157 -0
- package/dist/chunk-DYW6WUHE.mjs +277 -0
- package/dist/chunk-F5W5HD7S.mjs +285 -0
- package/dist/chunk-FIBAZFC4.mjs +871 -0
- package/dist/chunk-HPC5B3AR.mjs +558 -0
- package/dist/chunk-KQGRXTP5.mjs +650 -0
- package/dist/chunk-NMX4BW42.mjs +672 -0
- package/dist/chunk-NX46LPLF.mjs +440 -0
- package/dist/chunk-NZGJN7HG.mjs +314 -0
- package/dist/chunk-QDMZBJDV.mjs +251 -0
- package/dist/chunk-QR2SVYAD.mjs +1144 -0
- package/dist/chunk-SYJ5O4KH.mjs +136 -0
- package/dist/chunk-TNKJPFGI.mjs +569 -0
- package/dist/chunk-UMAMDBU4.mjs +1 -0
- package/dist/chunk-W2SJW3QQ.mjs +3925 -0
- package/dist/chunk-WFUJWDST.mjs +352 -0
- package/dist/chunk-XUBQ2IQS.mjs +1 -0
- package/dist/chunk-ZUKUKGNK.mjs +613 -0
- package/dist/controllers/index.d.mts +384 -0
- package/dist/controllers/index.mjs +13 -0
- package/dist/date-D_CjQPmM.d.mts +74 -0
- package/dist/display-format-CLVvRt4I.d.mts +57 -0
- package/dist/event/index.d.mts +267 -0
- package/dist/event/index.mjs +8 -0
- package/dist/event-surface-_R_LHD95.d.mts +21 -0
- package/dist/event.positioning-BdzAVPk7.d.mts +51 -0
- package/dist/event.utils-QSNdd-3W.d.mts +35 -0
- package/dist/index.d.mts +1128 -0
- package/dist/index.mjs +1022 -0
- package/dist/interaction/index.d.mts +442 -0
- package/dist/interaction/index.mjs +9 -0
- package/dist/month/index.d.mts +104 -0
- package/dist/month/index.mjs +6 -0
- package/dist/overlay-BYM9B6nC.d.mts +64 -0
- package/dist/resource/index.d.mts +172 -0
- package/dist/resource/index.mjs +1 -0
- package/dist/selection-CO_98HdS.d.mts +56 -0
- package/dist/time-grid/index.d.mts +92 -0
- package/dist/time-grid/index.mjs +13 -0
- package/dist/timeline/index.d.mts +165 -0
- package/dist/timeline/index.mjs +6 -0
- package/dist/touch-BhsMWsjf.d.mts +69 -0
- package/dist/utils/index.d.mts +494 -0
- package/dist/utils/index.mjs +17 -0
- package/dist/views/index.d.mts +51 -0
- package/dist/views/index.mjs +8 -0
- package/dist/views/timeline/index.d.mts +37 -0
- package/dist/views/timeline/index.mjs +4 -0
- package/dist/year/index.d.mts +70 -0
- package/dist/year/index.mjs +6 -0
- package/package.json +58 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { startOfDay, timeStringToMinutes, addDays, minutesToPixels } from './chunk-WFUJWDST.mjs';
|
|
2
|
+
import { createSchedulerDateFormatter, formatTimeRange } from './chunk-TNKJPFGI.mjs';
|
|
3
|
+
|
|
4
|
+
// src/utils/appointment-slots.ts
|
|
5
|
+
var DEFAULT_APPOINTMENT_SLOTS_OPTIONS = {
|
|
6
|
+
enabled: false,
|
|
7
|
+
displayMode: "overlay",
|
|
8
|
+
showAvailability: true,
|
|
9
|
+
showCapacity: false,
|
|
10
|
+
slotDuration: 30,
|
|
11
|
+
autoGenerate: false
|
|
12
|
+
};
|
|
13
|
+
function normalizeAppointmentSlotsOptions(options) {
|
|
14
|
+
if (options === false || options === void 0) {
|
|
15
|
+
return { ...DEFAULT_APPOINTMENT_SLOTS_OPTIONS, enabled: false };
|
|
16
|
+
}
|
|
17
|
+
if (options === true) {
|
|
18
|
+
return { ...DEFAULT_APPOINTMENT_SLOTS_OPTIONS, enabled: true };
|
|
19
|
+
}
|
|
20
|
+
return { ...DEFAULT_APPOINTMENT_SLOTS_OPTIONS, ...options, enabled: options.enabled ?? true };
|
|
21
|
+
}
|
|
22
|
+
function generateAppointmentSlots(date, slotDuration, minTime, maxTime, resourceId) {
|
|
23
|
+
const slots = [];
|
|
24
|
+
const dayStart = startOfDay(date);
|
|
25
|
+
const startMinutes = timeStringToMinutes(minTime);
|
|
26
|
+
const endMinutes = timeStringToMinutes(maxTime);
|
|
27
|
+
let currentMinutes = startMinutes;
|
|
28
|
+
let slotIndex = 0;
|
|
29
|
+
while (currentMinutes < endMinutes) {
|
|
30
|
+
const slotStart = new Date(dayStart);
|
|
31
|
+
slotStart.setMinutes(currentMinutes);
|
|
32
|
+
const slotEnd = new Date(dayStart);
|
|
33
|
+
slotEnd.setMinutes(Math.min(currentMinutes + slotDuration, endMinutes));
|
|
34
|
+
const slot = {
|
|
35
|
+
id: `slot-${date.toISOString().split("T")[0]}-${slotIndex}${resourceId ? `-${resourceId}` : ""}`,
|
|
36
|
+
start: slotStart,
|
|
37
|
+
end: slotEnd,
|
|
38
|
+
status: "available",
|
|
39
|
+
resourceId
|
|
40
|
+
};
|
|
41
|
+
slots.push(slot);
|
|
42
|
+
currentMinutes += slotDuration;
|
|
43
|
+
slotIndex++;
|
|
44
|
+
}
|
|
45
|
+
return slots;
|
|
46
|
+
}
|
|
47
|
+
function getSlotsForDay(options, date, resource, minTime = "00:00", maxTime = "24:00") {
|
|
48
|
+
const dayStart = startOfDay(date);
|
|
49
|
+
const dayEnd = addDays(dayStart, 1);
|
|
50
|
+
if (options.generateSlots) {
|
|
51
|
+
return options.generateSlots(date, resource);
|
|
52
|
+
}
|
|
53
|
+
if (options.autoGenerate && options.slotDuration) {
|
|
54
|
+
return generateAppointmentSlots(date, options.slotDuration, minTime, maxTime, resource?.id);
|
|
55
|
+
}
|
|
56
|
+
if (options.slots) {
|
|
57
|
+
return options.slots.filter((slot) => {
|
|
58
|
+
const slotStart = slot.start instanceof Date ? slot.start : new Date(slot.start);
|
|
59
|
+
const slotEnd = slot.end instanceof Date ? slot.end : new Date(slot.end);
|
|
60
|
+
const isInDay = slotStart < dayEnd && slotEnd > dayStart;
|
|
61
|
+
if (resource) {
|
|
62
|
+
return isInDay && (slot.resourceId === resource.id || slot.resourceId === void 0);
|
|
63
|
+
}
|
|
64
|
+
return isInDay;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
function getSlotsForTimeRange(options, startDate, endDate, resource, minTime = "00:00", maxTime = "24:00") {
|
|
70
|
+
const slots = [];
|
|
71
|
+
let currentDate = startOfDay(startDate);
|
|
72
|
+
const rangeEnd = startOfDay(endDate);
|
|
73
|
+
while (currentDate <= rangeEnd) {
|
|
74
|
+
const daySlots = getSlotsForDay(options, currentDate, resource, minTime, maxTime);
|
|
75
|
+
slots.push(...daySlots);
|
|
76
|
+
currentDate = addDays(currentDate, 1);
|
|
77
|
+
}
|
|
78
|
+
return slots;
|
|
79
|
+
}
|
|
80
|
+
function getEventDates(event) {
|
|
81
|
+
const start = typeof event.start === "string" ? new Date(event.start) : new Date(event.start);
|
|
82
|
+
const end = event.end ? typeof event.end === "string" ? new Date(event.end) : new Date(event.end) : new Date(start.getTime() + (event.duration ?? 30) * 60 * 1e3);
|
|
83
|
+
return { start, end };
|
|
84
|
+
}
|
|
85
|
+
function updateSlotAvailability(slot, events) {
|
|
86
|
+
const slotStart = slot.start instanceof Date ? slot.start : new Date(slot.start);
|
|
87
|
+
const slotEnd = slot.end instanceof Date ? slot.end : new Date(slot.end);
|
|
88
|
+
const overlappingEvents = events.filter((event) => {
|
|
89
|
+
if (slot.resourceId !== void 0) {
|
|
90
|
+
const eventResourceId = event.resourceId ?? event.resourceIds?.[0];
|
|
91
|
+
if (eventResourceId !== slot.resourceId) return false;
|
|
92
|
+
}
|
|
93
|
+
const { start: eventStart, end: eventEnd } = getEventDates(event);
|
|
94
|
+
return eventStart < slotEnd && eventEnd > slotStart;
|
|
95
|
+
});
|
|
96
|
+
const bookedCount = overlappingEvents.length;
|
|
97
|
+
const capacity = slot.capacity ?? 1;
|
|
98
|
+
let status = slot.status;
|
|
99
|
+
if (slot.status !== "blocked") {
|
|
100
|
+
if (bookedCount >= capacity) {
|
|
101
|
+
status = "booked";
|
|
102
|
+
} else if (bookedCount > 0) {
|
|
103
|
+
status = "tentative";
|
|
104
|
+
} else {
|
|
105
|
+
status = "available";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...slot,
|
|
110
|
+
bookedCount,
|
|
111
|
+
status
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function updateSlotsAvailability(slots, events) {
|
|
115
|
+
return slots.map((slot) => updateSlotAvailability(slot, events));
|
|
116
|
+
}
|
|
117
|
+
function canBookSlot(slot) {
|
|
118
|
+
if (slot.status === "blocked" || slot.status === "booked") {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (slot.capacity !== void 0 && slot.bookedCount !== void 0) {
|
|
122
|
+
return slot.bookedCount < slot.capacity;
|
|
123
|
+
}
|
|
124
|
+
return slot.status === "available" || slot.status === "tentative";
|
|
125
|
+
}
|
|
126
|
+
function getSlotAtTime(slots, date, resourceId) {
|
|
127
|
+
return slots.find((slot) => {
|
|
128
|
+
const slotStart = slot.start instanceof Date ? slot.start : new Date(slot.start);
|
|
129
|
+
const slotEnd = slot.end instanceof Date ? slot.end : new Date(slot.end);
|
|
130
|
+
const isInTimeRange = date >= slotStart && date < slotEnd;
|
|
131
|
+
if (resourceId !== void 0 && slot.resourceId !== void 0) {
|
|
132
|
+
return isInTimeRange && slot.resourceId === resourceId;
|
|
133
|
+
}
|
|
134
|
+
return isInTimeRange;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function getSlotsInTimeRange(slots, start, end, resourceId) {
|
|
138
|
+
return slots.filter((slot) => {
|
|
139
|
+
const slotStart = slot.start instanceof Date ? slot.start : new Date(slot.start);
|
|
140
|
+
const slotEnd = slot.end instanceof Date ? slot.end : new Date(slot.end);
|
|
141
|
+
const overlaps = slotStart < end && slotEnd > start;
|
|
142
|
+
if (resourceId !== void 0 && slot.resourceId !== void 0) {
|
|
143
|
+
return overlaps && slot.resourceId === resourceId;
|
|
144
|
+
}
|
|
145
|
+
return overlaps;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function getAvailableSlots(slots) {
|
|
149
|
+
return slots.filter((slot) => slot.status === "available" || slot.status === "tentative");
|
|
150
|
+
}
|
|
151
|
+
function getSlotCapacityDisplay(slot) {
|
|
152
|
+
if (slot.capacity === void 0) return "";
|
|
153
|
+
const booked = slot.bookedCount ?? 0;
|
|
154
|
+
return `${booked}/${slot.capacity}`;
|
|
155
|
+
}
|
|
156
|
+
function getSlotById(slots, id) {
|
|
157
|
+
return slots.find((slot) => slot.id === id);
|
|
158
|
+
}
|
|
159
|
+
function createSlotFromTimeRange(start, end, resourceId, options) {
|
|
160
|
+
return {
|
|
161
|
+
id: `slot-${start.getTime()}-${end.getTime()}${resourceId ? `-${resourceId}` : ""}`,
|
|
162
|
+
start,
|
|
163
|
+
end,
|
|
164
|
+
resourceId,
|
|
165
|
+
status: "available",
|
|
166
|
+
...options
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function toSlotDate(value) {
|
|
170
|
+
return value instanceof Date ? value : new Date(value);
|
|
171
|
+
}
|
|
172
|
+
function getAppointmentSlotDisplayLabels(slot, options = {}) {
|
|
173
|
+
const start = toSlotDate(slot.start);
|
|
174
|
+
const end = toSlotDate(slot.end);
|
|
175
|
+
const formatter = createSchedulerDateFormatter(options);
|
|
176
|
+
const dateLabel = formatter.format(start, options.dateOptions ?? { weekday: "long", year: "numeric", month: "long", day: "numeric" });
|
|
177
|
+
const timeRangeLabel = formatTimeRange(start, end, formatter.config.locale, options.timeFormat, formatter.config.timeZone);
|
|
178
|
+
const summaryLabel = options.includeDate === false ? timeRangeLabel : `${dateLabel}, ${timeRangeLabel}`;
|
|
179
|
+
const ariaLabel = `${slot.status ?? "available"} appointment slot, ${summaryLabel}`;
|
|
180
|
+
return {
|
|
181
|
+
dateLabel,
|
|
182
|
+
timeRangeLabel,
|
|
183
|
+
summaryLabel,
|
|
184
|
+
ariaLabel
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function getAppointmentSlotOverlays(options, date, events, slotMinTime, slotMaxTime, slotDuration, slotHeight, resourceId) {
|
|
188
|
+
if (!options.enabled || !options.showAvailability) return [];
|
|
189
|
+
const resource = resourceId !== void 0 ? { id: resourceId, title: "" } : void 0;
|
|
190
|
+
let slots = getSlotsForDay(options, date, resource, slotMinTime, slotMaxTime);
|
|
191
|
+
if (resourceId !== void 0) {
|
|
192
|
+
slots = slots.filter((slot) => slot.resourceId === resourceId || slot.resourceId === void 0);
|
|
193
|
+
}
|
|
194
|
+
slots = updateSlotsAvailability(slots, events);
|
|
195
|
+
const slotMinMinutes = timeStringToMinutes(slotMinTime);
|
|
196
|
+
const slotMaxMinutes = timeStringToMinutes(slotMaxTime);
|
|
197
|
+
const overlays = [];
|
|
198
|
+
for (const slot of slots) {
|
|
199
|
+
const slotStart = slot.start instanceof Date ? slot.start : new Date(slot.start);
|
|
200
|
+
const slotEnd = slot.end instanceof Date ? slot.end : new Date(slot.end);
|
|
201
|
+
const slotStartMinutes = slotStart.getHours() * 60 + slotStart.getMinutes();
|
|
202
|
+
const slotEndMinutes = slotEnd.getHours() * 60 + slotEnd.getMinutes();
|
|
203
|
+
const clampedStart = Math.max(slotStartMinutes, slotMinMinutes);
|
|
204
|
+
const clampedEnd = Math.min(slotEndMinutes, slotMaxMinutes);
|
|
205
|
+
if (clampedStart >= clampedEnd) continue;
|
|
206
|
+
const top = minutesToPixels(clampedStart - slotMinMinutes, slotDuration, slotHeight);
|
|
207
|
+
const height = minutesToPixels(clampedEnd - clampedStart, slotDuration, slotHeight);
|
|
208
|
+
overlays.push({
|
|
209
|
+
slot,
|
|
210
|
+
top,
|
|
211
|
+
height,
|
|
212
|
+
status: slot.status,
|
|
213
|
+
capacityDisplay: options.showCapacity && slot.capacity !== void 0 ? getSlotCapacityDisplay(slot) : void 0,
|
|
214
|
+
displayMode: options.displayMode
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return overlays;
|
|
218
|
+
}
|
|
219
|
+
function getAppointmentSlotIndicators(options, date, events, slotMinTime, slotMaxTime, slotDuration, slotHeight, resourceId) {
|
|
220
|
+
if (!options.enabled || !options.showAvailability) return [];
|
|
221
|
+
const resource = resourceId !== void 0 ? { id: resourceId, title: "" } : void 0;
|
|
222
|
+
let slots = getSlotsForDay(options, date, resource, slotMinTime, slotMaxTime);
|
|
223
|
+
if (resourceId !== void 0) {
|
|
224
|
+
slots = slots.filter((slot) => slot.resourceId === resourceId || slot.resourceId === void 0);
|
|
225
|
+
}
|
|
226
|
+
slots = updateSlotsAvailability(slots, events);
|
|
227
|
+
const slotMinMinutes = timeStringToMinutes(slotMinTime);
|
|
228
|
+
const slotMaxMinutes = timeStringToMinutes(slotMaxTime);
|
|
229
|
+
const indicatorsByTimeSlot = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const slot of slots) {
|
|
231
|
+
const slotStart = slot.start instanceof Date ? slot.start : new Date(slot.start);
|
|
232
|
+
const slotStartMinutes = slotStart.getHours() * 60 + slotStart.getMinutes();
|
|
233
|
+
if (slotStartMinutes < slotMinMinutes || slotStartMinutes >= slotMaxMinutes) continue;
|
|
234
|
+
const timeSlotKey = Math.floor((slotStartMinutes - slotMinMinutes) / slotDuration) * slotDuration + slotMinMinutes;
|
|
235
|
+
if (!indicatorsByTimeSlot.has(timeSlotKey)) {
|
|
236
|
+
indicatorsByTimeSlot.set(timeSlotKey, {
|
|
237
|
+
timeSlotMinutes: timeSlotKey,
|
|
238
|
+
top: minutesToPixels(timeSlotKey - slotMinMinutes, slotDuration, slotHeight) + slotHeight / 2,
|
|
239
|
+
slots: [],
|
|
240
|
+
availableCount: 0,
|
|
241
|
+
bookedCount: 0,
|
|
242
|
+
blockedCount: 0,
|
|
243
|
+
tentativeCount: 0,
|
|
244
|
+
primaryStatus: "available"
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const indicator = indicatorsByTimeSlot.get(timeSlotKey);
|
|
248
|
+
indicator.slots.push(slot);
|
|
249
|
+
switch (slot.status) {
|
|
250
|
+
case "available":
|
|
251
|
+
indicator.availableCount++;
|
|
252
|
+
break;
|
|
253
|
+
case "booked":
|
|
254
|
+
indicator.bookedCount++;
|
|
255
|
+
break;
|
|
256
|
+
case "blocked":
|
|
257
|
+
indicator.blockedCount++;
|
|
258
|
+
break;
|
|
259
|
+
case "tentative":
|
|
260
|
+
indicator.tentativeCount++;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const indicator of indicatorsByTimeSlot.values()) {
|
|
265
|
+
if (indicator.availableCount > 0) {
|
|
266
|
+
indicator.primaryStatus = "available";
|
|
267
|
+
} else if (indicator.tentativeCount > 0) {
|
|
268
|
+
indicator.primaryStatus = "tentative";
|
|
269
|
+
} else if (indicator.blockedCount > 0) {
|
|
270
|
+
indicator.primaryStatus = "blocked";
|
|
271
|
+
} else {
|
|
272
|
+
indicator.primaryStatus = "booked";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return Array.from(indicatorsByTimeSlot.values()).sort((a, b) => a.timeSlotMinutes - b.timeSlotMinutes);
|
|
276
|
+
}
|
|
277
|
+
function getSlotOverlayClasses(overlay) {
|
|
278
|
+
const classes = ["p-scheduler-appointment-slot", `p-scheduler-appointment-slot-${overlay.status}`];
|
|
279
|
+
switch (overlay.displayMode) {
|
|
280
|
+
case "overlay":
|
|
281
|
+
classes.push("p-scheduler-appointment-slot-overlay");
|
|
282
|
+
break;
|
|
283
|
+
case "grid":
|
|
284
|
+
classes.push("p-scheduler-appointment-slot-grid");
|
|
285
|
+
break;
|
|
286
|
+
case "indicator":
|
|
287
|
+
classes.push("p-scheduler-appointment-slot-indicator");
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
return classes;
|
|
291
|
+
}
|
|
292
|
+
function getSlotOverlayStyle(overlay) {
|
|
293
|
+
const style = {
|
|
294
|
+
position: "absolute",
|
|
295
|
+
top: `${overlay.top}px`,
|
|
296
|
+
zIndex: "0"
|
|
297
|
+
};
|
|
298
|
+
switch (overlay.displayMode) {
|
|
299
|
+
case "overlay":
|
|
300
|
+
style.left = "2px";
|
|
301
|
+
style.right = "2px";
|
|
302
|
+
style.height = `${overlay.height}px`;
|
|
303
|
+
break;
|
|
304
|
+
case "grid":
|
|
305
|
+
style.left = "4px";
|
|
306
|
+
style.right = "4px";
|
|
307
|
+
style.height = `${Math.max(overlay.height - 4, 20)}px`;
|
|
308
|
+
style.marginTop = "2px";
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
return style;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export { DEFAULT_APPOINTMENT_SLOTS_OPTIONS, canBookSlot, createSlotFromTimeRange, generateAppointmentSlots, getAppointmentSlotDisplayLabels, getAppointmentSlotIndicators, getAppointmentSlotOverlays, getAvailableSlots, getSlotAtTime, getSlotById, getSlotCapacityDisplay, getSlotOverlayClasses, getSlotOverlayStyle, getSlotsForDay, getSlotsForTimeRange, getSlotsInTimeRange, normalizeAppointmentSlotsOptions, updateSlotAvailability, updateSlotsAvailability };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// src/utils/timezone.ts
|
|
2
|
+
var formatCache = /* @__PURE__ */ new Map();
|
|
3
|
+
function getFormatter(timezone, options = {}) {
|
|
4
|
+
const key = `${timezone}-${JSON.stringify(options)}`;
|
|
5
|
+
let formatter = formatCache.get(key);
|
|
6
|
+
if (!formatter) {
|
|
7
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
8
|
+
...options,
|
|
9
|
+
timeZone: timezone
|
|
10
|
+
});
|
|
11
|
+
formatCache.set(key, formatter);
|
|
12
|
+
}
|
|
13
|
+
return formatter;
|
|
14
|
+
}
|
|
15
|
+
function isValidTimezone(timezone) {
|
|
16
|
+
try {
|
|
17
|
+
Intl.DateTimeFormat(void 0, { timeZone: timezone });
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function getLocalTimezone() {
|
|
24
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
25
|
+
}
|
|
26
|
+
function getTimezoneOffset(date, timezone) {
|
|
27
|
+
const targetDate = new Date(date.toLocaleString("en-US", { timeZone: timezone }));
|
|
28
|
+
const utcDate = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
|
|
29
|
+
return (utcDate.getTime() - targetDate.getTime()) / (60 * 1e3);
|
|
30
|
+
}
|
|
31
|
+
function getTimezoneOffsetString(date, timezone) {
|
|
32
|
+
const formatter = getFormatter(timezone, {
|
|
33
|
+
timeZoneName: "shortOffset"
|
|
34
|
+
});
|
|
35
|
+
const parts = formatter.formatToParts(date);
|
|
36
|
+
const tzPart = parts.find((p) => p.type === "timeZoneName");
|
|
37
|
+
if (tzPart) {
|
|
38
|
+
const match = tzPart.value.match(/GMT([+-]\d{1,2}(?::\d{2})?)?/);
|
|
39
|
+
if (match) {
|
|
40
|
+
return match[1] || "+0";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const offset = getTimezoneOffset(date, timezone);
|
|
44
|
+
const sign = offset <= 0 ? "+" : "-";
|
|
45
|
+
const hours = Math.floor(Math.abs(offset) / 60);
|
|
46
|
+
const minutes = Math.abs(offset) % 60;
|
|
47
|
+
return `${sign}${hours}${minutes > 0 ? ":" + String(minutes).padStart(2, "0") : ""}`;
|
|
48
|
+
}
|
|
49
|
+
function getTimezoneAbbreviation(date, timezone) {
|
|
50
|
+
const formatter = getFormatter(timezone, {
|
|
51
|
+
timeZoneName: "short"
|
|
52
|
+
});
|
|
53
|
+
const parts = formatter.formatToParts(date);
|
|
54
|
+
const tzPart = parts.find((p) => p.type === "timeZoneName");
|
|
55
|
+
return tzPart?.value || timezone;
|
|
56
|
+
}
|
|
57
|
+
function getTimezoneName(date, timezone, style = "long") {
|
|
58
|
+
const formatter = getFormatter(timezone, {
|
|
59
|
+
timeZoneName: style
|
|
60
|
+
});
|
|
61
|
+
const parts = formatter.formatToParts(date);
|
|
62
|
+
const tzPart = parts.find((p) => p.type === "timeZoneName");
|
|
63
|
+
return tzPart?.value || timezone;
|
|
64
|
+
}
|
|
65
|
+
function getTimezoneInfo(date, timezone) {
|
|
66
|
+
return {
|
|
67
|
+
id: timezone,
|
|
68
|
+
offset: getTimezoneOffset(date, timezone),
|
|
69
|
+
offsetString: getTimezoneOffsetString(date, timezone),
|
|
70
|
+
name: getTimezoneName(date, timezone),
|
|
71
|
+
abbreviation: getTimezoneAbbreviation(date, timezone)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function formatInTimezone(date, timezone, options = {}) {
|
|
75
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
76
|
+
...options,
|
|
77
|
+
timeZone: timezone
|
|
78
|
+
}).format(date);
|
|
79
|
+
}
|
|
80
|
+
function formatTimeInTimezone(date, timezone, locale = "en") {
|
|
81
|
+
return new Intl.DateTimeFormat(locale, {
|
|
82
|
+
hour: "numeric",
|
|
83
|
+
minute: "2-digit",
|
|
84
|
+
timeZone: timezone
|
|
85
|
+
}).format(date);
|
|
86
|
+
}
|
|
87
|
+
function formatDateInTimezone(date, timezone, locale = "en") {
|
|
88
|
+
return new Intl.DateTimeFormat(locale, {
|
|
89
|
+
year: "numeric",
|
|
90
|
+
month: "short",
|
|
91
|
+
day: "numeric",
|
|
92
|
+
timeZone: timezone
|
|
93
|
+
}).format(date);
|
|
94
|
+
}
|
|
95
|
+
function formatDateTimeInTimezone(date, timezone, locale = "en") {
|
|
96
|
+
return new Intl.DateTimeFormat(locale, {
|
|
97
|
+
year: "numeric",
|
|
98
|
+
month: "short",
|
|
99
|
+
day: "numeric",
|
|
100
|
+
hour: "numeric",
|
|
101
|
+
minute: "2-digit",
|
|
102
|
+
timeZone: timezone
|
|
103
|
+
}).format(date);
|
|
104
|
+
}
|
|
105
|
+
function toTimezone(date, fromTimezone, toTimezone2) {
|
|
106
|
+
const fromOffset = getTimezoneOffset(date, fromTimezone);
|
|
107
|
+
const toOffset = getTimezoneOffset(date, toTimezone2);
|
|
108
|
+
const diff = (fromOffset - toOffset) * 60 * 1e3;
|
|
109
|
+
return new Date(date.getTime() + diff);
|
|
110
|
+
}
|
|
111
|
+
function toUTC(date, timezone) {
|
|
112
|
+
return toTimezone(date, timezone, "UTC");
|
|
113
|
+
}
|
|
114
|
+
function fromUTC(date, timezone) {
|
|
115
|
+
return toTimezone(date, "UTC", timezone);
|
|
116
|
+
}
|
|
117
|
+
function toLocalTimezone(date, fromTimezone) {
|
|
118
|
+
return toTimezone(date, fromTimezone, getLocalTimezone());
|
|
119
|
+
}
|
|
120
|
+
function fromLocalTimezone(date, targetTimezone) {
|
|
121
|
+
return toTimezone(date, getLocalTimezone(), targetTimezone);
|
|
122
|
+
}
|
|
123
|
+
function getDatePartsInTimezone(date, timezone) {
|
|
124
|
+
const formatter = getFormatter(timezone, {
|
|
125
|
+
year: "numeric",
|
|
126
|
+
month: "numeric",
|
|
127
|
+
day: "numeric",
|
|
128
|
+
hour: "numeric",
|
|
129
|
+
minute: "numeric",
|
|
130
|
+
second: "numeric",
|
|
131
|
+
weekday: "short",
|
|
132
|
+
hour12: false
|
|
133
|
+
});
|
|
134
|
+
const parts = formatter.formatToParts(date);
|
|
135
|
+
const getValue = (type) => {
|
|
136
|
+
const part = parts.find((p) => p.type === type);
|
|
137
|
+
return part ? parseInt(part.value, 10) : 0;
|
|
138
|
+
};
|
|
139
|
+
const weekdayPart = parts.find((p) => p.type === "weekday");
|
|
140
|
+
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
141
|
+
const dayOfWeek = weekdayPart ? weekdays.indexOf(weekdayPart.value) : date.getDay();
|
|
142
|
+
return {
|
|
143
|
+
year: getValue("year"),
|
|
144
|
+
month: getValue("month") - 1,
|
|
145
|
+
day: getValue("day"),
|
|
146
|
+
// Some ICU/timezone combinations report midnight as hour 24 with hour12:false; normalize to 0.
|
|
147
|
+
hour: getValue("hour") % 24,
|
|
148
|
+
minute: getValue("minute"),
|
|
149
|
+
second: getValue("second"),
|
|
150
|
+
dayOfWeek: dayOfWeek >= 0 ? dayOfWeek : 0
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function createDateInTimezone(timezone, year, month, day, hour = 0, minute = 0, second = 0) {
|
|
154
|
+
const utcGuess = Date.UTC(year, month, day, hour, minute, second);
|
|
155
|
+
const offset = getTimezoneOffset(new Date(utcGuess), timezone);
|
|
156
|
+
const adjusted = new Date(utcGuess + offset * 60 * 1e3);
|
|
157
|
+
const verifyOffset = getTimezoneOffset(adjusted, timezone);
|
|
158
|
+
if (verifyOffset !== offset) {
|
|
159
|
+
return new Date(utcGuess + verifyOffset * 60 * 1e3);
|
|
160
|
+
}
|
|
161
|
+
return adjusted;
|
|
162
|
+
}
|
|
163
|
+
function isSameDayInTimezone(d1, d2, timezone) {
|
|
164
|
+
const parts1 = getDatePartsInTimezone(d1, timezone);
|
|
165
|
+
const parts2 = getDatePartsInTimezone(d2, timezone);
|
|
166
|
+
return parts1.year === parts2.year && parts1.month === parts2.month && parts1.day === parts2.day;
|
|
167
|
+
}
|
|
168
|
+
function isTodayInTimezone(date, timezone) {
|
|
169
|
+
return isSameDayInTimezone(date, /* @__PURE__ */ new Date(), timezone);
|
|
170
|
+
}
|
|
171
|
+
function startOfDayInTimezone(date, timezone) {
|
|
172
|
+
const parts = getDatePartsInTimezone(date, timezone);
|
|
173
|
+
return createDateInTimezone(timezone, parts.year, parts.month, parts.day, 0, 0, 0);
|
|
174
|
+
}
|
|
175
|
+
function endOfDayInTimezone(date, timezone) {
|
|
176
|
+
const parts = getDatePartsInTimezone(date, timezone);
|
|
177
|
+
return createDateInTimezone(timezone, parts.year, parts.month, parts.day, 23, 59, 59);
|
|
178
|
+
}
|
|
179
|
+
function convertEventToDisplayTimezone(event, schedulerTimezone) {
|
|
180
|
+
const eventTz = event.timezone;
|
|
181
|
+
if (!eventTz || eventTz === schedulerTimezone) {
|
|
182
|
+
return event;
|
|
183
|
+
}
|
|
184
|
+
const newStart = toTimezone(event.start, eventTz, schedulerTimezone);
|
|
185
|
+
const newEnd = toTimezone(event.end, eventTz, schedulerTimezone);
|
|
186
|
+
const durationMinutes = Math.round((newEnd.getTime() - newStart.getTime()) / (1e3 * 60));
|
|
187
|
+
const startDay = new Date(newStart.getFullYear(), newStart.getMonth(), newStart.getDate());
|
|
188
|
+
const endDay = new Date(newEnd.getFullYear(), newEnd.getMonth(), newEnd.getDate());
|
|
189
|
+
const spanDays = Math.ceil((endDay.getTime() - startDay.getTime()) / (1e3 * 60 * 60 * 24)) + 1;
|
|
190
|
+
return {
|
|
191
|
+
...event,
|
|
192
|
+
start: newStart,
|
|
193
|
+
end: newEnd,
|
|
194
|
+
durationMinutes,
|
|
195
|
+
isMultiDay: event.isMultiDay !== void 0 ? spanDays > 1 || !!event.isMultiDay : void 0,
|
|
196
|
+
spanDays: event.spanDays !== void 0 ? spanDays : void 0
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function convertEventsToTimezone(events, schedulerTimezone) {
|
|
200
|
+
if (!schedulerTimezone) return events;
|
|
201
|
+
return events.map((event) => convertEventToDisplayTimezone(event, schedulerTimezone));
|
|
202
|
+
}
|
|
203
|
+
function getTimezoneDisplayLabel(timezone, displayTimezone) {
|
|
204
|
+
if (!timezone && !displayTimezone) return null;
|
|
205
|
+
const tz = displayTimezone || timezone;
|
|
206
|
+
if (!tz) return null;
|
|
207
|
+
if (tz === "UTC") return "UTC";
|
|
208
|
+
const now = /* @__PURE__ */ new Date();
|
|
209
|
+
return getTimezoneAbbreviation(now, tz);
|
|
210
|
+
}
|
|
211
|
+
var COMMON_TIMEZONES = [
|
|
212
|
+
"UTC",
|
|
213
|
+
"America/New_York",
|
|
214
|
+
"America/Chicago",
|
|
215
|
+
"America/Denver",
|
|
216
|
+
"America/Los_Angeles",
|
|
217
|
+
"America/Anchorage",
|
|
218
|
+
"Pacific/Honolulu",
|
|
219
|
+
"Europe/London",
|
|
220
|
+
"Europe/Paris",
|
|
221
|
+
"Europe/Berlin",
|
|
222
|
+
"Europe/Moscow",
|
|
223
|
+
"Asia/Dubai",
|
|
224
|
+
"Asia/Kolkata",
|
|
225
|
+
"Asia/Shanghai",
|
|
226
|
+
"Asia/Tokyo",
|
|
227
|
+
"Asia/Singapore",
|
|
228
|
+
"Australia/Sydney",
|
|
229
|
+
"Pacific/Auckland"
|
|
230
|
+
];
|
|
231
|
+
function getCommonTimezones() {
|
|
232
|
+
const now = /* @__PURE__ */ new Date();
|
|
233
|
+
return COMMON_TIMEZONES.map((tz) => getTimezoneInfo(now, tz));
|
|
234
|
+
}
|
|
235
|
+
function parseISOWithTimezone(isoString) {
|
|
236
|
+
const match = isoString.match(/^(.+?)(?:\[(.+?)\])?$/);
|
|
237
|
+
if (!match) {
|
|
238
|
+
return { date: new Date(isoString), timezone: null };
|
|
239
|
+
}
|
|
240
|
+
const dateString = match[1];
|
|
241
|
+
const timezone = match[2] || null;
|
|
242
|
+
return { date: new Date(dateString), timezone };
|
|
243
|
+
}
|
|
244
|
+
function toISOWithTimezone(date, timezone) {
|
|
245
|
+
const parts = getDatePartsInTimezone(date, timezone);
|
|
246
|
+
const offsetString = getTimezoneOffsetString(date, timezone);
|
|
247
|
+
const iso = `${parts.year}-${String(parts.month + 1).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}T${String(parts.hour).padStart(2, "0")}:${String(parts.minute).padStart(2, "0")}:${String(parts.second).padStart(2, "0")}${offsetString}[${timezone}]`;
|
|
248
|
+
return iso;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { COMMON_TIMEZONES, convertEventToDisplayTimezone, convertEventsToTimezone, createDateInTimezone, endOfDayInTimezone, formatDateInTimezone, formatDateTimeInTimezone, formatInTimezone, formatTimeInTimezone, fromLocalTimezone, fromUTC, getCommonTimezones, getDatePartsInTimezone, getLocalTimezone, getTimezoneAbbreviation, getTimezoneDisplayLabel, getTimezoneInfo, getTimezoneName, getTimezoneOffset, getTimezoneOffsetString, isSameDayInTimezone, isTodayInTimezone, isValidTimezone, parseISOWithTimezone, startOfDayInTimezone, toISOWithTimezone, toLocalTimezone, toTimezone, toUTC };
|