Як переглянути цикл дат?


198

Я навіть не впевнений, як це зробити, не використовуючи якесь жахливе рішення для циклу / лічильника. Ось проблема:

Мені призначаються дві дати, дата початку та дата закінчення, і через визначений проміжок часу мені потрібно вжити певних дій. Наприклад: для кожної дати між 3.10.2009 кожного третього дня до 26.03.2009 мені потрібно створити запис у Списку. Тож мої вклади будуть:

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

і моїм результатом буде список, який має такі дати:

13.03.2009 16.03.2013 19.03.2009 22.03.2009 25.03.2009

То як, чорт, я зробив щось подібне? Я думав про використання циклу for, який би повторювався між кожним днем ​​у діапазоні, з окремим лічильником, таким чином:

int count = 0;

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

}

Але здається, що може бути кращий спосіб?


1
Я б здогадався, що C # має структуру даних для дат, які ви можете використовувати.
Анна

Відповіді:


471

Ну, вам потрібно буде так чи інакше перекинути їх. Я вважаю за краще визначити такий метод:

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

Тоді ви можете використовувати його так:

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

Таким чином, ви можете натискати через день, кожен третій день, лише будні дні тощо. Наприклад, повертатися кожен третій день, починаючи з дати "старту", ви можете просто зателефонувати AddDays(3)в цикл замість AddDays(1).


18
Можна навіть додати інший параметр для інтервалу.
Джастін Друрі

Це буде включати перше побачення. Якщо ви цього не хочете, просто змініть 'var day = from.Date' на 'var day = from.Date.AddDays (dayInterval)'
SwDevMan81

3
Справді приємне рішення цікавої та реальної проблеми зі словом. Мені подобається, як це показує досить багато корисних прийомів мови. І це нагадує мені, що цикл - це не тільки для (int i = 0; ...) (-.
Audrius

9
Якщо зробити цей метод розширення до дату, це може зробити його ще кращим.
MatteS

1
Подивіться на мою відповідь щодо продовження днів та місяців;) Лише для вашого задоволення: D
Яків Собус

31

У мене є Rangeклас у MiscUtil, який ви можете знайти корисним. У поєднанні з різними методами розширення ви можете:

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

(Ви можете або не хочете виключати кінець - я просто подумав, що надам це як приклад.)

Це в основному вже готова (і більш загальна) форма розчину мкандера.


2
Безумовно, лише питання смаку, чи любите ви, щоб такі речі були методами продовження чи ні. ExcludeEnd()мило.
mqp

Звичайно, ви можете зробити все це без використання методів розширення. Просто читати IMO :) буде набагато гірше і складніше:
Джон Скіт

1
Нічого собі - який чудовий ресурс MiscUtil - дякую за вашу відповідь!
onekidney

1
у випадку, якщо хтось інший, крім мене, сприйняв DayInterval як структура / клас, це фактично ціле число в цьому зразку. Звичайно, очевидно, якщо ви уважно читаєте питання, чого я не робив.
marc.d

23

Для вашого прикладу можна спробувати

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
Це те саме, що я думав (хоча мені також подобається відповідь mquander вище), але я не можу зрозуміти, як ви отримуєте хороший зразок коду, розміщений так швидко!
TLiebe

3
Я думаю, нам потрібен StartDate.AddDays (DayInterval); один раз у цю петлю не двічі.
Абдул Сабур

15

Код від @mquander та @Yogurt The Wise, що використовується в розширеннях:

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

У чому сенс EachDayToі EachMonthTo? Я думаю, що тут я щось пропустив.
Еліссон

@Alisson - це методи розширення, що працюють над об'єктом dateFrom :) Так що ви можете використовувати їх уже на створених об'єктах DateTime більш плавно (використовуючи щойно після інстанції). Більше про методи розширення тут: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Яків Собус

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

Через рік, чи може це допомогти комусь,

Ця версія включає присудок , щоб бути більш гнучким.

Використання

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

Щодня до мого дня народження

var toBirthday = today.RangeTo(birthday);  

Щомісяця до мого дня народження, крок 2 місяці

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

Щорічно до мого дня народження

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

Використовуйте RangeFromзамість цього

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

Впровадження

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

Екстри

Ви можете кинути виняток, якщо fromDate > toDate, але я вважаю за краще повернути порожній діапазон замість цього[]


Нічого собі - це справді всеосяжно. Дякую Ахмаде!
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
}

Зауважте, що це не включає дату початку, оскільки вона додає день при першому whileзапуску.
Джон

2

Відповідно до проблеми, ви можете спробувати це ...

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

    startDate = startDate.AddDays(1);
}

Дякую......


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

Ви можете замість цього записати ітератор, який дозволяє використовувати звичайний 'для' циклу синтаксису типу '++'. Я шукав і знайшов подібне запитання, відповів тут на StackOverflow, яке дає вказівки на те, щоб зробити DateTime ітерабельним.


1

Ви можете скористатися DateTime.AddDays()функцією, щоб додати свою DayIntervalдо StartDateі перевірити, чи не менше вона EndDate.


0

Ви повинні бути обережними тут, щоб не пропустити дати, коли в циклі було б краще рішення.

це дає вам першу дату запуску і використовувати його у циклі перед тим, як збільшити його, і він обробить усі дати, включаючи останню дату завершення, звідси <= завершення.

тому наведена відповідь є правильною.

while (startdate <= enddate)
{
    // do something with the startdate
    startdate = startdate.adddays(interval);
}

0

Ви можете використовувати це.

 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
 }

Це дійсно лаконічно. Приємно!
onekidney

0

Повторіть кожні 15 хвилин

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 і @mquander та @Yogurt не зовсім правильно. Якщо мені потрібно наступного дня, я чекаю 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

Ось мої 2 копійки в 2020 році.

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

Це круто. 👍
onekidney
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.