Используя мое расширение Scan
, основанное на операторе сканирования APL (например, Aggregate
, только оно возвращает промежуточные результаты):
public static class IEnumerableExt {
// TRes seedFn(T FirstValue)
// TRes combineFn(TRes PrevResult, T CurValue)
public static IEnumerable<TRes> Scan<T, TRes>(this IEnumerable<T> src, Func<T, TRes> seedFn, Func<TRes, T, TRes> combineFn) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prev = seedFn(srce.Current);
while (srce.MoveNext()) {
yield return prev;
prev = combineFn(prev, srce.Current);
}
yield return prev;
}
}
}
}
Тогда, предположив, что через два года вы имеете в виду 2 *365 дней, и при условии, что вы посчитаете даты начала и окончания каждого периода как часть общей суммы, этот LINQ найдет ответ:
var ans = src.Select(s => new { s.ID, s.Date1, s.Date2, Diff = (s.Date2.HasValue ? s.Date2.Value-s.Date1 : DateTime.Now.Date-s.Date1).TotalDays+1 })
.GroupBy(s => s.ID)
.Select(sg => new { ID = sg.Key, sg = sg.Scan(s => new { s.Date1, s.Date2, s.Diff, DiffAccum = s.Diff }, (res, s) => new { s.Date1, s.Date2, s.Diff, DiffAccum = res.DiffAccum + s.Diff }) })
.Select(IDsg => new { IDsg.ID, two_year_base = IDsg.sg.FirstOrDefault(s => s.DiffAccum > twoYears) ?? IDsg.sg.Last() })
.Select(s => new { s.ID, two_years = s.two_year_base.Date1.AddDays(twoYears-(s.two_year_base.DiffAccum - s.two_year_base.Diff)).Date });
Если ваши исходные данные не отсортированы в порядке Date1
,или ID
+ Date1
порядок, вам нужно будет добавить OrderBy
для сортировки по Date1
.
Объяснение: Сначала мы вычисляем рабочие дни (?), представленные каждой строкой, используя сегодня, еслиу нас нет окончания Date2
.Затем группируйте по идентификатору и работайте над каждой группой.Для каждого идентификатора мы вычисляем текущую сумму отработанных дней.Затем найдите Date1
, который следует за двухлетней отметкой, используя текущую сумму (DiffAccum
) и вычислите двухлетнюю дату из Date1
и оставшееся время, необходимое в этом периоде.
Если это возможноконкретный идентификатор может иметь много периодов, вы можете использовать другой вариант Scan
, ScanUntil
, который оценивает короткие замыкания на основе предиката.