Come faccio a scorrere un intervallo di date?


198

Non sono nemmeno sicuro di come farlo senza usare una soluzione orribile per il tipo loop / counter. Ecco il problema:

Mi vengono date due date, una data di inizio e una data di fine e su un intervallo specificato devo prendere alcune misure. Ad esempio: per ogni data compresa tra il 3/10/2009 e il terzo giorno fino al 26/03/2009, devo creare una voce in un elenco. Quindi i miei input sarebbero:

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

e il mio output sarebbe un elenco che ha le seguenti date:

13/03/2009 16/03/2009 19/03/2009 22/03/2009 25/03/2009

Quindi come diamine avrei fatto qualcosa del genere? Ho pensato di usare un ciclo for che avrebbe ripetuto ogni giorno nell'intervallo con un contatore separato in questo modo:

int count = 0;

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

}

Ma sembra che ci potrebbe essere un modo migliore?


1
Immagino che C # abbia una struttura di dati per le date che potresti usare.
— Anna,

Risposte:


473

Bene, dovrai passare su di loro in un modo o nell'altro. Preferisco definire un metodo come questo:

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

Quindi puoi usarlo in questo modo:

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

In questo modo potresti colpire ogni altro giorno, ogni terzo giorno, solo nei giorni feriali, ecc. Ad esempio, per tornare ogni terzo giorno a partire dalla data di "inizio", puoi semplicemente chiamare AddDays(3)nel loop anziché AddDays(1).


18
Puoi anche aggiungere un altro parametro per l'intervallo.
— Justin Drury,

Questo sarà comprensivo del primo appuntamento. Se non lo desideri, modifica 'var day = from.Date' in 'var day = from.Date.AddDays (dayInterval)'
— SwDevMan81

3
Una soluzione davvero piacevole per un problema di parole interessante e reale. Mi piace come questo mostri alcune utili tecniche del linguaggio. E questo mi ricorda che for loop non è solo per (int i = 0; ...) (-.
— Audrius

9
Rendere questo metodo di estensione a datetime potrebbe renderlo ancora migliore.
— MatteS,

1
Guarda la mia risposta per le estensioni per giorni e mesi;) Solo per il tuo piacere: D
— Jacob Sobus,

32

Ho un corso Rangein MiscUtil che potresti trovare utile. In combinazione con i vari metodi di estensione, è possibile eseguire:

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

(Potresti o meno voler escludere la fine - ho solo pensato di fornirlo come esempio.)

Questa è fondamentalmente una forma già pronta (e più generica) della soluzione di mquander.


2
Certamente solo una questione di gusti, che ti piaccia o meno che queste cose siano metodi di estensione. ExcludeEnd()è carino.
— mqp

Ovviamente puoi fare tutto ciò senza usare i metodi di estensione. Leggere l'IMO sarà molto più brutto e difficile :)
— Jon Skeet,

1
Wow - che grande risorsa MiscUtil è - grazie per la tua risposta!
— onekidney,

1
nel caso in cui qualcun altro, tranne me, abbia scambiato DayInterval come struct / class, in realtà è un numero intero in questo esempio. è ovviamente ovvio leggere attentamente la domanda, cosa che non ho fatto.
— marc.d

23

Per il tuo esempio puoi provare

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
È la stessa cosa che stavo pensando (anche se mi piace anche la risposta di mquander sopra) ma non riesco a capire come ottenere un buon esempio di codice pubblicato così velocemente!
— TLiebe,

3
Penso che abbiamo bisogno di StartDate.AddDays (DayInterval); una volta in questo ciclo non due volte.
— Abdul Saboor,

15

Codice di @mquander e @Yogurt The Wise utilizzato nelle estensioni:

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

Qual è il punto di EachDayToe EachMonthTo? Penso di aver perso qualcosa qui.
— Alisson,

@Alisson questi sono i metodi di estensione che lavorano sull'oggetto dateFrom :) Quindi puoi usarli già su oggetti DateTime creati più fluenti (usando solo dopo istanza). Maggiori informazioni sui metodi di estensione qui: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
— Jacob Sobus,

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 anno dopo, possa aiutare qualcuno,

Questa versione include un predicato , per essere più flessibile.

uso

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

Tutti i giorni per il mio compleanno

var toBirthday = today.RangeTo(birthday);  

Ogni mese per il mio compleanno, Step 2 mesi

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

Ogni anno per il mio compleanno

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

Usa RangeFrominvece

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

Implementazione

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

extra

Potresti lanciare un'eccezione se il fromDate > toDate, ma preferisco invece restituire un intervallo vuoto[]


Wow - questo è davvero completo. Grazie 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
}

Si noti che ciò non include la data di inizio, poiché aggiunge un giorno la prima volta che whileviene eseguita.
— John Washam,

2

In base al problema puoi provare questo ...

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

    startDate = startDate.AddDays(1);
}

Grazie......


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

Potresti prendere in considerazione la possibilità di scrivere un iteratore, che ti consenta di utilizzare la normale sintassi del ciclo "for" come "++". Ho cercato e trovato una domanda simile con risposta qui su StackOverflow che fornisce indicazioni su come rendere DateTime iterabile.


1

È possibile utilizzare la DateTime.AddDays()funzione per aggiungere il proprio DayIntervala StartDatee verificare che sia inferiore a EndDate.


0

devi stare attento qui a non perdere le date in cui nel ciclo sarebbe una soluzione migliore.

questo ti dà la prima data di startdate e lo usa nel ciclo prima di incrementarlo e elaborerà tutte le date inclusa l'ultima data di enddate quindi <= enddate.

quindi la risposta sopra è quella corretta.

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

0

puoi usare questo.

 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
 }

È davvero succinto. Bello!
— onekidney,

0

Iterare ogni 15 minuti

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 e @mquander e @Yogurt non sono esattamente corretti .. Se avrò bisogno del giorno successivo aspetto soprattutto le 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

Ecco i miei 2 centesimi nel 2020.

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

Questo e spettacolare. 👍
— onekidney
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.