public static TInterval[] MergeIntervals <TInterval>(params TInterval[] sourceIntervals) where TInterval : IRegisteredInterval { var sortedIntervals = sourceIntervals .OrderByDescending(oio => oio.StartTS.HasValue ? oio.StartTS.Value : DateTime.MinValue) .ThenByDescending(oio => oio.RegistrationDate.Value) .ToArray(); var ret = new List <TInterval>(); if (sortedIntervals.Length > 0) { ret.Add(sortedIntervals.First()); } foreach (var interval in sortedIntervals.Skip(1)) { if (VirkningType.ToStartDateTimeOrMinValue(interval.StartTS) < VirkningType.ToStartDateTimeOrMinValue(ret.First().StartTS)) { if (!interval.EndTS.HasValue || interval.EndTS.Value > VirkningType.ToStartDateTimeOrMinValue(ret.First().StartTS)) { interval.EndTS = VirkningType.ToStartDateTimeOrMinValue(ret.First().StartTS); } ret.Insert(0, interval); } else { // Cannot insert any previous intervals if latest inserted interval has no start date } } return(ret.ToArray()); }
public static TInterval[] CreateFromData <TInterval>(IQueryable <ITimedType> dataObjects, DataTypeTags[] allTags) where TInterval : Interval, new() { dataObjects = dataObjects.Where(o => allTags.Contains(o.Tag)); // Filter objects based on correction markers dataObjects = CorrectionMarker.Filter(dataObjects); // In case we have no correction records for history, check for possible overwrites dataObjects = Overwrite.Filter(dataObjects); // TODO: Also filter out objects that have StartDate<EndDate and an uncertainty marker on either dates (Like some HistoricalChurchInformation records) // sort by start date var sortedByStartDate = dataObjects .Select(o => new { StartTS = VirkningType.ToStartDateTimeOrMinValue(o.ToStartTS()), Object = o }) .OrderBy(o => o.StartTS) .ToArray(); var ret = new List <TInterval>(); // Group by start date foreach (var dataObject in sortedByStartDate) { var currentInterval = ret.LastOrDefault(); bool sameInterval = currentInterval != null && ( (dataObject.StartTS == currentInterval.StartTS.Value) || ( currentInterval.StartDate.Value == currentInterval.StartTS.Value && dataObject.StartTS.Date == currentInterval.StartDate ) ); if (sameInterval) { currentInterval.Data.Add(dataObject.Object); if (currentInterval.StartDate.Value == currentInterval.StartTS.Value) { currentInterval.StartTS = dataObject.StartTS; } } else { currentInterval = new TInterval() { StartTS = dataObject.StartTS, EndTS = null, Data = new List <ITimedType>(new ITimedType[] { dataObject.Object }) }; ret.Add(currentInterval); } } // Filter to only intervals that either have a non null start or that have objects that can mark interval start with null value ret = ret.Where(intvl => intvl.Data.Where(o => o.ToStartTS().HasValue || CreateIntervalIfStartTsIsNullAttribute.GetValue(o.GetType())).FirstOrDefault() != null).ToList(); // Fill the missing information foreach (var currentInterval in ret) { var missingTags = allTags.Where(tag => currentInterval.Data.Where(o => o.Tag == tag).Count() == 0); foreach (var missingTag in missingTags) { var missingTagObject = dataObjects.Where( o => o.Tag == missingTag && Utilities.Dates.DateRangeIncludes(o.ToStartTS(), o.ToEndTS(), currentInterval.StartTS) ).FirstOrDefault(); if (missingTagObject != null) { currentInterval.Data.Add(missingTagObject); } } } // Now set the end dates for (int i = 0; i < ret.Count; i++) { var currentInterval = ret[i]; if (currentInterval.StartTS == DateTime.MinValue) { currentInterval.StartTS = null; } // TODO: Do now allow Protection intervals to set end date for the last interval. Does that differ if the end date is in the future? currentInterval.EndTS = currentInterval.Data.Where(o => o.ToEndTS().HasValue).Select(o => o.ToEndTS()).Min(); if (i < ret.Count - 1) { var nextInterval = ret[i + 1]; if (currentInterval.EndTS == null || currentInterval.EndTS > nextInterval.StartTS) { currentInterval.EndTS = nextInterval.StartTS; } } } // Now extend the last interval if it was closed by a type that cannot close open ends var lastInterval = ret.LastOrDefault(); if (lastInterval != null && lastInterval.EndTS.HasValue) { var suggestedData = lastInterval.Data.Where(dataObject => CannotCloseOpenEndIntervalsAttribute.GetValue(dataObject.GetType()) == false).ToList(); var suggestedEndDate = suggestedData.Where(d => d.ToEndTS().HasValue).Select(d => d.ToEndTS()).Max(); if ( suggestedData.Count < lastInterval.Data.Count && (!suggestedEndDate.HasValue || suggestedEndDate.Value > lastInterval.EndTS.Value) ) { var suggestedInterval = new TInterval() { Data = suggestedData, StartTS = lastInterval.EndTS, EndTS = suggestedEndDate }; ret.Add(suggestedInterval); } } return(ret.ToArray()); }