Jak przejść przez zakres dat?

197

Nie jestem nawet pewien, jak to zrobić bez użycia jakiegoś okropnego rozwiązania dla pętli / licznika. Oto problem:

Dostaję dwie daty, datę początkową i datę końcową, aw określonych odstępach czasu muszę podjąć pewne działania. Na przykład: dla każdej daty od 3/10/2009 co trzeci dzień do 26.03.2009 muszę utworzyć pozycję na liście. Więc moje dane wejściowe to:

DateTime StartDate = "3/10/2009";
DateTime EndDate = "3/26/2009";
int DayInterval = 3;

a moim wynikiem byłaby lista z następującymi datami:

3/13/2009 3/16/2009 3/19/2009 3/22/2009 3/25/2009

Więc jak do cholery miałbym zrobić coś takiego? Pomyślałem o użyciu pętli for, która będzie iterować między każdym dniem w zakresie z osobnym licznikiem w ten sposób:

int count = 0;

for(int i = 0; i < n; i++)
{
     count++;
     if(count >= DayInterval)
     {
          //take action
          count = 0;
     }

}

Ale wydaje się, że może być lepszy sposób?

onekidney
źródło
1
Domyślam się, że C # ma strukturę danych dla dat, których można użyć.
Anna,

Odpowiedzi:

470

Cóż, musisz je zapętlić w taki czy inny sposób. Wolę zdefiniować taką metodę:

public IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
    for(var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
        yield return day;
}

Następnie możesz użyć tego w następujący sposób:

foreach (DateTime day in EachDay(StartDate, EndDate))
    // print it or whatever

W ten sposób możesz uderzać co drugi dzień, co trzeci dzień, tylko w dni powszednie itp. Na przykład, aby wrócić co trzeci dzień, zaczynając od daty „rozpoczęcia”, możesz po prostu zadzwonić AddDays(3)w pętli zamiast AddDays(1).

mqp
źródło
18
Możesz nawet dodać kolejny parametr dla przedziału.
Justin Drury,
Będzie to obejmowało pierwszą randkę. Jeśli nie chcesz tego, po prostu zmień „var day = from.Date” na „var day = from.Date.AddDays (dayInterval)”
SwDevMan81
3
Naprawdę fajne rozwiązanie interesującego i prawdziwego problemu słownego. Podoba mi się, jak pokazuje to kilka przydatnych technik języka. I to przypomina mi, że for loop jest nie tylko dla (int i = 0; ...) (-.
Audrius
9
Uczynienie z tej metody rozszerzenia daty i godziny może uczynić ją jeszcze lepszą.
MatteS
1
Spójrz na moją odpowiedź na przedłużenia dla dni i miesięcy;) Tylko dla twojej przyjemności: D
Jacob Sobus
31

Mam Rangezajęcia w MiscUtil, które mogą ci się przydać. W połączeniu z różnymi metodami rozszerzenia możesz:

foreach (DateTime date in StartDate.To(EndDate).ExcludeEnd()
                                   .Step(DayInterval.Days())
{
    // Do something with the date
}

(Możesz lub nie chcesz wykluczyć koniec - pomyślałem, że podam go jako przykład).

Jest to w zasadzie gotowa (i bardziej ogólna) forma rozwiązania mquandera.

Jon Skeet
źródło
2
Z pewnością kwestia gustu, czy lubisz takie metody, czy nie. ExcludeEnd()jest uroczy.
mqp
Możesz oczywiście to wszystko zrobić bez użycia metod rozszerzenia. Będzie o wiele brzydsze i trudniejsze do odczytania IMO :)
Jon Skeet
1
Wow - co za świetny zasób MiscUtil - dzięki za odpowiedź!
onekidney,
1
na wypadek, gdyby ktokolwiek inny oprócz mnie pomylił DayInterval jako struct / class, w rzeczywistości jest to liczba całkowita w tej próbce. jest oczywiście oczywiste, jeśli uważnie przeczytasz pytanie, czego nie zrobiłem.
marc. D.
23

Na przykład możesz spróbować

DateTime StartDate = new DateTime(2009, 3, 10);
DateTime EndDate = new DateTime(2009, 3, 26);
int DayInterval = 3;

