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


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.