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.