using System; using System.Collections.Generic; using System.Linq; using BeWo.Data.Entities; using BS.Shared.Core; using BS.Shared.Extensions; using DevExpress.XtraScheduler; using DevExpress.XtraScheduler.Compatibility; using NHibernate.Criterion; using NHibernate.SqlCommand; namespace BeWo.Data.Utils { public class IntervalFinderHelper { public static List GenerateIntervalsForCriteria(DateTime intervalStart, DateTime intervalEnd, bool skipWeekends) { var result = new List(); if (intervalEnd <= intervalStart) { return result; } var start = intervalStart; var end = start.MergeDatesByDate(intervalEnd); while(end <= intervalEnd) { end = start.Date.Equals(intervalEnd.Date) ? start.MergeDatesByDate(intervalEnd) : new DateTime(start.Year, start.Month, start.Day, 23, 59, 59); if(!start.Date.Equals(intervalStart.Date)) { start = new DateTime(start.Year, start.Month, start.Day); } if(skipWeekends && (start.Date.DayOfWeek == DayOfWeek.Saturday || start.Date.DayOfWeek == DayOfWeek.Sunday)) { start = start.AddDays(1); end = start.Date.Equals(intervalEnd.Date) ? start.MergeDatesByDate(intervalEnd) : new DateTime(start.Year, start.Month, start.Day, 23, 59, 59); continue; } result.AddIfNotIn(new DateTimeSpan(start, end)); start = start.AddDays(1); end = start.MergeDatesByDate(intervalEnd); } return result; } public static List CreateIntervalRecurrenceCriterias(List intervals, bool skipWeekends) { var result = new List(); foreach(var interval in intervals) { var start = interval.StartDate; var end = interval.EndDate; result.AddIfNotIn(CreateRecurrenceCriterion(start, end)); } // TODO: KGW //for (var i = 0; i < totalDays; i++) //{ // start = start.AddDays(i).MergeDatesByDate(startDate); // if (skipWeekends && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday)) // { // continue; // } // DateTime e; // var end = start.MergeDatesByDate(endDate); // while ((e = start.AddMinutes(duration)) <= end && start <= end.AddMinutes(-1 * duration)) // { // result.AddIfNotIn(CreateRecurrenceCriterion(start, e)); // start = start.AddMinutes(intervalBuffer); // } //} return result; } private static ICriterion CreateRecurrenceCriterion(DateTime start, DateTime end) { var recurrenceBetween = $"'{start:yyyy-MM-dd HH:mm:ss}' BETWEEN STR_TO_DATE(SUBSTRING(RecurrenceInfo, 24, 19), '%m/%d/%Y %H:%i:%s') AND STR_TO_DATE(SUBSTRING(RecurrenceInfo, 50, 19), '%m/%d/%Y %H:%i:%s')"; var recurrenceAfter = $"'{start:yyyy-MM-dd HH:mm:ss}' > STR_TO_DATE(SUBSTRING(RecurrenceInfo, 24, 19), '%m/%d/%Y %H:%i:%s')"; var result = Restrictions.Or( Restrictions.And( Restrictions.Not(Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "Range", MatchMode.Anywhere)), Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "OccurrenceCount=\"10\"", MatchMode.Anywhere)), Restrictions.Or( Restrictions.Or( Restrictions.And(Restrictions.Lt(nameof(SchedulerAppointment.StartDate), end), Restrictions.Ge(nameof(SchedulerAppointment.EndDate), start)), Restrictions.Or(Restrictions.And( Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "End", MatchMode.Anywhere), Restrictions.And(Restrictions.IsNotNull(nameof(SchedulerAppointment.RecurrenceInfo)), Expression.Sql(new SqlString(recurrenceBetween))) ), Restrictions.And( Restrictions.And( Restrictions.IsNotNull(nameof(SchedulerAppointment.RecurrenceInfo)), Restrictions.Not(Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "End", MatchMode.Anywhere))), Expression.Sql(recurrenceAfter))) ), Restrictions.And(Restrictions.Eq(nameof(SchedulerAppointment.Type), 3), Expression.Sql(new SqlString(recurrenceBetween))))); return result; } public static Dictionary> CreateFreeIntervalsDictionary(IEnumerable freeIntervals) { var result = new Dictionary>(); var groupedIntervals = from dateTimeSpan in freeIntervals group dateTimeSpan by dateTimeSpan.StartDate.Date; var unsortedDictionary = new Dictionary>(); foreach(var grouping in groupedIntervals) { var orderedList = grouping.OrderBy(dateTimeSpan => dateTimeSpan.StartDate).ToList(); unsortedDictionary.AddOrUpdateValueInDictionary(grouping.Key, orderedList); } var keys = unsortedDictionary.Keys.OrderBy(key => key.Date).ToList(); foreach(var key in keys) { result.Add(key, unsortedDictionary[key]); } return result; } public static List CreateIntervals(int duration, DateTime intervalStart, DateTime intervalEnd, bool skipWeekends, int intervalBuffer) { var result = new List(); if(duration > 0 && intervalStart < intervalEnd && intervalBuffer > 10) { var timeSpan = intervalEnd - intervalStart; var totalDays = Convert.ToInt32(Math.Ceiling(timeSpan.TotalDays)); for(var i = 0; i < totalDays; i++) { var start = intervalStart.AddDays(i).MergeDatesByDate(intervalStart); if(skipWeekends && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday)) { continue; } DateTime e; var end = intervalEnd; while((e = start.AddMinutes(duration)) <= end && start <= end.AddMinutes(-1 * duration)) { result.Add(new DateTimeSpan(start, e, true)); start = start.AddMinutes(intervalBuffer); } } } return result; } /// /// Liefert DateTimeSpan-Objekte für ganze Tage zwischen zwei Datumsangaben zurück. /// Z.B. 04.12.2023 bis 06.12.2023 liefert drei DateTimeSpan für den 04.12., 05.12. und 06.12. zurück. /// /// Intervallstart /// Intervallende /// Liste der DateTimeSpan-Objekte für die Anzahl der Tage im angegebenen Intervall. public static List GenereateIntervalsForChecking(DateTime intervalStart, DateTime intervalEnd) { var start = intervalStart.Date; var end = intervalEnd.Date; var result = new List(); if (end < start) { return result; } var days = Convert.ToInt32((end - start).TotalDays); if (days == 0) { days = 1; } var rest = days % 6; var limit = (days - rest) / 6; var s = start; DateTime e; for(var i = 0; i < limit; i++) { e = s.AddDays(5); result.Add(new DateTimeSpan(s, e)); s = e.AddDays(1); } if(rest > 0) { e = s.AddDays(rest); if (rest == 1) { e = e.AddDays(-1); } result.Add(new DateTimeSpan(s, e)); } return result; } public static IEnumerable CalculateRecurrences(IEnumerable appointments, DateTime start, DateTime end, IReadOnlyCollection changedOccurrences, IReadOnlyCollection deletedOccurrences) { var result = new List(); var interval = new TimeInterval(start, end); foreach (var appointment in appointments.Where(app => app.RecurrenceInfo != null && app.Type == 1 && app.StartDate.HasValue && app.EndDate.HasValue)) { var recurrenceInfo = new RecurrenceInfo(); recurrenceInfo.FromXml(appointment.RecurrenceInfo); var occurrenceCalculator = OccurrenceCalculator.CreateInstance(recurrenceInfo); var pattern = StaticAppointmentFactory.CreateAppointment(AppointmentType.Pattern); pattern.RecurrenceInfo.FromXml(appointment.RecurrenceInfo); pattern.Start = pattern.RecurrenceInfo.Start; pattern.End = pattern.RecurrenceInfo.End; var patternId = pattern.RecurrenceInfo.Id.ToString(); var occurrences = occurrenceCalculator.CalcOccurrences(interval, pattern); if (occurrences.Count > 0) { foreach (var app in occurrences.GetAppointments(interval)) { if(appointment.EndDate == null || appointment.StartDate == null) { continue; } var index = app.RecurrenceIndex; var duration = (appointment.EndDate.Value - appointment.StartDate.Value).TotalMinutes; var s = app.Start; var e = app.Start.AddMinutes(duration); var isOutOfInterval = s.AreInBetweenDates(e, start, end) == false; var isChangedOccurrence = changedOccurrences.Any(a => a.PatternId.Equals(patternId) && a.Index == index); var isDeletedOccurrence = deletedOccurrences.Any(a => a.PatternId.Equals(patternId) && a.Index == index); if (isOutOfInterval || isChangedOccurrence || isDeletedOccurrence) { continue; } var recurringAppointment = new SchedulerAppointment { AllDay = appointment.AllDay, CustomerList = appointment.CustomerList, Notice = appointment.Notice, EmployeeList = appointment.EmployeeList, EndDate = app.Start.AddMinutes(duration), FormerBookingSequenceOid = appointment.FormerBookingSequenceOid, IsPrivate = appointment.IsPrivate, Location = appointment.Location, Originator = appointment.Originator, RecurrenceInfo = app.RecurrenceInfo.ToXml(), ReminderInfo = appointment.ReminderInfo, ResourceList = appointment.ResourceList, StartDate = app.Start, Subject = appointment.Subject ?? string.Empty, Type = appointment.Type }; result.AddIfNotIn(recurringAppointment); } } } return result; } public static IEnumerable GetRecurrencesForIntervalFinder(List appointments, DateTime startDate, DateTime endDate, IReadOnlyCollection changedOccurrences, IReadOnlyCollection deletedOccurrences, bool skipWeekends) { var result = new List(); var dayCount = Convert.ToInt32(Math.Ceiling((endDate - startDate).TotalDays)); if(dayCount <= 0) { return result; } var startTimeBeforeEndTime = startDate >= startDate.MergeDatesByDate(endDate); if(startTimeBeforeEndTime || startDate.Date == endDate.Date) { return CalculateRecurrences(appointments, startDate, endDate, changedOccurrences, deletedOccurrences); } var start = startDate; for(var i = 0; i < dayCount; i++) { if(skipWeekends && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday)) { start = start.AddDays(1); continue; } var end = i == dayCount - 1 ? start.MergeDatesByDate(endDate) : start.AddDays(1).MergeDatesByDate(start.Date); start = i == 0 ? start : start.MergeDatesByDate(start.Date); var calculatedRecurrences = CalculateRecurrences(appointments, start, end, changedOccurrences, deletedOccurrences); result.AddRange(calculatedRecurrences); start = start.AddDays(1); } return result; } public static Dictionary> CreateFreeIntervals(List freeIntervals, DateTime intervalStart, DateTime intervalEnd, int buffer, int durationInMinutes) { var result = new Dictionary>(); if(intervalEnd < intervalStart) { (intervalEnd, intervalStart) = (intervalStart, intervalEnd); } if(durationInMinutes <= 0) { durationInMinutes = 60; } if(buffer < 30) { buffer = 30; } var possibleIntervals = CalculatePossibleIntervals(intervalStart, intervalEnd, buffer, durationInMinutes); var days = freeIntervals.Select(dateTimeSpan => dateTimeSpan.StartDate.Date).Distinct().ToList(); foreach(var possibleInterval in possibleIntervals) { foreach(var day in days) { var hasFreeSlot = freeIntervals.Any(freeInterval => { var isTimeEqual = freeInterval.EqualTimes(possibleInterval); var isDayEqual = freeInterval.StartDate.Date.Equals(day); return isTimeEqual && isDayEqual; }); if(result.Keys.Any(key => key.EqualTimes(possibleInterval))) { result[possibleInterval].AddAndIgnoreDuplicates(day, hasFreeSlot); } else { result.Add(possibleInterval, new Dictionary { { day, hasFreeSlot } }); } } } return result; } private static List CalculatePossibleIntervals(DateTime intervalStart, DateTime intervalEnd, int buffer, int durationInMinutes) { var possibleIntervals = new List(); if(intervalEnd < intervalStart) { (intervalEnd, intervalStart) = (intervalStart, intervalEnd); } if(durationInMinutes <= 0) { durationInMinutes = 60; } if(buffer < 30) { buffer = 30; } var start = intervalStart; var end = intervalStart.AddMinutes(durationInMinutes); while(end < intervalEnd) { if(possibleIntervals.Any(timeSpan => timeSpan.StartDate.CompareTime(start))) { start = start.AddMinutes(buffer); end = start.AddMinutes(durationInMinutes); continue; } possibleIntervals.Add(new DateTimeSpan(start, end, true)); start = start.AddMinutes(buffer); end = start.AddMinutes(durationInMinutes); } return possibleIntervals.OrderBy(dateTimeSpan => dateTimeSpan.StartDate.Hour).ThenBy(dateTimeSpan => dateTimeSpan.StartDate.Minute).ToList(); } } }