Skip to content

Commit

Permalink
New classes and conceptual grouper
Browse files Browse the repository at this point in the history
  • Loading branch information
geoperez committed Nov 20, 2023
1 parent a9509bd commit 85c3c2c
Show file tree
Hide file tree
Showing 15 changed files with 408 additions and 13 deletions.
20 changes: 20 additions & 0 deletions src/Unosquare.DateTimeExt/BaseGrouper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Unosquare.DateTimeExt.Interfaces;

namespace Unosquare.DateTimeExt;

public abstract class BaseGrouper<T, TConcrete> : IGrouper<T, TConcrete>
{
public abstract IEnumerable<IGrouping<TConcrete, T>> GroupByDateRange();

public IDictionary<string, List<T>> GroupByLabel() =>
GroupByDateRange()
.ToDictionary(x => x.Key!.ToString()!, x => x.ToList());

public IDictionary<string, List<TResult>> GroupByLabel<TResult>(Func<T, TResult> selector) =>
GroupByDateRange()
.ToDictionary(x => x.Key!.ToString()!, x => x.Select(selector).ToList());

public IDictionary<string, int> GroupCount() =>
GroupByDateRange()
.ToDictionary(x => x.Key!.ToString()!, x => x.Count());
}
4 changes: 2 additions & 2 deletions src/Unosquare.DateTimeExt/DateOnlyRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Unosquare.DateTimeExt;
/// <summary>
/// Represents a range of dates with only the date component (no time component).
/// </summary>
public sealed class DateOnlyRange : RangeBase<DateOnly>, IReadOnlyDateOnlyRange, IComparable<DateOnlyRange>,
public sealed class DateOnlyRange : RangeBase<DateOnly, DateOnly>, IReadOnlyDateOnlyRange, IComparable<DateOnlyRange>,
IEnumerable<DateOnly>, IHasBusinessDaysDateOnly
{
/// <summary>
Expand All @@ -25,7 +25,7 @@ public DateOnlyRange()
/// <param name="endDate">The end date (optional).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the end date is before the start date.</exception>
public DateOnlyRange(DateOnly startDate, DateOnly? endDate = null)
: base(startDate, endDate)
: base(startDate, endDate ?? startDate)
{
if (EndDate < StartDate)
throw new ArgumentOutOfRangeException(nameof(endDate), "End Date should be after Start Date");
Expand Down
4 changes: 2 additions & 2 deletions src/Unosquare.DateTimeExt/DateRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Unosquare.DateTimeExt;
/// <summary>
/// Represents a range of dates with a start and end date.
/// </summary>
public class DateRange : RangeBase<DateTime>, IReadOnlyDateRange, IHasReadOnlyMidnightEndDate, IComparable<DateRange>, IEnumerable<DateTime>, IHasBusinessDays
public class DateRange : RangeBase<DateTime, DateTime>, IReadOnlyDateRange, IHasReadOnlyMidnightEndDate, IComparable<DateRange>, IEnumerable<DateTime>, IHasBusinessDays
{
/// <summary>
/// Initializes a new instance of the <see cref="DateRange"/> class with the current UTC date and time.
Expand All @@ -22,7 +22,7 @@ public DateRange(IReadOnlyDateRange range)
}

public DateRange(DateTime startDate, DateTime? endDate = null)
: base(startDate, endDate)
: base(startDate, endDate ?? startDate)
{
if (EndDate < StartDate)
throw new ArgumentOutOfRangeException(nameof(endDate), "End Date should be after Start Date");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Unosquare.DateTimeExt.Interfaces;

public interface ICanHaveReadOnlyEndDate
{
DateTime? EndDate { get; }
}
12 changes: 12 additions & 0 deletions src/Unosquare.DateTimeExt/Interfaces/IGrouper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Unosquare.DateTimeExt.Interfaces;

public interface IGrouper<T, out TConcrete>
{
IEnumerable<IGrouping<TConcrete, T>> GroupByDateRange();

IDictionary<string, List<T>> GroupByLabel();

IDictionary<string, List<TResult>> GroupByLabel<TResult>(Func<T, TResult> selector);

IDictionary<string, int> GroupCount();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Unosquare.DateTimeExt.Interfaces;

public interface IReadOnlyOpenDateRange : IHasReadOnlyStartDate, ICanHaveReadOnlyEndDate
{
}
46 changes: 46 additions & 0 deletions src/Unosquare.DateTimeExt/OpenDateRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections;
using Unosquare.DateTimeExt.Interfaces;

namespace Unosquare.DateTimeExt;

public class OpenDateRange(DateTime startDate, DateTime? endDate = null) : RangeBase<DateTime, DateTime?>(startDate, endDate), IReadOnlyOpenDateRange, IComparable<OpenDateRange>, IEnumerable<DateTime>, IHasBusinessDays

Check warning on line 6 in src/Unosquare.DateTimeExt/OpenDateRange.cs

View workflow job for this annotation

GitHub Actions / Build

When implementing IComparable<T>, you should also override Equals, ==, !=, <, <=, >, and >=. (https://rules.sonarsource.com/csharp/RSPEC-1210)

Check warning on line 6 in src/Unosquare.DateTimeExt/OpenDateRange.cs

View workflow job for this annotation

GitHub Actions / Build

This class overrides 'GetHashCode' and should therefore also override 'Equals'. (https://rules.sonarsource.com/csharp/RSPEC-1206)

Check warning on line 6 in src/Unosquare.DateTimeExt/OpenDateRange.cs

View workflow job for this annotation

GitHub Actions / Build

When implementing IComparable<T>, you should also override Equals, ==, !=, <, <=, >, and >=. (https://rules.sonarsource.com/csharp/RSPEC-1210)

Check warning on line 6 in src/Unosquare.DateTimeExt/OpenDateRange.cs

View workflow job for this annotation

GitHub Actions / Build

This class overrides 'GetHashCode' and should therefore also override 'Equals'. (https://rules.sonarsource.com/csharp/RSPEC-1206)
{
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public override IEnumerator<DateTime> GetEnumerator()
{
if (EndDate is not null)
{
var daysInBetween = (EndDate.Value - StartDate).Days;
for (var i = 0; i <= daysInBetween; i++)
yield return StartDate.AddDays(i);
}
else
{
var i = 0;

while (DateTime.MaxValue != StartDate.AddDays(i))
yield return StartDate.AddDays(++i);
}
}

public int CompareTo(OpenDateRange? other)
{
if (ReferenceEquals(this, other))
return 0;

if (other is null)
return 1;

var startDateComparison = StartDate.CompareTo(other.StartDate);

return startDateComparison != 0 ? startDateComparison : (EndDate?.CompareTo(other.EndDate) ?? 0);
}

public override string ToString() => $"{StartDate.ToShortDateString()} - {EndDate?.ToShortDateString()}";

public override int GetHashCode() => StartDate.GetHashCode() + EndDate.GetHashCode();

public DateTime FirstBusinessDay => StartDate.GetFirstBusinessDayOfMonth();
public DateTime LastBusinessDay => (EndDate ?? DateTime.Now).GetLastBusinessDayOfMonth();
}
16 changes: 8 additions & 8 deletions src/Unosquare.DateTimeExt/RangeBase.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
namespace Unosquare.DateTimeExt;

public abstract class RangeBase<T> where T : struct
public abstract class RangeBase<TStart, TEnd> where TStart : struct
{
protected RangeBase(T startDate, T? endDate = default)
protected RangeBase(TStart startDate, TEnd endDate)
{
StartDate = startDate;
EndDate = endDate ?? startDate;
EndDate = endDate;
}

public T StartDate { get; }
public TStart StartDate { get; }

public T EndDate { get; }
public TEnd EndDate { get; }

public IEnumerable<TK> Select<TK>(Func<T, TK> selector)
public IEnumerable<TK> Select<TK>(Func<TStart, TK> selector)
{
using var enumerator = GetEnumerator();

while (enumerator.MoveNext())
yield return selector(enumerator.Current);
}

public abstract IEnumerator<T> GetEnumerator();
public abstract IEnumerator<TStart> GetEnumerator();

public void Deconstruct(out T startDate, out T endDate)
public void Deconstruct(out TStart startDate, out TEnd endDate)
{
startDate = StartDate;
endDate = EndDate;
Expand Down
3 changes: 2 additions & 1 deletion src/Unosquare.DateTimeExt/Unosquare.DateTimeExt.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<AnalysisLevel>latest</AnalysisLevel>
<CodeAnalysisRuleSet>..\..\StyleCop.Analyzers.ruleset</CodeAnalysisRuleSet>
<Version>1.2.1</Version>
<Version>1.3.0</Version>
</PropertyGroup>

</Project>
8 changes: 8 additions & 0 deletions src/Unosquare.DateTimeExt/YearMonthGrouper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Unosquare.DateTimeExt.Interfaces;

namespace Unosquare.DateTimeExt;

public class YearMonthGrouper<T>(IEnumerable<T> query) : BaseGrouper<T, YearMonth> where T : IYearMonth
{
public override IEnumerable<IGrouping<YearMonth, T>> GroupByDateRange() => query.GroupBy(x => new YearMonth(x.Month, x.Year));
}
8 changes: 8 additions & 0 deletions src/Unosquare.DateTimeExt/YearMonthRecordGrouper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Unosquare.DateTimeExt.Interfaces;

namespace Unosquare.DateTimeExt;

public class YearMonthRecordGrouper<T>(IEnumerable<T> query) : BaseGrouper<T, YearMonthRecord> where T : IYearMonth
{
public override IEnumerable<IGrouping<YearMonthRecord, T>> GroupByDateRange() => query.GroupBy(x => new YearMonthRecord { Month = x.Month, Year = x.Year });
}
8 changes: 8 additions & 0 deletions src/Unosquare.DateTimeExt/YearQuarterGrouper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Unosquare.DateTimeExt.Interfaces;

namespace Unosquare.DateTimeExt;

public class YearQuarterGrouper<T>(IEnumerable<T> query) : BaseGrouper<T, YearQuarter> where T : IYearQuarter
{
public override IEnumerable<IGrouping<YearQuarter, T>> GroupByDateRange() => query.GroupBy(x => new YearQuarter(x.Quarter, x.Year));
}
8 changes: 8 additions & 0 deletions src/Unosquare.DateTimeExt/YearQuarterRecordGrouper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Unosquare.DateTimeExt.Interfaces;

namespace Unosquare.DateTimeExt;

public class YearQuarterRecordGrouper<T>(IEnumerable<T> query) : BaseGrouper<T, YearQuarterRecord> where T : IYearQuarter
{
public override IEnumerable<IGrouping<YearQuarterRecord, T>> GroupByDateRange() => query.GroupBy(x => new YearQuarterRecord { Year = x.Year, Quarter = x.Quarter });
}
Loading

0 comments on commit 85c3c2c

Please sign in to comment.