Vali-Range
IValiRange proporciona un conjunto completo de operaciones con rangos de fechas construido sobre el struct DateRange. Admite creación, consultas, operaciones de conjuntos (unión, intersección), enumeración, división e iteración de días laborables con conciencia del calendario.
Instalación
dotnet add package Vali-Range
Registro
builder.Services.AddValiRange();
// o mediante el meta-paquete:
builder.Services.AddValiTempo();
Struct DateRange
DateRange es un tipo de valor que representa un intervalo de fechas cerrado [Start, End].
var range = new DateRange(new DateTime(2025, 1, 1), new DateTime(2025, 3, 31));
Console.WriteLine(range.Start); // 2025-01-01
Console.WriteLine(range.End); // 2025-03-31
Console.WriteLine(range.Duration); // TimeSpan de 90 días
Propiedades:
| Propiedad | Tipo | Descripción |
|---|---|---|
Start | DateTime | Fecha de inicio inclusiva |
End | DateTime | Fecha de fin inclusiva |
Duration | TimeSpan | End - Start |
TotalDays | int | Número de días calendario |
IsEmpty | bool | Verdadero cuando Start == End o el rango es inválido |
API de IValiRange
Create
Crea un DateRange a partir de fechas de inicio y fin:
DateRange r = range.Create(new DateTime(2025, 1, 1), new DateTime(2025, 12, 31));
Firma: DateRange Create(DateTime start, DateTime end)
LastUnits
Crea un rango que cubre las últimas N unidades antes de una fecha de referencia:
DateRange last30Days = range.LastUnits(30, TimeUnit.Days);
DateRange last3Months = range.LastUnits(3, TimeUnit.Months, referenceDate: DateTime.Today);
Firma: DateRange LastUnits(int count, TimeUnit unit, DateTime? referenceDate = null)
Nota:
amountdebe ser mayor que 0. Pasar 0 o un valor negativo lanzaArgumentException.
NextUnits
Crea un rango que cubre las próximas N unidades después de una fecha de referencia:
DateRange next7Days = range.NextUnits(7, TimeUnit.Days);
DateRange nextQuarter = range.NextUnits(1, TimeUnit.Months, count: 3);
Firma: DateRange NextUnits(int count, TimeUnit unit, DateTime? referenceDate = null)
Nota:
amountdebe ser mayor que 0. Pasar 0 o un valor negativo lanzaArgumentException.
ThisMonth / ThisWeek / ThisQuarter / ThisYear
Métodos de fábrica convenientes para rangos comunes:
DateRange month = range.ThisMonth();
DateRange week = range.ThisWeek();
DateRange quarter = range.ThisQuarter();
DateRange year = range.ThisYear();
Contains
Comprueba si una fecha u otro rango está completamente contenido:
bool has = range.Contains(myRange, new DateTime(2025, 6, 15));
bool sub = range.Contains(myRange, subRange);
Firma:
bool Contains(DateRange range, DateTime date)bool Contains(DateRange range, DateRange other)
Overlaps
Comprueba si dos rangos se superponen (comparten al menos un día):
bool overlapping = range.Overlaps(rangeA, rangeB);
Firma: bool Overlaps(DateRange a, DateRange b)
IsContainedBy
Comprueba si el primer rango está completamente dentro del segundo:
bool inside = range.IsContainedBy(inner, outer);
Firma: bool IsContainedBy(DateRange range, DateRange container)
Intersection
Devuelve la parte superpuesta de dos rangos (o un rango vacío si no se superponen):
DateRange overlap = range.Intersection(rangeA, rangeB);
// → DateRange que cubre los días compartidos
Firma: DateRange Intersection(DateRange a, DateRange b)
Union
Fusiona dos rangos en el rango más pequeño que cubre ambos (no necesitan superponerse):
DateRange merged = range.Union(rangeA, rangeB);
Firma: DateRange Union(DateRange a, DateRange b)
Expand
Extiende un rango N unidades en uno o ambos extremos:
DateRange expanded = range.Expand(myRange, 7, TimeUnit.Days);
// Agrega 7 días en cada extremo
DateRange expandedStart = range.Expand(myRange, 7, TimeUnit.Days, expandStart: true, expandEnd: false);
Firma: DateRange Expand(DateRange range, int amount, TimeUnit unit, bool expandStart = true, bool expandEnd = true)
Shrink
Contrae un rango N unidades desde uno o ambos extremos:
DateRange shrunk = range.Shrink(myRange, 1, TimeUnit.Weeks);
Firma: DateRange Shrink(DateRange range, int amount, TimeUnit unit, bool shrinkStart = true, bool shrinkEnd = true)
Shift
Mueve un rango hacia adelante o hacia atrás en el tiempo:
DateRange shifted = range.Shift(myRange, 1, TimeUnit.Months); // hacia adelante
DateRange back = range.Shift(myRange, -2, TimeUnit.Weeks); // hacia atrás
Firma: DateRange Shift(DateRange range, int amount, TimeUnit unit)
IsAdjacent
Comprueba si dos rangos son adyacentes (el fin de uno es el día anterior al inicio del otro):
bool adj = range.IsAdjacent(rangeA, rangeB);
Firma: bool IsAdjacent(DateRange a, DateRange b)
Merge
Fusiona una colección de rangos superpuestos o adyacentes en rangos mínimos no superpuestos:
IEnumerable<DateRange> merged = range.Merge(listOfRanges);
Firma: IEnumerable<DateRange> Merge(IEnumerable<DateRange> ranges)
Gaps
Devuelve rangos que representan brechas entre una lista ordenada de rangos, delimitados por un rango contenedor:
var container = range.Create(new DateTime(2025, 1, 1), new DateTime(2025, 12, 31));
IEnumerable<DateRange> gaps = range.Gaps(listOfRanges, container);
// → Rangos para períodos dentro del contenedor no cubiertos por ningún rango de entrada
Firma: IEnumerable<DateRange> Gaps(IEnumerable<DateRange> ranges, DateRange container)
El parámetro container define el rango delimitador dentro del cual se calculan las brechas. Solo se devuelven los períodos no cubiertos que caen dentro de container.
EachDay / EachWeek / EachMonth
Enumera fechas dentro del rango a un paso dado:
foreach (DateTime day in range.EachDay(myRange))
Console.WriteLine(day);
foreach (DateTime weekStart in range.EachWeek(myRange))
Console.WriteLine(weekStart);
foreach (DateTime monthStart in range.EachMonth(myRange))
Console.WriteLine(monthStart);
Firmas:
IEnumerable<DateTime> EachDay(DateRange range)IEnumerable<DateTime> EachWeek(DateRange range)IEnumerable<DateTime> EachMonth(DateRange range)
EachWorkday
Enumera solo los días laborables (lun–vie) dentro del rango:
foreach (DateTime workday in range.EachWorkday(myRange))
Console.WriteLine(workday);
Firma: IEnumerable<DateTime> EachWorkday(DateRange range)
SplitByDay / SplitByWeek / SplitByMonth / SplitByQuarter
Divide un rango en sub-rangos:
IEnumerable<DateRange> days = range.SplitByDay(myRange);
IEnumerable<DateRange> weeks = range.SplitByWeek(myRange);
IEnumerable<DateRange> months = range.SplitByMonth(myRange);
IEnumerable<DateRange> quarters = range.SplitByQuarter(myRange);
Ejemplo completo
public class ProjectAnalyzer(IValiRange range)
{
public void AnalyzeProject(DateTime projectStart, DateTime projectEnd)
{
var project = range.Create(projectStart, projectEnd);
Console.WriteLine($"Duration: {project.TotalDays} days");
// Dividir en sprints mensuales
var sprints = range.SplitByMonth(project).ToList();
Console.WriteLine($"Sprints: {sprints.Count}");
// Encontrar brechas de días festivos (si el servicio de feriados está disponible)
var workdays = range.EachWorkday(project).Count();
Console.WriteLine($"Workdays: {workdays}");
// Desglose trimestre por trimestre
foreach (var quarter in range.SplitByQuarter(project))
{
Console.WriteLine($" {quarter.Start:MMM dd} – {quarter.End:MMM dd}: {quarter.TotalDays} days");
}
}
}