Comment parcourir une plage de dates?

203

Je ne sais même pas comment faire cela sans utiliser une solution horrible de type boucle / compteur. Voici le problème:

On me donne deux dates, une date de début et une date de fin et à un intervalle spécifié, je dois prendre des mesures. Par exemple: pour chaque date entre le 3/10/2009, tous les trois jours jusqu'au 26/03/2009, je dois créer une entrée dans une liste. Donc mes entrées seraient:

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

et ma sortie serait une liste qui a les dates suivantes:

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

Alors comment diable pourrais-je faire quelque chose comme ça? J'ai pensé à utiliser une boucle for qui itérerait entre tous les jours dans la plage avec un compteur séparé comme ceci:

int count = 0;

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

}

Mais il semble qu'il pourrait y avoir un meilleur moyen?

onekidney
la source
2
Je suppose que C # a une structure de données pour les dates que vous pouvez utiliser.
Anna

Réponses:

489

Eh bien, vous devrez les parcourir d'une manière ou d'une autre. Je préfère définir une méthode comme celle-ci:

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

Ensuite, vous pouvez l'utiliser comme ceci:

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

De cette manière, vous pouvez frapper tous les deux jours, tous les trois jours, uniquement les jours de la semaine, etc. Par exemple, pour revenir tous les trois jours en commençant par la date de «début», vous pouvez simplement appeler AddDays(3)dans la boucle au lieu de AddDays(1).

mqp
la source
18
Vous pouvez même ajouter un autre paramètre pour l'intervalle.
Justin Drury
Cela inclura la première date. Si vous ne voulez pas cela, changez simplement le 'var day = from.Date' en 'var day = from.Date.AddDays (dayInterval)'
SwDevMan81
3
Une solution vraiment sympa pour un problème de mots intéressant et réel. J'aime la façon dont cela montre quelques techniques utiles de la langue. Et cela me rappelle que la boucle for n'est pas seulement pour (int i = 0; ...) (-.
Audrius
9
Faire de cette méthode une extension de datetime pourrait le rendre encore meilleur.
MatteS
1
Regardez ma réponse pour les extensions pendant des jours et des mois;) Juste pour votre plaisir: D
Jacob Sobus
32

J'ai une Rangeclasse dans MiscUtil que vous pourriez trouver utile. Combiné avec les différentes méthodes d'extension, vous pouvez faire:

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

(Vous pouvez ou non vouloir exclure la fin - j'ai juste pensé que je le donnerais à titre d'exemple.)

Il s'agit essentiellement d'une forme prête à l'emploi (et plus polyvalente) de la solution de mquander.

Jon Skeet
la source
2
Certainement juste une question de goût, que vous aimiez que ces choses soient des méthodes d'extension ou non. ExcludeEnd()est mignon.
mqp
Vous pouvez, bien sûr, faire tout cela sans utiliser les méthodes d'extension. Ce sera juste beaucoup plus laid et difficile à lire IMO :)
Jon Skeet
1
Wow - quelle excellente ressource MiscUtil est - merci pour votre réponse!
onekidney
1
au cas où quelqu'un d'autre que moi aurait confondu DayInterval avec une structure / classe, il s'agit en fait d'un entier dans cet exemple. c'est bien sûr évident si vous lisez attentivement la question, ce que je n'ai pas fait.
marc.d
23

Pour votre exemple, vous pouvez essayer

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);
}
Adriaan Stander
la source
1
C'est la même chose à laquelle je pensais (bien que j'aime aussi la réponse de mquander ci-dessus) mais je ne peux pas comprendre comment vous obtenez un bel exemple de code affiché si rapidement!
TLiebe
3
Je pense que nous avons besoin de StartDate.AddDays (DayInterval); une fois dans cette boucle pas deux fois.
Abdul Saboor
17

Code de @mquander et @Yogurt The Wise utilisé dans les extensions:

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);
}
Jacob Sobus
la source
Quel est l'intérêt de EachDayToet EachMonthTo? Je pense que j'ai raté quelque chose ici.
Alisson
@Alisson ce sont les méthodes d'extension qui fonctionnent sur l'objet dateFrom :) Vous pouvez donc les utiliser déjà sur des objets DateTime créés plus couramment (en utilisant juste. Après l'instance). Plus d'informations sur les méthodes d'extension ici: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Jacob Sobus
9

1 an plus tard, que cela aide quelqu'un,

Cette version inclut un prédicat , pour être plus flexible.

Usage

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

Tous les jours jusqu'à mon anniversaire

var toBirthday = today.RangeTo(birthday);  

Chaque mois jusqu'à mon anniversaire, étape 2 mois

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

Chaque année jusqu'à mon anniversaire

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

Utilisez RangeFromplutôt

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

la mise en oeuvre

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

Suppléments

Vous pouvez lancer une exception si le fromDate > toDate, mais je préfère renvoyer une plage vide à la place[]

amd
la source
Wow - c'est vraiment complet. Merci Ahmad!
onekidney
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))
{

}
Gilbertc
la source
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
}
devnull
la source
Notez que cela n'inclut pas la date de début, car cela ajoute un jour à la première whileexécution.
John Washam
2

Selon le problème, vous pouvez essayer ceci ...

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

    startDate = startDate.AddDays(1);
}

Merci......

Rejwanul Reja
la source
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);
 }
Sunil Joshi
la source
1

Vous pourriez envisager d'écrire un itérateur à la place, ce qui vous permet d'utiliser une syntaxe de boucle 'for' normale comme '++'. J'ai recherché et trouvé une question similaire répondue ici sur StackOverflow qui donne des pointeurs pour rendre DateTime itérable.

REDace0
la source
1

Vous pouvez utiliser la DateTime.AddDays()fonction pour ajouter votre DayIntervalà StartDateet vérifier qu'il est inférieur à EndDate.

TLiebe
la source
0

il faut faire attention ici à ne pas manquer les dates où dans la boucle une meilleure solution serait.

cela vous donne la première date de date de début et de l'utiliser dans la boucle avant de l'incrémenter et il traitera toutes les dates, y compris la dernière date de fin, donc <= date de fin.

donc la réponse ci-dessus est la bonne.

while (startdate <= enddate)
{
    // do something with the startdate
    startdate = startdate.adddays(interval);
}
Robert Peter Bronstein
la source
0

vous pouvez utiliser ceci.

 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
 }
porya ras
la source
C'est vraiment succinct. Agréable!
onekidney
0

Itérer toutes les 15 minutes

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);
        }
McNiel Viray
la source
0

@ jacob-sobus et @mquander et @Yogurt ne sont pas tout à fait corrects .. Si j'ai besoin du lendemain, j'attends la plupart du temps 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);
    }
DantaliaN
la source
0

Voici mes 2 cents en 2020.

Enumerable.Range(0, (endDate - startDate).Days + 1)
.ToList()
.Select(a => startDate.AddDays(a));
mko
la source
C'est génial. 👍
onekidney