List<DateTime> dateList = new List<DateTime>();
while (StartDate.AddDays(DayInterval) <= EndDate)
{
   StartDate = StartDate.AddDays(DayInterval);
   dateList.Add(StartDate);
}
Adriaan Stander
źródło
1
To samo myślałem (chociaż również podoba mi się odpowiedź mquandera powyżej), ale nie mogę zrozumieć, jak szybko otrzymałeś ładną próbkę kodu!
TLiebe,
3
Myślę, że potrzebujemy StartDate.AddDays (DayInterval); raz w tej pętli nie dwa razy.
Abdul Saboor
15

Kod z @mquander i @Yogurt The Wise używany w rozszerzeniach:

public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
    for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
        yield return day;
}

public static IEnumerable<DateTime> EachMonth(DateTime from, DateTime thru)
{
    for (var month = from.Date; month.Date <= thru.Date || month.Month == thru.Month; month = month.AddMonths(1))
        yield return month;
}

public static IEnumerable<DateTime> EachDayTo(this DateTime dateFrom, DateTime dateTo)
{
    return EachDay(dateFrom, dateTo);
}

public static IEnumerable<DateTime> EachMonthTo(this DateTime dateFrom, DateTime dateTo)
{
    return EachMonth(dateFrom, dateTo);
}
Jacob Sobus
źródło
Jaki jest sens EachDayToi EachMonthTo? Myślę, że coś mi umknęło.
Alisson
@Alisson są to metody rozszerzenia działające na obiekcie dateFrom :) Więc możesz ich używać już na utworzonych obiektach DateTime bardziej płynnie (używając tylko. Po wystąpieniu). Więcej informacji na temat metod rozszerzenia tutaj: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Jacob
8
DateTime startDate = new DateTime(2009, 3, 10);
DateTime stopDate = new DateTime(2009, 3, 26);
int interval = 3;

for (DateTime dateTime=startDate;
     dateTime < stopDate; 
     dateTime += TimeSpan.FromDays(interval))
{

}
Gilbertc
źródło
8

1 rok później, czy może komuś pomóc,

Ta wersja zawiera predykat , aby być bardziej elastycznym.

Stosowanie

var today = DateTime.UtcNow;
var birthday = new DateTime(2018, 01, 01);

Codziennie na moje urodziny

var toBirthday = today.RangeTo(birthday);  

Co miesiąc do moich urodzin, krok 2 miesiące

var toBirthday = today.RangeTo(birthday, x => x.AddMonths(2));

Co roku na moje urodziny

var toBirthday = today.RangeTo(birthday, x => x.AddYears(1));

Użyj RangeFromzamiast tego

// same result
var fromToday = birthday.RangeFrom(today);
var toBirthday = today.RangeTo(birthday);

Realizacja

public static class DateTimeExtensions 
{

    public static IEnumerable<DateTime> RangeTo(this DateTime from, DateTime to, Func<DateTime, DateTime> step = null)
    {
        if (step == null)
        {
            step = x => x.AddDays(1);
        }

        while (from < to)
        {
            yield return from;
            from = step(from);
        }
    }

    public static IEnumerable<DateTime> RangeFrom(this DateTime to, DateTime from, Func<DateTime, DateTime> step = null)
    {
        return from.RangeTo(to, step);
    }
}

Dodatki

Możesz zgłosić wyjątek, jeśli fromDate > toDate, ale wolę zamiast tego zwrócić pusty zakres[]

amd
źródło
Wow - to naprawdę kompleksowe. Dzięki Ahmad!
onekidney
3
DateTime startDate = new DateTime(2009, 3, 10);
DateTime stopDate = new DateTime(2009, 3, 26);
int interval = 3;

while ((startDate = startDate.AddDays(interval)) <= stopDate)
{
    // do your thing
}
diabelnie
źródło
Pamiętaj, że nie obejmuje to daty rozpoczęcia, ponieważ dodaje dzień przy pierwszym whileuruchomieniu.
John Washam
2

W zależności od problemu możesz spróbować tego ...

// looping between date range    
while (startDate <= endDate)
{
    //here will be your code block...

    startDate = startDate.AddDays(1);
}

dzięki......

Rejwanul Reja
źródło
2
DateTime begindate = Convert.ToDateTime("01/Jan/2018");
DateTime enddate = Convert.ToDateTime("12 Feb 2018");
 while (begindate < enddate)
 {
    begindate= begindate.AddDays(1);
    Console.WriteLine(begindate + "  " + enddate);
 }
