2012-04-13 15 views
5

Jeśli mam okres czasu, powiedzmy DateFrom i DateTo i mam listę dat, daty te będą podzielonymi datami. Na przykład:Podziel okres na wiele okresów

DateTime dateFrom = new DateTime(2012, 1, 1); 
DateTime dateTo = new DateTime(2012, 12, 31); 

List<DateTime> splitDates = new List<DateTime> 
    { 
     new DateTime(2012,2,1), 
     new DateTime(2012,5,1), 
     new DateTime(2012,7,1), 
     new DateTime(2012,11,1), 
    }; 

List<Tuple<DateTime, DateTime>> periods = SplitDatePeriod(dateFrom, dateTo, splitDates); 

Chcę wynikiem będzie wykaz okresów, więc dla poprzedniego przykładu wynik powinien być:

(01/01/2012 - 01/02/2012) 
(02/02/2012 - 01/05/2012) 
(02/05/2012 - 01/07/2012) 
(02/07/2012 - 01/11/2012) 
(02/11/2012 - 31/12/2012) 

już napisał metodę, aby to zrobić:

List<Tuple<DateTime, DateTime>> SplitDatePeriod(DateTime dateFrom, DateTime dateTo, List<DateTime> splitDates) 
{ 
    var resultDates = new List<Tuple<DateTime, DateTime>>(); 

    // sort split dates 
    List<DateTime> _splitDates = splitDates.OrderBy(d => d.Date).ToList(); 

    DateTime _curDate = dateFrom.Date; 
    for (int i = 0; i <= _splitDates.Count; ++i) 
    { 
     DateTime d = (i < _splitDates.Count) ? _splitDates[i] : dateTo; 

     // skip dates out of range 
     if (d.Date < dateFrom.Date || d.Date > dateTo.Date) 
      continue; 

     resultDates.Add(Tuple.Create(_curDate, d)); 

     _curDate = d.AddDays(1); 
    } 
    return resultDates; 
} 

Pytanie
to wygląda tak brzydki, Czy istnieje bardziej schludny i krótszy sposób to zrobić? używając Linq może?

+5

lepiej pasuje do [Code Review] (http://codereview.stackexchange.com/) –

+0

jak 'TimeSpan'? –

+3

@RitchMelton Wierzę, że 'TimeSpan' nie ma tu nic do roboty ... –

Odpowiedz

4

to taki, który pracuje i opiekuje niektórych przypadkach krawędziowych również:

var realDates = splitDates 
    .Where(d => d > dateFrom && d < dateTo) 
    .Concat(new List<DateTime>() {dateFrom.AddDays(-1), dateTo}) 
    .Select(d => d.Date) 
    .Distinct() 
    .OrderBy(d => d) 
    .ToList(); 

// now we have    (start - 1) -- split1 -- split2 -- split3 -- end 
// we zip it against   split1 -- split2 -- split3 -- end 
// and produce  start,split1 -- split1+1,split2 -- split2+1,split3 -- split3+1,end 

realDates.Zip(realDates.Skip(1), (a, b) => Tuple.Create(a.AddDays(1), b)); 
+0

Podczas gdy większość odpowiedzi jest poprawna, Twoja jest najlepsza, a komentarze są dobre. –

+0

Naprawiłem pierwszą linię, aby wykluczyć duplikaty. Jeśli dwie lub więcej daty podziału zostaną zduplikowane, wynik jest nieprawidłowy. –

+1

Co zawiera AddDays? Również - dokumentacja Enumerable.Distinct mówi, że nie powinniśmy polegać na nim, zachowując porządek. –

0

Podczas LB jest poprawny i to prawdopodobnie należy Code Review, czułam jak przy pęknięcia w tym:

Biorąc pod uwagę Twój pierwszy kod bloku, poniższy kod zrobi co prosisz:

// List of all dates in order that are valid 
var dateSegments = new [] { dateFrom, dateTo } 
    .Concat(splitDates.Where(x => x > dateFrom && x < dateTo)) 
    .OrderBy(x => x) 
    .ToArray(); 

List<Tuple<DateTime, DateTime>> results = new List<Tuple<DateTime, DateTime>>(); 
for(var i = 0; i < dateSegments.Length - 1; i++) 
{ 
    results.Add(new Tuple<DateTime, DateTime>(dateSegments[i], dateSegments[i+1])); 
} 
0

można to zrobić tak:

List<DateTime> split = 
    splitDates.Where(d => d >= dateFrom && d <= dateTo).ToList(); 

List<Tuple<DateTime, DateTime>> periods = 
    Enumerable.Range(0, split.Count + 1) 
    .Select(i => new Tuple<DateTime, DateTime>(
    i == 0 ? dateFrom : split[i - 1].AddDays(1), 
    i == split.Count ? dateTo : split[i] 
)) 
    .ToList(); 
0

Jeśli umieścisz wszystkie daty w jednym liście, to powinno działać:

var dates = new List<DateTime> 
     { 
      new DateTime(2012, 1, 1), 
      new DateTime(2012, 2, 1), 
      new DateTime(2012, 5, 1), 
      new DateTime(2012, 7, 1), 
      new DateTime(2012, 11, 1), 
      new DateTime(2012, 12, 31) 
     }; 

var z = dates.Zip(dates.Skip(1), (f, s) => Tuple.Create(f.Equals(dates[0]) ? f : f.AddDays(1), s)); 
0
List<DateTime> splitDates = GetSplitDates(); 
DateTime dateFrom = GetDateFrom(); 
DateTime dateTo = GetDateTo(); 

List<DateTime> edges = splitDates 
    .Where(d => dateFrom < d && d < dateTo) 
    .Concat(new List<DateTime>() {dateFrom, dateTo}) 
    .Distinct() 
    .OrderBy(d => d) 
    .ToList(); 

//must be at least one edge since we added at least one unique date to this. 
DateTime currentEdge = edges.First(); 

List<Tuple<DateTime, DateTime>> resultItems = new List<Tuple<DateTime, DateTime>>(); 

foreach(DateTime nextEdge in edges.Skip(1)) 
{ 
    resultItems.Add(Tuple.Create(currentEdge, nextEdge)); 
    currentEdge = nextEdge; 
} 

return resultItems; 
0

Zrobiłem to proste, aby uzyskać dniach między DATERANGE usług.

model obiektowy

public class DateObjectClass 
{ 
    public DateTime startDate { get; set; } 
    public DateTime endDate { get; set; } 
} 

Działanie:

public List<DateObjectClass> SplitDateRangeByDates(DateTime start,DateTime end) 
    { 
     List<DateObjectClass> datesCollection = new List<DateObjectClass>(); 
     DateTime startOfThisPeriod = start; 
     while (startOfThisPeriod < end) 
     { 
      DateTime endOfThisPeriod =new DateTime(startOfThisPeriod.Year,startOfThisPeriod.Month,startOfThisPeriod.Day,23,59,59); 
      endOfThisPeriod = endOfThisPeriod < end ? endOfThisPeriod : end; 
      datesCollection.Add(new DateObjectClass() { startDate= startOfThisPeriod ,endDate =endOfThisPeriod}); 
      startOfThisPeriod = endOfThisPeriod; 
      startOfThisPeriod = startOfThisPeriod.AddSeconds(1); 
     } 

     return datesCollection; 
    }