Como percorrer um período?


197

Eu nem tenho certeza de como fazer isso sem usar uma solução horrível para loop / contador. Aqui está o problema:

Recebi duas datas, uma data de início e uma data de término e, em um intervalo especificado, preciso tomar alguma ação. Por exemplo: para cada data entre 10/03/2009 a cada três dias até 26/03/2009, preciso criar uma entrada em uma Lista. Então, minhas entradas seriam:

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

e minha saída seria uma lista com as seguintes datas:

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

Então, como diabos eu faria algo assim? Pensei em usar um loop for que iteraria entre todos os dias no intervalo com um contador separado da seguinte forma:

int count = 0;

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

}

Mas parece que poderia haver uma maneira melhor?


1
Eu acho que o C # tem uma estrutura de dados para datas que você pode usar.
4159 Anna

Respostas:


470

Bem, você precisará passar por eles de uma maneira ou de outra. Eu prefiro definir um método como este:

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

Então você pode usá-lo assim:

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

Dessa maneira, você pode pressionar todos os dias, a cada três dias, apenas dias da semana, etc. Por exemplo, para retornar a cada terceiro dia começando com a data de "início", basta chamar AddDays(3)o loop em vez de AddDays(1).


18
Você pode até adicionar outro parâmetro para o intervalo.
Justin Drury

Isso incluirá o primeiro encontro. Se você não quiser isso, basta alterar o 'var day = from.Date' para 'var day = from.Date.AddDays (dayInterval)' #
SwDevMan81

3
Uma solução muito boa para um problema de palavras interessante e real. Eu gosto de como isso mostra algumas técnicas úteis da linguagem. E isso me faz lembrar que para o laço não é só para (int i = 0; ...) (-.
Audrius

9
Tornar este um método de extensão para data e hora pode torná-lo ainda melhor.
Mattes

1
Olhe para a minha resposta para as extensões dos dias e meses;) Apenas para o seu prazer: D
Jacob Sobus

31

Eu tenho uma Rangeaula no MiscUtil que você pode achar útil. Combinado com os vários métodos de extensão, você pode fazer:

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

(Você pode ou não querer excluir o final - pensei em fornecer o exemplo.)

Essa é basicamente uma forma de solução pronta para o uso do mquander (e mais genérica).


2
Certamente é apenas uma questão de gosto, se você gosta que essas coisas sejam métodos de extensão ou não. ExcludeEnd()é fofo.
Mqp 04/12/2009

Obviamente, você pode fazer tudo isso sem usar os métodos de extensão. Será apenas uma feia muito e mais difícil de ler IMO :)
Jon Skeet

1
Uau - que grande recurso o MiscUtil é - obrigado por sua resposta!
onekidney

1
no caso de alguém, exceto eu, confundir DayInterval como uma struct / classe, na verdade, é um número inteiro neste exemplo. é claro que é óbvio se você ler a pergunta com atenção, o que eu não fiz.
precisa saber é

23

Para o seu exemplo, você pode tentar

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
É a mesma coisa que eu estava pensando (embora eu goste da resposta do mquander acima também), mas não consigo descobrir como você consegue postar um bom exemplo de código tão rapidamente!
TLiebe

3
Eu acho que precisamos StartDate.AddDays (DayInterval); uma vez neste loop não duas vezes.
Abdul Saboor 12/02

15

Código de @mquander e @Yogurt The Wise usado em extensões:

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 é o sentido EachDayToe EachMonthTo? Eu acho que perdi alguma coisa aqui.
Alisson

@ Alisson, esses são os métodos de extensão que trabalham no objeto dateFrom :) Para que você já possa usá-los em objetos DateTime criados mais fluentes (usando apenas. Após instância). Mais sobre métodos de extensão aqui: 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 ano depois, pode ajudar alguém,

Esta versão inclui um predicado , para ser mais flexível.

Uso

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

Diariamente ao meu aniversário

var toBirthday = today.RangeTo(birthday);  

Mensalmente até meu aniversário, Etapa 2 meses

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

Anualmente ao meu aniversário

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

Use em RangeFromvez disso

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

Implementação

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

Extras

Você pode lançar uma exceção se o fromDate > toDate , mas eu prefiro retornar um intervalo vazio[]


Uau - isso é realmente abrangente. Obrigado Ahmad!
Onekidney 5/05

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
}

Observe que isso não inclui a data de início, pois adiciona um dia na primeira vez em que whileé executado.
John Washam

2

De acordo com o problema, você pode tentar isso ...

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

    startDate = startDate.AddDays(1);
}

obrigado......


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

Você pode escrever um iterador, que permite usar a sintaxe normal do loop 'for' como '++'. Pesquisei e encontrei uma pergunta semelhante respondida aqui no StackOverflow, que fornece dicas sobre como tornar o DateTime iterável.


1

Você pode usar a DateTime.AddDays()função para adicionar DayIntervalao StartDatee verificar se ele é menor que o EndDate.


0

você deve ter cuidado aqui para não perder as datas em que, no circuito, seria uma solução melhor.

isso fornece a primeira data da data de início e a utiliza no loop antes de incrementá-la. Ele processará todas as datas, incluindo a última data da data final, portanto <= data final.

então a resposta acima é a correta.

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

0

Você pode usar isso.

 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
 }

Isso é realmente sucinto. Agradável!
Onekidney 28/03

0

Iterar a cada 15 minutos

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 não estão exatamente corretos .. Se eu precisar no dia seguinte, espero 00:00, na maior parte do tempo

    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

Aqui estão meus 2 centavos em 2020.

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

Isso é incrível. One
onekidney 4/03
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.