¿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?

onekidney
fuente
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).

mqp
fuente
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.

Jon Skeet
fuente
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);
}
Adriaan Stander
fuente
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);
}
Jacob Sobus
fuente
¿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))
{

}
Gilbertc
fuente
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[]

amd
fuente
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
}
devnull
fuente
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......

Rejwanul Reja
fuente
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
fuente
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.

REDace0
fuente
1

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

TLiebe
fuente
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);
}
Robert Peter Bronstein
fuente
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
 }
porya ras
fuente
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);
        }
McNiel Viray
fuente
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);
    }
DantaliaN
fuente
0

Aquí están mis 2 centavos en 2020.

Enumerable.Range(0, (endDate - startDate).Days + 1)
.ToList()
.Select(a => startDate.AddDays(a));
mko
fuente
Esto es asombroso 👍
onekidney