Skip to content

Commit

Permalink
Fix partly broken import of excluded match dates (#196)
Browse files Browse the repository at this point in the history
* Fix `GermanHoliday.ProcessHoliday` method to set correct `dateFrom` and `dateTo`.
* Add more constructors to `GermanHolidayImporter` to import more than 1 file in one go.
* Remove ignored test case and added new one in `GermanHolidayImporterTests`.
* Rename `EnumerableValueTupleExtensions.cs` to `EnumerableRangeExtensions.cs` for consecutive range calculations.
* Add `EnumerableValueTupleExtensionsTests` for `EnumerableValueTupleExtensions` methods.*
  • Loading branch information
axunonb authored Oct 4, 2024
1 parent 8bfc966 commit ed0d297
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 83 deletions.
4 changes: 2 additions & 2 deletions Axuno.Tools/GermanHoliday.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class GermanHoliday
/// <summary>
/// CTOR.
/// </summary>
/// <param name="id">Nullable HolidayId</param>
/// <param name="id">A well-known <see cref="GermanHolidays.Id"/> or <see langword="null"/> </param>
/// <param name="type">HolidayType</param>
/// <param name="name">Holiday name</param>
/// <param name="calcDateFunc">Function for date calculation</param>
Expand All @@ -26,7 +26,7 @@ internal GermanHoliday(GermanHolidays.Id? id, GermanHolidays.Type type, string n
}

/// <summary>
/// Constructor for usage from inside of class GermanHolidays.
/// Constructor for usage from class GermanHolidays.
/// </summary>
/// <param name="id">HolidayId</param>
/// <param name="type">HolidayType</param>
Expand Down
18 changes: 12 additions & 6 deletions Axuno.Tools/GermanHolidays.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ private DateTime GetEasterSunday()
int tA, tB, tC, tD, tE; // tables A to E

var firstDigits = Year / 100;
var remainding19 = Year % 19;
var remainder19 = Year % 19;

// Calculate Paschal Full Moon
var temp = (firstDigits - 15) / 2 + 202 - 11 * remainding19;
var temp = (firstDigits - 15) / 2 + 202 - 11 * remainder19;
switch (firstDigits)
{
case 21:
Expand Down Expand Up @@ -168,7 +168,7 @@ private DateTime GetEasterSunday()
tA = temp + 21;
if (temp == 29)
tA -= 1;
if (temp == 28 && remainding19 > 10)
if (temp == 28 && remainder19 > 10)
tA -= 1;

// Calculate next Sunday
Expand Down Expand Up @@ -206,7 +206,7 @@ private DateTime GetEasterSunday()
/// <returns>Advent date</returns>
private DateTime GetAdventDate(int num)
{
if (num < 1 || num > 4)
if (num is < 1 or > 4)
throw new InvalidOperationException("Only Advents 1 to 4 are allowed.");

// 4th Advent is the latest Sunday before 25th December
Expand Down Expand Up @@ -294,7 +294,7 @@ private DateTime GetMuttertag()

switch (holidayId)
{
// general public holidays, are those where all federal states have the same public holidays defined
// national holidays, are those where all federal states have the same public holidays defined
case Id.Neujahr:
case Id.KarFreitag:
case Id.OsterSonntag:
Expand Down Expand Up @@ -458,6 +458,12 @@ private void ProcessHoliday(Id? holidayId, ActionType action, DateTime dateFrom,

ValidateDateRange(action, dateFrom, dateTo);

var dateIsSet = dateFrom != DateTime.MinValue && dateTo != DateTime.MinValue;
if (holidayId.HasValue && !dateIsSet && Exists(h => h.Id == holidayId))
{
dateFrom = dateTo = this[holidayId.Value]!.CalcDateFunc();
}

while (dateFrom <= dateTo)
{
var tmpDateFrom = dateFrom; // no capture of modified closure
Expand Down Expand Up @@ -508,7 +514,7 @@ private void ReplaceHoliday(Id? holidayId, GermanHoliday newHoliday)
{
if (!holidayId.HasValue || this[holidayId.Value] == null)
throw new InvalidOperationException("Holiday to replace not found.");

var existingHoliday = this[holidayId.Value]!;

// Replace the existing holiday with the new one
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version='1.0' encoding="UTF-8" standalone='yes'?>
<Holidays>
<Holiday Action="add" Id="Neujahr">
<Type>Public</Type>
<Name>Sample New Year</Name>
<DateFrom>2024-01-01</DateFrom>
<DateTo>2024-01-01</DateTo>
<PublicHolidayStateIds>
<StateId>Bayern</StateId>
</PublicHolidayStateIds>
</Holiday>
</Holidays>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version='1.0' encoding="UTF-8" standalone='yes'?>
<Holidays>
<Holiday Action="remove" Id="Neujahr">
<Type>Public</Type>
<DateFrom>2024-01-01</DateFrom>
<DateTo>2024-01-01</DateTo>
</Holiday>
</Holidays>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using NUnit.Framework;
using TournamentManager.Importers.ExcludeDates;

namespace TournamentManager.Tests.Importers.ExcludeDates;

[TestFixture]
internal class EnumerableValueTupleExtensionsTests
{
[Test]
public void IntegerRanges_ShouldBeConsecutive()
{
// Unsorted list of integers with missing numbers
var intList = new List<int>
{
// group #3: number 7 missing
10,
9,
8,
// group #1
2,
3,
4,
// group #2: number 5 missing
6
};

var ranges = intList.ConsecutiveRanges().ToList();
Assert.Multiple(() =>
{
Assert.That(ranges, Has.Count.EqualTo(3));
Assert.That(ranges[0], Is.EqualTo((2, 4)));
Assert.That(ranges[1], Is.EqualTo((6, 6)));
Assert.That(ranges[2], Is.EqualTo((8, 10)));
});
}

[Test]
public void DateOnlyRanges_ShouldBeConsecutive()
{
// Unsorted list of DateTime with missing dates
var dateOnlyList = new List<DateOnly>
{
// group #3: number 7 Oct missing
new (2024, 10, 10),
new (2024, 10, 9),
new (2024, 10, 8),
// group #1
new (2024, 10, 2),
new (2024, 10, 3),
new (2024, 10, 4),
// group #2: number 5 Oct missing
new (2024, 10, 6)
};

var ranges = dateOnlyList.ConsecutiveRanges().ToList();
Assert.Multiple(() =>
{
Assert.That(ranges, Has.Count.EqualTo(3));
Assert.That(ranges[0], Is.EqualTo((new DateOnly(2024, 10, 2), new DateOnly(2024, 10, 4))));
Assert.That(ranges[1], Is.EqualTo((new DateOnly(2024, 10, 6), new DateOnly(2024, 10, 6))));
Assert.That(ranges[2], Is.EqualTo((new DateOnly(2024, 10, 8), new DateOnly(2024, 10, 10))));
});
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ public void Import_HolidaysInAllFederalStates(DateTime from, DateTime to, int ex
// using CET as time zone
var tzConverter = new Axuno.Tools.DateAndTime.TimeZoneConverter(
"Europe/Berlin", CultureInfo.CurrentCulture);
var holidayFilter = new Predicate<GermanHoliday>(h => h.Type == GermanHolidays.Type.Public && h.PublicHolidayStateIds.Count == new GermanFederalStates().Count);
var hImporter = new GermanHolidayImporter(null, holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);
var holidayFilter = new Predicate<GermanHoliday>(h =>
h.Type == GermanHolidays.Type.Public && h.PublicHolidayStateIds.Count == new GermanFederalStates().Count);
var hImporter =
new GermanHolidayImporter(holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);

var imported = hImporter.Import(new DateTimePeriod(from, to)).ToList();

Expand All @@ -33,8 +35,10 @@ public void Import_HolidaysInBavaria(DateTime from, DateTime to, int expectedCou
// using CET as time zone
var tzConverter = new Axuno.Tools.DateAndTime.TimeZoneConverter(
"Europe/Berlin", CultureInfo.CurrentCulture);
var holidayFilter = new Predicate<GermanHoliday>(h => h.Type == GermanHolidays.Type.Public && h.PublicHolidayStateIds.Contains(GermanFederalStates.Id.Bayern));
var hImporter = new GermanHolidayImporter(null, holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);
var holidayFilter = new Predicate<GermanHoliday>(h =>
h.Type == GermanHolidays.Type.Public && h.PublicHolidayStateIds.Contains(GermanFederalStates.Id.Bayern));
var hImporter =
new GermanHolidayImporter(holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);

var imported = hImporter.Import(new DateTimePeriod(from, to)).ToList();

Expand All @@ -52,34 +56,66 @@ public void Import_Holidays_Volleyball_League_Augsburg(DateTime from, DateTime t
var holidayFilter = new Predicate<GermanHoliday>(
h =>
h.Type == GermanHolidays.Type.Public &&
h.PublicHolidayStateIds.Contains(GermanFederalStates.Id.Bayern)
h.PublicHolidayStateIds.Contains(GermanFederalStates.Id.Bayern)
// add 5 more local holidays
|| h.Id == GermanHolidays.Id.AugsburgerFriedensfest || h.Id == GermanHolidays.Id.HeiligerAbend ||
h.Id == GermanHolidays.Id.RosenMontag || h.Id == GermanHolidays.Id.FaschingsDienstag ||
h.Id == GermanHolidays.Id.Silvester);
h.Id == GermanHolidays.Id.RosenMontag || h.Id == GermanHolidays.Id.FaschingsDienstag ||
h.Id == GermanHolidays.Id.Silvester);

var hImporter = new GermanHolidayImporter(null, holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);
var hImporter =
new GermanHolidayImporter(holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);

var imported = hImporter.Import(new DateTimePeriod(from, to)).ToList();

Assert.That(imported, Has.Count.EqualTo(expectedCount));
}
[Ignore("Tests fails and requires refactoring of GermanHolidays", Until = "2024-09-30")]
[TestCase("2019-09-01", "2020-06-30", 9)]

[TestCase("2019-09-01", "2020-06-30", 6)]
public void Import_With_Custom_School_Holidays(DateTime from, DateTime to, int expectedCount)
{
// Note: Custom_Holidays_Sample.xml contains 6 school holidays.
// But as the command for them is "Merge", existing holidays persist unchanged,
// and additional holiday periods are added.
var customHolidayFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Custom_Holidays_Sample.xml");
var customHolidayFilePath =
Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Custom_Holidays_Sample.xml");
// using CET as time zone
var tzConverter = new Axuno.Tools.DateAndTime.TimeZoneConverter(
"Europe/Berlin", CultureInfo.CurrentCulture);

var holidayFilter = new Predicate<GermanHoliday>(h => h.Type == GermanHolidays.Type.School);
var hImporter = new GermanHolidayImporter(customHolidayFilePath, holidayFilter, tzConverter, NullLogger<GermanHolidayImporter>.Instance);
var hImporter = new GermanHolidayImporter(customHolidayFilePath, holidayFilter, tzConverter,
NullLogger<GermanHolidayImporter>.Instance);

// The period from 2019-09-01 to 2020-06-30 contains 6 imported school holidays.
var imported = hImporter.Import(new DateTimePeriod(from, to)).ToList();

Assert.That(imported, Has.Count.EqualTo(expectedCount));
}

[Test]
public void Add_And_Remove_Holiday()
{
// Note: Custom_Holidays_Sample.xml contains 6 school holidays.
// But as the command for them is "Merge", existing holidays persist unchanged,
// and additional holiday periods are added.
var addHolidayFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Single_Holiday_To_Add.xml");
var removeHolidayFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Assets", "Single_Holiday_To_Remove.xml");
// using CET as time zone
var tzConverter = new Axuno.Tools.DateAndTime.TimeZoneConverter(
"Europe/Berlin", CultureInfo.CurrentCulture);

var holidayFilter = new Predicate<GermanHoliday>(h => h.Type == GermanHolidays.Type.Public);
var hImporter = new GermanHolidayImporter(new[] { removeHolidayFilePath, addHolidayFilePath }, holidayFilter,
tzConverter, NullLogger<GermanHolidayImporter>.Instance);

var imported = hImporter.Import(new DateTimePeriod(new DateTime(2024, 1, 1), new DateTime(2024, 1, 1))).ToList();


Assert.Multiple(() =>
{
Assert.That(imported, Has.Count.EqualTo(1));
Assert.That(imported[0].Reason, Is.EqualTo("Sample New Year"));
});

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<ProjectReference Include="..\TournamentManager\TournamentManager.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Assets\Single_Holiday_To_Remove.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Assets\Single_Holiday_To_Add.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Assets\Custom_Holidays_Sample.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down

This file was deleted.

Loading

0 comments on commit ed0d297

Please sign in to comment.