Sunil Joshi
źródło
1

Możesz zamiast tego napisać iterator, który pozwala używać normalnej składni pętli „for”, takiej jak „++”. Przeszukałem i znalazłem podobne pytanie , na które udzielono odpowiedzi na StackOverflow, które daje wskazówki na temat iteracji DateTime.

REDace0
źródło
1

Możesz użyć tej DateTime.AddDays()funkcji, aby dodać swoją DayIntervaldo StartDatei sprawdzić, czy jest mniejsza niż EndDate.

TLiebe
źródło
0

musisz uważać tutaj, aby nie przegapić dat, kiedy w pętli byłoby lepszym rozwiązaniem.

daje to pierwszą datę rozpoczęcia i użyj jej w pętli przed zwiększeniem, a przetworzy wszystkie daty, w tym ostatnią datę zakończenia, stąd <= data zakończenia.

więc powyższa odpowiedź jest poprawna.

while (startdate <= enddate)
{
    // do something with the startdate
    startdate = startdate.adddays(interval);
}
Robert Peter Bronstein
źródło
0

możesz tego użyć.

 DateTime dt0 = new DateTime(2009, 3, 10);
 DateTime dt1 = new DateTime(2009, 3, 26);

 for (; dt0.Date <= dt1.Date; dt0=dt0.AddDays(3))
 {
    //Console.WriteLine(dt0.Date.ToString("yyyy-MM-dd"));
    //take action
 }
porya ras
źródło
To naprawdę zwięzłe. Miły!
onekidney
0

Iteruj co 15 minut

DateTime startDate = DateTime.Parse("2018-06-24 06:00");
        DateTime endDate = DateTime.Parse("2018-06-24 11:45");

        while (startDate.AddMinutes(15) <= endDate)
        {

            Console.WriteLine(startDate.ToString("yyyy-MM-dd HH:mm"));
            startDate = startDate.AddMinutes(15);
        }
McNiel Viray
źródło
0

@ jacob-sobus i @mquander i @Yogurt nie są dokładnie poprawne .. Jeśli potrzebuję następnego dnia, najczęściej czekam 00:00

    public static IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
    {
        for (var day = from.Date; day.Date <= thru.Date; day = day.NextDay())
            yield return day;
    }

    public static IEnumerable<DateTime> EachMonth(DateTime from, DateTime thru)
    {
        for (var month = from.Date; month.Date <= thru.Date || month.Year == thru.Year && month.Month == thru.Month; month = month.NextMonth())
            yield return month;
    }

    public static IEnumerable<DateTime> EachYear(DateTime from, DateTime thru)
    {
        for (var year = from.Date; year.Date <= thru.Date || year.Year == thru.Year; year = year.NextYear())
            yield return year;
    }

    public static DateTime NextDay(this DateTime date)
    {
        return date.AddTicks(TimeSpan.TicksPerDay - date.TimeOfDay.Ticks);
    }

    public static DateTime NextMonth(this DateTime date)
    {
        return date.AddTicks(TimeSpan.TicksPerDay * DateTime.DaysInMonth(date.Year, date.Month) - (date.TimeOfDay.Ticks + TimeSpan.TicksPerDay * (date.Day - 1)));
    }

    public static DateTime NextYear(this DateTime date)
    {
        var yearTicks = (new DateTime(date.Year + 1, 1, 1) - new DateTime(date.Year, 1, 1)).Ticks;
        var ticks = (date - new DateTime(date.Year, 1, 1)).Ticks;
        return date.AddTicks(yearTicks - ticks);
    }

    public static IEnumerable<DateTime> EachDayTo(this DateTime dateFrom, DateTime dateTo)
    {
        return EachDay(dateFrom, dateTo);
    }

    public static IEnumerable<DateTime> EachMonthTo(this DateTime dateFrom, DateTime dateTo)
    {
        return EachMonth(dateFrom, dateTo);
    }

    public static IEnumerable<DateTime> EachYearTo(this DateTime dateFrom, DateTime dateTo)
    {
        return EachYear(dateFrom, dateTo);
    }
Dantalia N.
źródło
0

Oto moje 2 centy w 2020 roku.

Enumerable.Range(0, (endDate - startDate).Days + 1)
.ToList()
.Select(a => startDate.AddDays(a));
mko
źródło
To jest niesamowite. 👍
onekidney