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.