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?


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).


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.


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);
}

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);
}

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))
{

}

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[]


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
}

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......


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);
 }

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.


1

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


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);
}

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
 }

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);
        }

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);
    }

0

Oto moje 2 centy w 2020 roku.

Enumerable.Range(0, (endDate - startDate).Days + 1)
.ToList()
.Select(a => startDate.AddDays(a));

To jest niesamowite. 👍
— onekidney
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.