¿Cómo recorro un rango de fechas?


197

Ni siquiera estoy seguro de cómo hacer esto sin usar alguna solución horrible para el tipo de bucle / contador. Aquí está el problema:

Me dan dos fechas, una fecha de inicio y una fecha de finalización y en un intervalo específico necesito tomar alguna medida. Por ejemplo: para cada fecha entre el 10/03/2009 y el tercer día hasta el 26/03/2009, necesito crear una entrada en una Lista. Entonces mis entradas serían:

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

y mi salida sería una lista que tiene las siguientes fechas:

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

Entonces, ¿cómo diablos haría algo como esto? Pensé en usar un bucle for que iteraría entre cada día en el rango con un contador separado como este:

int count = 0;

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

}

Pero parece que podría haber una mejor manera?


1
Supongo que C # tiene una estructura de datos para fechas que podría usar.
— Anna

Respuestas:


470

Bueno, deberás recorrerlos de una forma u otra. Prefiero definir un 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;
}

Entonces puedes usarlo así:

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

De esta manera, podría presionar cada dos días, cada tercer día, solo entre semana, etc. Por ejemplo, para regresar cada tercer día a partir de la fecha de "inicio", puede simplemente llamar AddDays(3)al bucle en lugar de hacerlo AddDays(1).


18
Incluso puede agregar otro parámetro para el intervalo.
— Justin Drury el

Esto incluirá la primera fecha. Si no quiere eso, simplemente cambie 'var day = from.Date' a 'var day = from.Date.AddDays (dayInterval)'
— SwDevMan81

3
Una solución realmente agradable para un problema verbal interesante y real. Me gusta cómo esto muestra bastantes técnicas útiles del lenguaje. Y esto me recuerda que for loop no es solo para (int i = 0; ...) (-.
— Audrius

9
Hacer de este un método de extensión para datetime podría hacerlo aún mejor.
— Mattes

1
Mire mi respuesta para extensiones por días y meses;) Solo para su placer: D
— Jacob Sobus

31

Tengo una Rangeclase en MiscUtil que puedes encontrar útil. En combinación con los diversos métodos de extensión, puede hacer:

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

(Es posible que desee o no excluir el final, solo pensé en proporcionarlo como ejemplo).

Esto es básicamente una forma lista para usar (y de uso más general) de la solución de mquander.


2
Ciertamente solo es cuestión de gustos si le gustan tales cosas como métodos de extensión o no. ExcludeEnd()es lindo.
— mqp

Por supuesto, puede hacer todo eso sin usar los métodos de extensión. Será mucho más feo y más difícil de leer IMO :)
— Jon Skeet

1
Wow, qué gran recurso es MiscUtil, ¡gracias por tu respuesta!
— onekidney el

1
en caso de que alguien más que yo haya confundido DayInterval como una estructura / clase, en realidad es un número entero en este ejemplo. Por supuesto, es obvio si lees la pregunta con cuidado, lo cual no hice.
— marc.d

23

Para tu ejemplo puedes probar

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
Eso es lo mismo que estaba pensando (aunque también me gusta la respuesta de mquander arriba), ¡pero no puedo entender cómo se publica un buen ejemplo de código tan rápido!
— TLiebe

3
Creo que necesitamos StartDate.AddDays (DayInterval); una vez en este ciclo, no dos veces.
— Abdul Saboor

15

Código de @mquander y @Yogurt The Wise utilizado en extensiones:

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

¿Cuál es el punto de EachDayToy EachMonthTo? Creo que me perdí algo aquí.
— Alisson

@Alisson, esos son los métodos de extensión que funcionan en el objeto dateFrom :) Por lo tanto, puede usarlos ya en objetos DateTime creados con más fluidez (usando solo después de la instancia). Más información sobre los métodos de extensión aquí: 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 año después, que pueda ayudar a alguien,

Esta versión incluye un predicado , para ser más flexible.

Uso

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

Diariamente a mi cumpleaños

var toBirthday = today.RangeTo(birthday);  

Mensualmente para mi cumpleaños, paso 2 meses

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

Anual para mi cumpleaños

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

Utilizar en su RangeFromlugar

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

Implementación

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

Podría lanzar una excepción si el fromDate > toDate, pero prefiero devolver un rango vacío en su lugar[]


Wow, esto es realmente completo. Gracias 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
}

Tenga en cuenta que esto no incluye la fecha de inicio, ya que agrega un día la primera vez que se whileejecuta.
— John Washam

2

Según el problema, puedes probar esto ...

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

    startDate = startDate.AddDays(1);
}

Gracias......


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

Puede considerar escribir un iterador en su lugar, lo que le permite usar la sintaxis de bucle 'for' normal como '++'. Busqué y encontré una pregunta similar respondida aquí en StackOverflow, que ofrece sugerencias para hacer que DateTime sea iterable.


1

Puede usar la DateTime.AddDays()función para agregar su DayIntervalal StartDatey verificar para asegurarse de que sea menor que el EndDate.


0

debe tener cuidado aquí para no perder las fechas cuando en el bucle sería una mejor solución.

esto le da la primera fecha de inicio y la usa en el ciclo antes de incrementarla y procesará todas las fechas, incluida la última fecha de finalización, por lo tanto <= finalización.

entonces la respuesta anterior es la correcta.

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

0

Puedes usar esto.

 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
 }

Eso es realmente sucinto. ¡Agradable!
— onekidney

0

Iterar 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 y @mquander y @Yogurt no son exactamente correctos ... Si necesito al día siguiente, espero las 00:00 en su mayoría

    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

Aquí están mis 2 centavos en 2020.

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

Esto es asombroso 👍
— onekidney
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.