@multisystemsuite/timezone-engine-scheduler 1.0.0 → 2.0.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/README.md +73 -0
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @multisystemsuite/timezone-engine-scheduler
|
|
2
|
+
|
|
3
|
+
Enterprise scheduling engine — meetings, business hours, shifts, holidays, and payroll timezone support.
|
|
4
|
+
|
|
5
|
+
## What it is used for
|
|
6
|
+
|
|
7
|
+
- **Smart meeting planner** across global timezones
|
|
8
|
+
- **Business hours** and overlap detection
|
|
9
|
+
- **Global workforce shifts** and payroll hour calculation
|
|
10
|
+
- **Holiday calendars** per timezone
|
|
11
|
+
- HRMS, manufacturing shift planning, SaaS booking systems
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @multisystemsuite/timezone-engine-scheduler
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import {
|
|
23
|
+
createSchedulerEngine,
|
|
24
|
+
defaultSchedulerConfig,
|
|
25
|
+
} from "@multisystemsuite/timezone-engine-scheduler";
|
|
26
|
+
|
|
27
|
+
const scheduler = createSchedulerEngine({
|
|
28
|
+
defaultTimezone: "UTC",
|
|
29
|
+
businessHours: { start: "09:00", end: "17:00", days: [1, 2, 3, 4, 5] },
|
|
30
|
+
slotDurationMinutes: 30,
|
|
31
|
+
bufferMinutes: 15,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Find meeting slots across NYC, London, Mumbai
|
|
35
|
+
const slots = scheduler.findAvailableSlots(
|
|
36
|
+
["America/New_York", "Europe/London", "Asia/Kolkata"],
|
|
37
|
+
new Date(),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const best = scheduler.suggestMeetingTime(["America/New_York", "Asia/Kolkata"], new Date(), 60);
|
|
41
|
+
|
|
42
|
+
// Shift / payroll
|
|
43
|
+
scheduler.registerShift({
|
|
44
|
+
id: "in-morning",
|
|
45
|
+
name: "India Morning",
|
|
46
|
+
timezone: "Asia/Kolkata",
|
|
47
|
+
startTime: "06:00",
|
|
48
|
+
endTime: "14:00",
|
|
49
|
+
days: [1, 2, 3, 4, 5],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const { start, end } = scheduler.computeShiftHoursUTC(shift, new Date());
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Key APIs
|
|
56
|
+
|
|
57
|
+
| API | Purpose |
|
|
58
|
+
| --------------------------- | ------------------------------------------ |
|
|
59
|
+
| `findAvailableSlots()` | Common free windows across timezones |
|
|
60
|
+
| `suggestMeetingTime()` | Best single slot recommendation |
|
|
61
|
+
| `calculateMeetingOverlap()` | Overlap duration between participants |
|
|
62
|
+
| `isBusinessHour()` | Check if datetime is within business hours |
|
|
63
|
+
| `registerHoliday()` | Block dates for scheduling |
|
|
64
|
+
| `computeShiftHoursUTC()` | Payroll-safe UTC shift boundaries |
|
|
65
|
+
|
|
66
|
+
## Related packages
|
|
67
|
+
|
|
68
|
+
- `@multisystemsuite/timezone-engine-core` — timezone conversion
|
|
69
|
+
- `@multisystemsuite/timezone-engine-react` — Scheduler UI component
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,6 @@ var SchedulerEngine = class {
|
|
|
37
37
|
const zoned = dateFnsTz.toZonedTime(date, timezone);
|
|
38
38
|
const day = zoned.getDay();
|
|
39
39
|
if (!bh.days.includes(day)) return bh;
|
|
40
|
-
parseTimeOnDate(zoned, bh.start, timezone);
|
|
41
|
-
parseTimeOnDate(zoned, bh.end, timezone);
|
|
42
40
|
return bh;
|
|
43
41
|
}
|
|
44
42
|
isBusinessHour(date, timezone, hours) {
|
|
@@ -54,7 +52,11 @@ var SchedulerEngine = class {
|
|
|
54
52
|
/** Calculate meeting overlap across participants */
|
|
55
53
|
calculateMeetingOverlap(slots) {
|
|
56
54
|
if (slots.length < 2) {
|
|
57
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
hasOverlap: false,
|
|
57
|
+
overlapDurationMinutes: 0,
|
|
58
|
+
participants: slots.map((s) => s.timezone)
|
|
59
|
+
};
|
|
58
60
|
}
|
|
59
61
|
const intervals = slots.map((s) => ({ start: s.start, end: s.end }));
|
|
60
62
|
let hasOverlap = false;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["parse","fromZonedTime","toZonedTime","isWithinInterval","areIntervalsOverlapping","findCommonWorkingHours","addMinutes"],"mappings":"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","file":"index.cjs","sourcesContent":["import { addMinutes, isWithinInterval, parse, areIntervalsOverlapping } from \"date-fns\";\nimport { toZonedTime, fromZonedTime } from \"date-fns-tz\";\nimport type {\n SchedulerConfig,\n MeetingSlot,\n MeetingOverlapResult,\n ShiftDefinition,\n HolidayDefinition,\n BusinessHours,\n} from \"@multisystemsuite/timezone-engine-shared-types\";\nimport { findCommonWorkingHours } from \"@multisystemsuite/timezone-engine-core\";\n\nfunction parseTimeOnDate(date: Date, time: string, timezone: string): Date {\n const parsed = parse(time, \"HH:mm\", date);\n return fromZonedTime(parsed, timezone);\n}\n\n/** Enterprise scheduler engine */\nexport class SchedulerEngine {\n private config: SchedulerConfig;\n private holidays: HolidayDefinition[] = [];\n private shifts: ShiftDefinition[] = [];\n\n constructor(config: SchedulerConfig) {\n this.config = config;\n }\n\n updateConfig(config: Partial<SchedulerConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n registerHoliday(holiday: HolidayDefinition): void {\n this.holidays.push(holiday);\n }\n\n registerShift(shift: ShiftDefinition): void {\n this.shifts.push(shift);\n }\n\n /** Check if a date falls on a registered holiday */\n isHoliday(date: Date, timezone: string): boolean {\n const zoned = toZonedTime(date, timezone);\n const dateStr = zoned.toISOString().slice(0, 10);\n return this.holidays.some((h) => h.date === dateStr && h.timezone === timezone);\n }\n\n /** Check if time is within business hours */\n isWithinBusinessHours(date: Date, timezone: string, hours?: BusinessHours): BusinessHours {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return bh;\n\n const start = parseTimeOnDate(zoned, bh.start, timezone);\n const end = parseTimeOnDate(zoned, bh.end, timezone);\n\n return bh;\n }\n\n isBusinessHour(date: Date, timezone: string, hours?: BusinessHours): boolean {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return false;\n if (this.isHoliday(date, timezone)) return false;\n\n const start = parseTimeOnDate(zoned, bh.start, timezone);\n const end = parseTimeOnDate(zoned, bh.end, timezone);\n return isWithinInterval(zoned, { start, end });\n }\n\n /** Calculate meeting overlap across participants */\n calculateMeetingOverlap(slots: MeetingSlot[]): MeetingOverlapResult {\n if (slots.length < 2) {\n return { hasOverlap: false, overlapDurationMinutes: 0, participants: slots.map((s) => s.timezone) };\n }\n\n const intervals = slots.map((s) => ({ start: s.start, end: s.end }));\n let hasOverlap = false;\n let overlapStart: Date | undefined;\n let overlapEnd: Date | undefined;\n\n for (let i = 0; i < intervals.length - 1; i++) {\n for (let j = i + 1; j < intervals.length; j++) {\n const a = intervals[i];\n const b = intervals[j];\n if (a && b && areIntervalsOverlapping(a, b)) {\n hasOverlap = true;\n const start = a.start > b.start ? a.start : b.start;\n const end = a.end < b.end ? a.end : b.end;\n if (!overlapStart || start > overlapStart) overlapStart = start;\n if (!overlapEnd || end < overlapEnd) overlapEnd = end;\n }\n }\n }\n\n const duration =\n overlapStart && overlapEnd\n ? (overlapEnd.getTime() - overlapStart.getTime()) / 60_000\n : 0;\n\n const result: MeetingOverlapResult = {\n hasOverlap,\n overlapDurationMinutes: duration,\n participants: slots.map((s) => s.timezone),\n };\n\n if (overlapStart) result.overlapStart = overlapStart;\n if (overlapEnd) result.overlapEnd = overlapEnd;\n\n return result;\n }\n\n /** Find available meeting slots across timezones */\n findAvailableSlots(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot[] {\n const duration = durationMinutes ?? this.config.slotDurationMinutes;\n const { startHourUTC, endHourUTC, overlapHours } = findCommonWorkingHours(\n participantTimezones,\n parseInt(this.config.businessHours.start.split(\":\")[0] ?? \"9\", 10),\n parseInt(this.config.businessHours.end.split(\":\")[0] ?? \"17\", 10),\n date,\n );\n\n if (overlapHours <= 0) return [];\n\n const slots: MeetingSlot[] = [];\n const startMs = date.getTime();\n const slotStart = new Date(startMs);\n slotStart.setUTCHours(Math.floor(startHourUTC), (startHourUTC % 1) * 60, 0, 0);\n\n let current = slotStart;\n const endTime = new Date(startMs);\n endTime.setUTCHours(Math.floor(endHourUTC), (endHourUTC % 1) * 60, 0, 0);\n\n while (current < endTime) {\n const slotEnd = addMinutes(current, duration);\n if (slotEnd <= endTime) {\n slots.push({\n start: current,\n end: slotEnd,\n timezone: this.config.defaultTimezone,\n });\n }\n current = addMinutes(current, duration + (this.config.bufferMinutes ?? 0));\n }\n\n return slots;\n }\n\n /** Smart meeting planner — suggest best slot */\n suggestMeetingTime(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot | null {\n const slots = this.findAvailableSlots(participantTimezones, date, durationMinutes);\n return slots[0] ?? null;\n }\n\n /** Global workforce shift support */\n getActiveShifts(date: Date): ShiftDefinition[] {\n const day = date.getDay();\n return this.shifts.filter((shift) => {\n const zoned = toZonedTime(date, shift.timezone);\n return shift.days.includes(zoned.getDay()) || shift.days.includes(day);\n });\n }\n\n /** Payroll timezone — compute shift hours in UTC */\n computeShiftHoursUTC(shift: ShiftDefinition, date: Date): { start: Date; end: Date } {\n const zoned = toZonedTime(date, shift.timezone);\n const start = parseTimeOnDate(zoned, shift.startTime, shift.timezone);\n const end = parseTimeOnDate(zoned, shift.endTime, shift.timezone);\n return { start: fromZonedTime(start, shift.timezone), end: fromZonedTime(end, shift.timezone) };\n }\n}\n\nexport function createSchedulerEngine(config: SchedulerConfig): SchedulerEngine {\n return new SchedulerEngine(config);\n}\n\nexport const defaultSchedulerConfig: SchedulerConfig = {\n defaultTimezone: \"UTC\",\n businessHours: {\n start: \"09:00\",\n end: \"17:00\",\n days: [1, 2, 3, 4, 5],\n },\n slotDurationMinutes: 30,\n bufferMinutes: 15,\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["parse","fromZonedTime","toZonedTime","isWithinInterval","areIntervalsOverlapping","findCommonWorkingHours","addMinutes"],"mappings":"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","file":"index.cjs","sourcesContent":["import { addMinutes, isWithinInterval, parse, areIntervalsOverlapping } from \"date-fns\";\nimport { toZonedTime, fromZonedTime } from \"date-fns-tz\";\nimport type {\n SchedulerConfig,\n MeetingSlot,\n MeetingOverlapResult,\n ShiftDefinition,\n HolidayDefinition,\n BusinessHours,\n} from \"@multisystemsuite/timezone-engine-shared-types\";\nimport { findCommonWorkingHours } from \"@multisystemsuite/timezone-engine-core\";\n\nfunction parseTimeOnDate(date: Date, time: string, timezone: string): Date {\n const parsed = parse(time, \"HH:mm\", date);\n return fromZonedTime(parsed, timezone);\n}\n\n/** Enterprise scheduler engine */\nexport class SchedulerEngine {\n private config: SchedulerConfig;\n private holidays: HolidayDefinition[] = [];\n private shifts: ShiftDefinition[] = [];\n\n constructor(config: SchedulerConfig) {\n this.config = config;\n }\n\n updateConfig(config: Partial<SchedulerConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n registerHoliday(holiday: HolidayDefinition): void {\n this.holidays.push(holiday);\n }\n\n registerShift(shift: ShiftDefinition): void {\n this.shifts.push(shift);\n }\n\n /** Check if a date falls on a registered holiday */\n isHoliday(date: Date, timezone: string): boolean {\n const zoned = toZonedTime(date, timezone);\n const dateStr = zoned.toISOString().slice(0, 10);\n return this.holidays.some((h) => h.date === dateStr && h.timezone === timezone);\n }\n\n /** Check if time is within business hours */\n isWithinBusinessHours(date: Date, timezone: string, hours?: BusinessHours): BusinessHours {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return bh;\n\n return bh;\n }\n\n isBusinessHour(date: Date, timezone: string, hours?: BusinessHours): boolean {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return false;\n if (this.isHoliday(date, timezone)) return false;\n\n const start = parseTimeOnDate(zoned, bh.start, timezone);\n const end = parseTimeOnDate(zoned, bh.end, timezone);\n return isWithinInterval(zoned, { start, end });\n }\n\n /** Calculate meeting overlap across participants */\n calculateMeetingOverlap(slots: MeetingSlot[]): MeetingOverlapResult {\n if (slots.length < 2) {\n return {\n hasOverlap: false,\n overlapDurationMinutes: 0,\n participants: slots.map((s) => s.timezone),\n };\n }\n\n const intervals = slots.map((s) => ({ start: s.start, end: s.end }));\n let hasOverlap = false;\n let overlapStart: Date | undefined;\n let overlapEnd: Date | undefined;\n\n for (let i = 0; i < intervals.length - 1; i++) {\n for (let j = i + 1; j < intervals.length; j++) {\n const a = intervals[i];\n const b = intervals[j];\n if (a && b && areIntervalsOverlapping(a, b)) {\n hasOverlap = true;\n const start = a.start > b.start ? a.start : b.start;\n const end = a.end < b.end ? a.end : b.end;\n if (!overlapStart || start > overlapStart) overlapStart = start;\n if (!overlapEnd || end < overlapEnd) overlapEnd = end;\n }\n }\n }\n\n const duration =\n overlapStart && overlapEnd ? (overlapEnd.getTime() - overlapStart.getTime()) / 60_000 : 0;\n\n const result: MeetingOverlapResult = {\n hasOverlap,\n overlapDurationMinutes: duration,\n participants: slots.map((s) => s.timezone),\n };\n\n if (overlapStart) result.overlapStart = overlapStart;\n if (overlapEnd) result.overlapEnd = overlapEnd;\n\n return result;\n }\n\n /** Find available meeting slots across timezones */\n findAvailableSlots(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot[] {\n const duration = durationMinutes ?? this.config.slotDurationMinutes;\n const { startHourUTC, endHourUTC, overlapHours } = findCommonWorkingHours(\n participantTimezones,\n parseInt(this.config.businessHours.start.split(\":\")[0] ?? \"9\", 10),\n parseInt(this.config.businessHours.end.split(\":\")[0] ?? \"17\", 10),\n date,\n );\n\n if (overlapHours <= 0) return [];\n\n const slots: MeetingSlot[] = [];\n const startMs = date.getTime();\n const slotStart = new Date(startMs);\n slotStart.setUTCHours(Math.floor(startHourUTC), (startHourUTC % 1) * 60, 0, 0);\n\n let current = slotStart;\n const endTime = new Date(startMs);\n endTime.setUTCHours(Math.floor(endHourUTC), (endHourUTC % 1) * 60, 0, 0);\n\n while (current < endTime) {\n const slotEnd = addMinutes(current, duration);\n if (slotEnd <= endTime) {\n slots.push({\n start: current,\n end: slotEnd,\n timezone: this.config.defaultTimezone,\n });\n }\n current = addMinutes(current, duration + (this.config.bufferMinutes ?? 0));\n }\n\n return slots;\n }\n\n /** Smart meeting planner — suggest best slot */\n suggestMeetingTime(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot | null {\n const slots = this.findAvailableSlots(participantTimezones, date, durationMinutes);\n return slots[0] ?? null;\n }\n\n /** Global workforce shift support */\n getActiveShifts(date: Date): ShiftDefinition[] {\n const day = date.getDay();\n return this.shifts.filter((shift) => {\n const zoned = toZonedTime(date, shift.timezone);\n return shift.days.includes(zoned.getDay()) || shift.days.includes(day);\n });\n }\n\n /** Payroll timezone — compute shift hours in UTC */\n computeShiftHoursUTC(shift: ShiftDefinition, date: Date): { start: Date; end: Date } {\n const zoned = toZonedTime(date, shift.timezone);\n const start = parseTimeOnDate(zoned, shift.startTime, shift.timezone);\n const end = parseTimeOnDate(zoned, shift.endTime, shift.timezone);\n return { start: fromZonedTime(start, shift.timezone), end: fromZonedTime(end, shift.timezone) };\n }\n}\n\nexport function createSchedulerEngine(config: SchedulerConfig): SchedulerEngine {\n return new SchedulerEngine(config);\n}\n\nexport const defaultSchedulerConfig: SchedulerConfig = {\n defaultTimezone: \"UTC\",\n businessHours: {\n start: \"09:00\",\n end: \"17:00\",\n days: [1, 2, 3, 4, 5],\n },\n slotDurationMinutes: 30,\n bufferMinutes: 15,\n};\n"]}
|
package/dist/index.js
CHANGED
|
@@ -35,8 +35,6 @@ var SchedulerEngine = class {
|
|
|
35
35
|
const zoned = toZonedTime(date, timezone);
|
|
36
36
|
const day = zoned.getDay();
|
|
37
37
|
if (!bh.days.includes(day)) return bh;
|
|
38
|
-
parseTimeOnDate(zoned, bh.start, timezone);
|
|
39
|
-
parseTimeOnDate(zoned, bh.end, timezone);
|
|
40
38
|
return bh;
|
|
41
39
|
}
|
|
42
40
|
isBusinessHour(date, timezone, hours) {
|
|
@@ -52,7 +50,11 @@ var SchedulerEngine = class {
|
|
|
52
50
|
/** Calculate meeting overlap across participants */
|
|
53
51
|
calculateMeetingOverlap(slots) {
|
|
54
52
|
if (slots.length < 2) {
|
|
55
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
hasOverlap: false,
|
|
55
|
+
overlapDurationMinutes: 0,
|
|
56
|
+
participants: slots.map((s) => s.timezone)
|
|
57
|
+
};
|
|
56
58
|
}
|
|
57
59
|
const intervals = slots.map((s) => ({ start: s.start, end: s.end }));
|
|
58
60
|
let hasOverlap = false;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { addMinutes, isWithinInterval, parse, areIntervalsOverlapping } from \"date-fns\";\nimport { toZonedTime, fromZonedTime } from \"date-fns-tz\";\nimport type {\n SchedulerConfig,\n MeetingSlot,\n MeetingOverlapResult,\n ShiftDefinition,\n HolidayDefinition,\n BusinessHours,\n} from \"@multisystemsuite/timezone-engine-shared-types\";\nimport { findCommonWorkingHours } from \"@multisystemsuite/timezone-engine-core\";\n\nfunction parseTimeOnDate(date: Date, time: string, timezone: string): Date {\n const parsed = parse(time, \"HH:mm\", date);\n return fromZonedTime(parsed, timezone);\n}\n\n/** Enterprise scheduler engine */\nexport class SchedulerEngine {\n private config: SchedulerConfig;\n private holidays: HolidayDefinition[] = [];\n private shifts: ShiftDefinition[] = [];\n\n constructor(config: SchedulerConfig) {\n this.config = config;\n }\n\n updateConfig(config: Partial<SchedulerConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n registerHoliday(holiday: HolidayDefinition): void {\n this.holidays.push(holiday);\n }\n\n registerShift(shift: ShiftDefinition): void {\n this.shifts.push(shift);\n }\n\n /** Check if a date falls on a registered holiday */\n isHoliday(date: Date, timezone: string): boolean {\n const zoned = toZonedTime(date, timezone);\n const dateStr = zoned.toISOString().slice(0, 10);\n return this.holidays.some((h) => h.date === dateStr && h.timezone === timezone);\n }\n\n /** Check if time is within business hours */\n isWithinBusinessHours(date: Date, timezone: string, hours?: BusinessHours): BusinessHours {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return bh;\n\n const start = parseTimeOnDate(zoned, bh.start, timezone);\n const end = parseTimeOnDate(zoned, bh.end, timezone);\n\n return bh;\n }\n\n isBusinessHour(date: Date, timezone: string, hours?: BusinessHours): boolean {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return false;\n if (this.isHoliday(date, timezone)) return false;\n\n const start = parseTimeOnDate(zoned, bh.start, timezone);\n const end = parseTimeOnDate(zoned, bh.end, timezone);\n return isWithinInterval(zoned, { start, end });\n }\n\n /** Calculate meeting overlap across participants */\n calculateMeetingOverlap(slots: MeetingSlot[]): MeetingOverlapResult {\n if (slots.length < 2) {\n return { hasOverlap: false, overlapDurationMinutes: 0, participants: slots.map((s) => s.timezone) };\n }\n\n const intervals = slots.map((s) => ({ start: s.start, end: s.end }));\n let hasOverlap = false;\n let overlapStart: Date | undefined;\n let overlapEnd: Date | undefined;\n\n for (let i = 0; i < intervals.length - 1; i++) {\n for (let j = i + 1; j < intervals.length; j++) {\n const a = intervals[i];\n const b = intervals[j];\n if (a && b && areIntervalsOverlapping(a, b)) {\n hasOverlap = true;\n const start = a.start > b.start ? a.start : b.start;\n const end = a.end < b.end ? a.end : b.end;\n if (!overlapStart || start > overlapStart) overlapStart = start;\n if (!overlapEnd || end < overlapEnd) overlapEnd = end;\n }\n }\n }\n\n const duration =\n overlapStart && overlapEnd\n ? (overlapEnd.getTime() - overlapStart.getTime()) / 60_000\n : 0;\n\n const result: MeetingOverlapResult = {\n hasOverlap,\n overlapDurationMinutes: duration,\n participants: slots.map((s) => s.timezone),\n };\n\n if (overlapStart) result.overlapStart = overlapStart;\n if (overlapEnd) result.overlapEnd = overlapEnd;\n\n return result;\n }\n\n /** Find available meeting slots across timezones */\n findAvailableSlots(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot[] {\n const duration = durationMinutes ?? this.config.slotDurationMinutes;\n const { startHourUTC, endHourUTC, overlapHours } = findCommonWorkingHours(\n participantTimezones,\n parseInt(this.config.businessHours.start.split(\":\")[0] ?? \"9\", 10),\n parseInt(this.config.businessHours.end.split(\":\")[0] ?? \"17\", 10),\n date,\n );\n\n if (overlapHours <= 0) return [];\n\n const slots: MeetingSlot[] = [];\n const startMs = date.getTime();\n const slotStart = new Date(startMs);\n slotStart.setUTCHours(Math.floor(startHourUTC), (startHourUTC % 1) * 60, 0, 0);\n\n let current = slotStart;\n const endTime = new Date(startMs);\n endTime.setUTCHours(Math.floor(endHourUTC), (endHourUTC % 1) * 60, 0, 0);\n\n while (current < endTime) {\n const slotEnd = addMinutes(current, duration);\n if (slotEnd <= endTime) {\n slots.push({\n start: current,\n end: slotEnd,\n timezone: this.config.defaultTimezone,\n });\n }\n current = addMinutes(current, duration + (this.config.bufferMinutes ?? 0));\n }\n\n return slots;\n }\n\n /** Smart meeting planner — suggest best slot */\n suggestMeetingTime(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot | null {\n const slots = this.findAvailableSlots(participantTimezones, date, durationMinutes);\n return slots[0] ?? null;\n }\n\n /** Global workforce shift support */\n getActiveShifts(date: Date): ShiftDefinition[] {\n const day = date.getDay();\n return this.shifts.filter((shift) => {\n const zoned = toZonedTime(date, shift.timezone);\n return shift.days.includes(zoned.getDay()) || shift.days.includes(day);\n });\n }\n\n /** Payroll timezone — compute shift hours in UTC */\n computeShiftHoursUTC(shift: ShiftDefinition, date: Date): { start: Date; end: Date } {\n const zoned = toZonedTime(date, shift.timezone);\n const start = parseTimeOnDate(zoned, shift.startTime, shift.timezone);\n const end = parseTimeOnDate(zoned, shift.endTime, shift.timezone);\n return { start: fromZonedTime(start, shift.timezone), end: fromZonedTime(end, shift.timezone) };\n }\n}\n\nexport function createSchedulerEngine(config: SchedulerConfig): SchedulerEngine {\n return new SchedulerEngine(config);\n}\n\nexport const defaultSchedulerConfig: SchedulerConfig = {\n defaultTimezone: \"UTC\",\n businessHours: {\n start: \"09:00\",\n end: \"17:00\",\n days: [1, 2, 3, 4, 5],\n },\n slotDurationMinutes: 30,\n bufferMinutes: 15,\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { addMinutes, isWithinInterval, parse, areIntervalsOverlapping } from \"date-fns\";\nimport { toZonedTime, fromZonedTime } from \"date-fns-tz\";\nimport type {\n SchedulerConfig,\n MeetingSlot,\n MeetingOverlapResult,\n ShiftDefinition,\n HolidayDefinition,\n BusinessHours,\n} from \"@multisystemsuite/timezone-engine-shared-types\";\nimport { findCommonWorkingHours } from \"@multisystemsuite/timezone-engine-core\";\n\nfunction parseTimeOnDate(date: Date, time: string, timezone: string): Date {\n const parsed = parse(time, \"HH:mm\", date);\n return fromZonedTime(parsed, timezone);\n}\n\n/** Enterprise scheduler engine */\nexport class SchedulerEngine {\n private config: SchedulerConfig;\n private holidays: HolidayDefinition[] = [];\n private shifts: ShiftDefinition[] = [];\n\n constructor(config: SchedulerConfig) {\n this.config = config;\n }\n\n updateConfig(config: Partial<SchedulerConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n registerHoliday(holiday: HolidayDefinition): void {\n this.holidays.push(holiday);\n }\n\n registerShift(shift: ShiftDefinition): void {\n this.shifts.push(shift);\n }\n\n /** Check if a date falls on a registered holiday */\n isHoliday(date: Date, timezone: string): boolean {\n const zoned = toZonedTime(date, timezone);\n const dateStr = zoned.toISOString().slice(0, 10);\n return this.holidays.some((h) => h.date === dateStr && h.timezone === timezone);\n }\n\n /** Check if time is within business hours */\n isWithinBusinessHours(date: Date, timezone: string, hours?: BusinessHours): BusinessHours {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return bh;\n\n return bh;\n }\n\n isBusinessHour(date: Date, timezone: string, hours?: BusinessHours): boolean {\n const bh = hours ?? this.config.businessHours;\n const zoned = toZonedTime(date, timezone);\n const day = zoned.getDay();\n if (!bh.days.includes(day)) return false;\n if (this.isHoliday(date, timezone)) return false;\n\n const start = parseTimeOnDate(zoned, bh.start, timezone);\n const end = parseTimeOnDate(zoned, bh.end, timezone);\n return isWithinInterval(zoned, { start, end });\n }\n\n /** Calculate meeting overlap across participants */\n calculateMeetingOverlap(slots: MeetingSlot[]): MeetingOverlapResult {\n if (slots.length < 2) {\n return {\n hasOverlap: false,\n overlapDurationMinutes: 0,\n participants: slots.map((s) => s.timezone),\n };\n }\n\n const intervals = slots.map((s) => ({ start: s.start, end: s.end }));\n let hasOverlap = false;\n let overlapStart: Date | undefined;\n let overlapEnd: Date | undefined;\n\n for (let i = 0; i < intervals.length - 1; i++) {\n for (let j = i + 1; j < intervals.length; j++) {\n const a = intervals[i];\n const b = intervals[j];\n if (a && b && areIntervalsOverlapping(a, b)) {\n hasOverlap = true;\n const start = a.start > b.start ? a.start : b.start;\n const end = a.end < b.end ? a.end : b.end;\n if (!overlapStart || start > overlapStart) overlapStart = start;\n if (!overlapEnd || end < overlapEnd) overlapEnd = end;\n }\n }\n }\n\n const duration =\n overlapStart && overlapEnd ? (overlapEnd.getTime() - overlapStart.getTime()) / 60_000 : 0;\n\n const result: MeetingOverlapResult = {\n hasOverlap,\n overlapDurationMinutes: duration,\n participants: slots.map((s) => s.timezone),\n };\n\n if (overlapStart) result.overlapStart = overlapStart;\n if (overlapEnd) result.overlapEnd = overlapEnd;\n\n return result;\n }\n\n /** Find available meeting slots across timezones */\n findAvailableSlots(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot[] {\n const duration = durationMinutes ?? this.config.slotDurationMinutes;\n const { startHourUTC, endHourUTC, overlapHours } = findCommonWorkingHours(\n participantTimezones,\n parseInt(this.config.businessHours.start.split(\":\")[0] ?? \"9\", 10),\n parseInt(this.config.businessHours.end.split(\":\")[0] ?? \"17\", 10),\n date,\n );\n\n if (overlapHours <= 0) return [];\n\n const slots: MeetingSlot[] = [];\n const startMs = date.getTime();\n const slotStart = new Date(startMs);\n slotStart.setUTCHours(Math.floor(startHourUTC), (startHourUTC % 1) * 60, 0, 0);\n\n let current = slotStart;\n const endTime = new Date(startMs);\n endTime.setUTCHours(Math.floor(endHourUTC), (endHourUTC % 1) * 60, 0, 0);\n\n while (current < endTime) {\n const slotEnd = addMinutes(current, duration);\n if (slotEnd <= endTime) {\n slots.push({\n start: current,\n end: slotEnd,\n timezone: this.config.defaultTimezone,\n });\n }\n current = addMinutes(current, duration + (this.config.bufferMinutes ?? 0));\n }\n\n return slots;\n }\n\n /** Smart meeting planner — suggest best slot */\n suggestMeetingTime(\n participantTimezones: string[],\n date: Date,\n durationMinutes?: number,\n ): MeetingSlot | null {\n const slots = this.findAvailableSlots(participantTimezones, date, durationMinutes);\n return slots[0] ?? null;\n }\n\n /** Global workforce shift support */\n getActiveShifts(date: Date): ShiftDefinition[] {\n const day = date.getDay();\n return this.shifts.filter((shift) => {\n const zoned = toZonedTime(date, shift.timezone);\n return shift.days.includes(zoned.getDay()) || shift.days.includes(day);\n });\n }\n\n /** Payroll timezone — compute shift hours in UTC */\n computeShiftHoursUTC(shift: ShiftDefinition, date: Date): { start: Date; end: Date } {\n const zoned = toZonedTime(date, shift.timezone);\n const start = parseTimeOnDate(zoned, shift.startTime, shift.timezone);\n const end = parseTimeOnDate(zoned, shift.endTime, shift.timezone);\n return { start: fromZonedTime(start, shift.timezone), end: fromZonedTime(end, shift.timezone) };\n }\n}\n\nexport function createSchedulerEngine(config: SchedulerConfig): SchedulerEngine {\n return new SchedulerEngine(config);\n}\n\nexport const defaultSchedulerConfig: SchedulerConfig = {\n defaultTimezone: \"UTC\",\n businessHours: {\n start: \"09:00\",\n end: \"17:00\",\n days: [1, 2, 3, 4, 5],\n },\n slotDurationMinutes: 30,\n bufferMinutes: 15,\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multisystemsuite/timezone-engine-scheduler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Enterprise scheduling engine for @multisystemsuite/timezone-engine",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "MultiSystemSuite",
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"sideEffects": false,
|
|
32
32
|
"files": [
|
|
33
|
-
"dist"
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md"
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"date-fns": "^4.1.0",
|
|
37
38
|
"date-fns-tz": "^3.2.0",
|
|
38
|
-
"@multisystemsuite/timezone-engine-core": "
|
|
39
|
-
"@multisystemsuite/timezone-engine-shared-types": "
|
|
39
|
+
"@multisystemsuite/timezone-engine-core": "2.0.0",
|
|
40
|
+
"@multisystemsuite/timezone-engine-shared-types": "2.0.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"rimraf": "^6.0.1",
|