Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e9986a6
:zap: FNV performance boost for bit sizes greater than 128; modest op…
gimlichael Nov 23, 2025
a3d261c
:recycle: guard rails to ensure FowlerNollVoHash specs
gimlichael Nov 23, 2025
f7680c1
:sparkles: added copilot instructions
gimlichael Nov 23, 2025
7608649
:zap: optimized foundation for DateSpan incl. updated unit test
gimlichael Nov 23, 2025
054fc90
:white_check_mark: fixed bug in unit test
gimlichael Nov 23, 2025
37b24ac
:zap: significant optimizations for FowlerNollVoHash base class (than…
gimlichael Nov 23, 2025
6588893
:zap: optimized RandomString to be 2-3x more performant
gimlichael Nov 23, 2025
e90a69c
:white_check_mark: additional unit tests
gimlichael Nov 23, 2025
e0903d5
:chart_with_upwards_trend: first attempt to write performance test us…
gimlichael Nov 23, 2025
4a70d0e
:zap: avoid using reflection (Activator)
gimlichael Nov 25, 2025
eef6194
:zap: optimize DelimitedString handling and performance
gimlichael Nov 25, 2025
f95ab69
:fix: refactor decryption logic and update using directives
gimlichael Nov 26, 2025
b312f00
:building_construction: refine project configurations and targets
gimlichael Nov 26, 2025
d54e999
:bug: add null-check validation to computehash input
gimlichael Nov 26, 2025
324e8da
fix
gimlichael Nov 26, 2025
6fbf13a
:fix: make setup parameter optional in constructor (consistency)
gimlichael Nov 26, 2025
7df4132
:zap: optimize AES performance via TransformFinalBlock
gimlichael Nov 27, 2025
934327c
:white_check_mark: add comprehensive unit tests for cryptography
gimlichael Nov 27, 2025
92563d9
:zap: optimized crc classes for efficiency and clarity
gimlichael Nov 29, 2025
f77066c
:white_check_mark: add unit tests for crc and fnv hash algorithms
gimlichael Nov 29, 2025
74d6773
:white_check_mark: add tests for hashfactory factory methods
gimlichael Nov 29, 2025
e79edbb
:books: update testing and benchmarking copilot guidelines
gimlichael Nov 29, 2025
21062a7
:arrow_up: update xunit package versions to 3.2.1
gimlichael Nov 29, 2025
12b7242
:construction_worker: update ci pipeline for os matrix support
gimlichael Nov 29, 2025
cb33dc4
:memo: update docs for tests and benchmarks naming conventions
gimlichael Nov 29, 2025
d450cab
:sparkles: add post-configuration support for parameter objects
gimlichael Dec 6, 2025
ce113e8
:white_check_mark: update test assertion for configurationTypesCount
gimlichael Dec 6, 2025
4d12ffc
:package: updated NuGet package definition
gimlichael Dec 6, 2025
bdd450c
chore
gimlichael Dec 6, 2025
feec99d
:speech_balloon: updated community health pages
gimlichael Dec 6, 2025
7e3948c
:arrow_up: bump dependencies
gimlichael Dec 6, 2025
7f2f009
ai
gimlichael Dec 6, 2025
83fde7e
fixes
gimlichael Dec 6, 2025
13bc4d9
:alembic: early stages of support for benchmarkdotnet on select free …
gimlichael Dec 6, 2025
27a9d0e
dependencies
gimlichael Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
481 changes: 481 additions & 0 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions Cuemon.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
# Visual Studio Version 18
VisualStudioVersion = 18.0.11217.181 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8}"
EndProject
Expand Down Expand Up @@ -170,6 +170,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cuemon.AspNetCore.Mvc.Funct
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cuemon.AspNetCore.FunctionalTests", "test\Cuemon.AspNetCore.FunctionalTests\Cuemon.AspNetCore.FunctionalTests.csproj", "{0618AFB5-D95A-48F9-A07A-B3A2DB876FC0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tooling", "tooling", "{11659885-1727-4DC8-809A-8FFCC6F53966}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cuemon.Core.Benchmarks", "tooling\Cuemon.Core.Benchmarks\Cuemon.Core.Benchmarks.csproj", "{6BC4C5DC-CECC-4E6C-8B95-673670A40BF8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -492,6 +496,10 @@ Global
{0618AFB5-D95A-48F9-A07A-B3A2DB876FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0618AFB5-D95A-48F9-A07A-B3A2DB876FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0618AFB5-D95A-48F9-A07A-B3A2DB876FC0}.Release|Any CPU.Build.0 = Release|Any CPU
{6BC4C5DC-CECC-4E6C-8B95-673670A40BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BC4C5DC-CECC-4E6C-8B95-673670A40BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BC4C5DC-CECC-4E6C-8B95-673670A40BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BC4C5DC-CECC-4E6C-8B95-673670A40BF8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -576,6 +584,7 @@ Global
{AE0ADF91-E7C7-4CB4-A39D-E1A5374C5602} = {B59C8DF7-7DEC-46AF-A165-CC9E3AD01EA8}
{28AC63CA-9E57-4C36-81B4-C03DD0CFC0EA} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338}
{0618AFB5-D95A-48F9-A07A-B3A2DB876FC0} = {31707D2B-843E-4D4F-B9C7-3E74EF8DA338}
{6BC4C5DC-CECC-4E6C-8B95-673670A40BF8} = {11659885-1727-4DC8-809A-8FFCC6F53966}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2A848386-B682-4F6D-8254-B5F6247C3054}
Expand Down
16 changes: 13 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project>
<PropertyGroup>
<IsTestProject>$(MSBuildProjectName.EndsWith('Tests'))</IsTestProject>
<IsBenchmarkProject>$(MSBuildProjectName.EndsWith('Benchmarks'))</IsBenchmarkProject>
<IsLinux>$([MSBuild]::IsOSPlatform('Linux'))</IsLinux>
<IsWindows>$([MSBuild]::IsOSPlatform('Windows'))</IsWindows>
<IsMainAuthor Condition="'$(EMAIL)' == '[email protected]'">true</IsMainAuthor>
Expand All @@ -13,7 +14,7 @@
<Deterministic>true</Deterministic>
</PropertyGroup>

<PropertyGroup Condition="'$(IsTestProject)' == 'false'">
<PropertyGroup Condition="'$(IsTestProject)' == 'false' AND '$(IsBenchmarkProject)' == 'false'">
<TargetFrameworks>net10.0;net9.0;netstandard2.0</TargetFrameworks>
<Copyright>Copyright © Geekle 2009-2025. All rights reserved.</Copyright>
<Authors>gimlichael</Authors>
Expand Down Expand Up @@ -46,7 +47,7 @@
<SourceRoot Include="$(NuGetPackageRoot)" RepositoryUrl="https://github.com/codebeltnet/cuemon" />
</ItemGroup>

<ItemGroup Condition="'$(IsTestProject)' == 'false'">
<ItemGroup Condition="'$(IsTestProject)' == 'false' AND '$(IsBenchmarkProject)' == 'false'">
<PackageReference Include="MinVer" PrivateAssets="all" />
<None Include="..\..\.nuget\icon.png" Pack="true" Visible="false" PackagePath="icon.png" />
<None Include="..\..\.nuget\$(MSBuildProjectName)\README.md" Pack="true" PackagePath="\" />
Expand All @@ -60,7 +61,7 @@
<TargetFrameworks>net10.0;net9.0;net48</TargetFrameworks>
</PropertyGroup>

<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<PropertyGroup Condition="'$(IsTestProject)' == 'true' OR '$(IsBenchmarkProject)' == 'true'">
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<RunAnalyzers>false</RunAnalyzers>
Expand Down Expand Up @@ -97,4 +98,13 @@
<PackageReference Include="Codebelt.Extensions.Xunit" />
</ItemGroup>

<PropertyGroup Condition="'$(IsBenchmarkProject)' == 'true'">
<TargetFrameworks>net10.0;net9.0;net48</TargetFrameworks>
</PropertyGroup>

<ItemGroup Condition="'$(IsBenchmarkProject)' == 'true'">
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.6" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.6" />
<PackageVersion Include="Codebelt.Extensions.Xunit" Version="11.0.1" />
<PackageVersion Include="Codebelt.Extensions.Xunit.Hosting" Version="11.0.1" />
<PackageVersion Include="Codebelt.Extensions.Xunit.Hosting.AspNetCore" Version="11.0.1" />
Expand Down
193 changes: 127 additions & 66 deletions src/Cuemon.Core/DateSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Globalization;
using System.Linq;
using Cuemon.Collections.Generic;
using Cuemon.Security;

namespace Cuemon
{
Expand All @@ -13,6 +14,9 @@ namespace Cuemon
private readonly DateTime _lower;
private readonly DateTime _upper;
private readonly Calendar _calendar;
private readonly ulong _calendarId;
private static readonly ulong Hash = (ulong)new FowlerNollVo64().OffsetBasis;
private static readonly ulong Prime = (ulong)new FowlerNollVo64().Prime;

/// <summary>
/// Initializes a new instance of the <see cref="DateSpan"/> structure with a default <see cref="DateTime"/> value set to <see cref="DateTime.Today"/>.
Expand All @@ -23,7 +27,7 @@ public DateSpan(DateTime start) : this(start, DateTime.Today)
}

/// <summary>
/// Initializes a new instance of the <see cref="DateSpan"/> structure with a default <see cref="_calendar"/> value from the <see cref="CultureInfo.InvariantCulture"/> class.
/// Initializes a new instance of the <see cref="DateSpan"/> structure with a default <see cref="Calendar"/> value from the <see cref="CultureInfo.InvariantCulture"/> class.
/// </summary>
/// <param name="start">A <see cref="DateTime"/> value for the <see cref="DateSpan"/> calculation.</param>
/// <param name="end">A <see cref="DateTime"/> value for the <see cref="DateSpan"/> calculation.</param>
Expand All @@ -45,69 +49,34 @@ public DateSpan(DateTime start, DateTime end, Calendar calendar) : this()
_upper = Arguments.ToEnumerableOf(start, end).Max();
_calendar = calendar;

_calendarId = _calendar switch
{
ChineseLunisolarCalendar => 1,
JapaneseLunisolarCalendar => 2,
KoreanLunisolarCalendar => 3,
TaiwanLunisolarCalendar => 4,
EastAsianLunisolarCalendar => 5,
GregorianCalendar => 6,
HebrewCalendar => 7,
HijriCalendar => 8,
JapaneseCalendar => 9,
JulianCalendar => 10,
KoreanCalendar => 11,
PersianCalendar => 12,
TaiwanCalendar => 13,
ThaiBuddhistCalendar => 14,
UmAlQuraCalendar => 15,
_ => (ulong)_calendar.GetType().FullName!.GetHashCode()
};

var lower = _lower;
var upper = _upper;

var months = 0;
var days = 0;

var years = GetYears(upper, lower, out var adjustYearsMinusOne);
var hours = 0;
var milliseconds = 0;

int daysPerYears = 0;
var y = lower.Year;
do
{
daysPerYears += _calendar.GetDaysInYear(y);
y++;
} while (y < upper.Year);

var daysPerYearsAverage = years == 0 ? daysPerYears : Convert.ToDouble(daysPerYears) / Convert.ToDouble(years);

while (!lower.Year.Equals(upper.Year) || !lower.Month.Equals(upper.Month))
{
var daysPerMonth = _calendar.GetDaysInMonth(lower.Year, lower.Month);
var peekNextLower = lower.AddMonths(1);
if (peekNextLower > upper)
{
while (!lower.Month.Equals(upper.Month) || !lower.Day.Equals(upper.Day))
{
days++;
lower = lower.AddDays(1);
}

if (lower > upper)
{
lower = lower.AddDays(-1);
days--;

while (!lower.Hour.Equals(upper.Hour))
{
hours++;
lower = lower.AddHours(1);
}

while (!lower.Minute.Equals(upper.Minute) || !lower.Second.Equals(upper.Second) || !lower.Millisecond.Equals(upper.Millisecond))
{
milliseconds++;
lower = lower.AddMilliseconds(1);
}
}
}
else
{
days += daysPerMonth;
lower = lower.AddMonths(1);
months++;
}
}
var daysPerYearsAverage = CalculateAverageDaysPerYear(lower, upper, years);

while (!lower.Day.Equals(upper.Day))
{
days++;
lower = lower.AddDays(1);
}
CalculateDifference(ref lower, upper, out var months, out var days, out var hours, out var milliseconds);

var averageDaysPerMonth = months == 0 ? days : Convert.ToDouble(days) / Convert.ToDouble(months);
var remainder = new TimeSpan(days, hours, 0, 0, milliseconds);
Expand Down Expand Up @@ -149,21 +118,98 @@ private static int GetYears(DateTime upper, DateTime lower, out bool adjustYears
return years;
}

private double CalculateAverageDaysPerYear(DateTime lower, DateTime upper, int years)
{
var daysPerYears = 0;
var y = lower.Year;
do
{
daysPerYears += _calendar.GetDaysInYear(y);
y++;
} while (y < upper.Year);

return years == 0 ? daysPerYears : Convert.ToDouble(daysPerYears) / Convert.ToDouble(years);
}

private void CalculateDifference(ref DateTime lower, DateTime upper, out int months, out int days, out int hours, out int milliseconds)
{
months = 0;
days = 0;
hours = 0;
milliseconds = 0;

while (!lower.Year.Equals(upper.Year) || !lower.Month.Equals(upper.Month))
{
var daysPerMonth = _calendar.GetDaysInMonth(lower.Year, lower.Month);
var peekNextLower = lower.AddMonths(1);
if (peekNextLower > upper)
{
CalculatePartialMonthDifference(ref lower, upper, ref days, ref hours, ref milliseconds);
}
else
{
days += daysPerMonth;
lower = lower.AddMonths(1);
months++;
}
}

while (!lower.Day.Equals(upper.Day))
{
days++;
lower = lower.AddDays(1);
}
}

private static void CalculatePartialMonthDifference(ref DateTime lower, DateTime upper, ref int days, ref int hours, ref int milliseconds)
{
while (!lower.Month.Equals(upper.Month) || !lower.Day.Equals(upper.Day))
{
days++;
lower = lower.AddDays(1);
}

if (lower > upper)
{
lower = lower.AddDays(-1);
days--;

CalculateTimeDifference(ref lower, upper, ref hours, ref milliseconds);
}
}

private static void CalculateTimeDifference(ref DateTime lower, DateTime upper, ref int hours, ref int milliseconds)
{
while (!lower.Hour.Equals(upper.Hour))
{
hours++;
lower = lower.AddHours(1);
}

while (!lower.Minute.Equals(upper.Minute) || !lower.Second.Equals(upper.Second) || !lower.Millisecond.Equals(upper.Millisecond))
{
milliseconds++;
lower = lower.AddMilliseconds(1);
}
}

/// <summary>
/// Calculates the number of weeks represented by the current <see cref="DateSpan"/> structure.
/// </summary>
/// <value>Calculates the number of weeks represented by the current <see cref="DateSpan"/> structure.</value>
public int GetWeeks()
{
var range = _upper.Subtract(_lower);
var totalDays = 0;
int totalDays;
if (range.Days <= 7)
{
totalDays = _lower.DayOfWeek > _upper.DayOfWeek ? 2 : 1;
}
if (totalDays == 0) { totalDays = range.Days - 7 + (int)_lower.DayOfWeek; }
int nextWeek = 0, weeks;
for (weeks = 1; nextWeek < totalDays; weeks++) { nextWeek += 7; }
else
{
totalDays = range.Days - 7 + (int)_lower.DayOfWeek;
}
var weeks = 1 + ((totalDays + 6) / 7);
return weeks;
}

Expand All @@ -176,7 +222,22 @@ public int GetWeeks()
/// </returns>
public override int GetHashCode()
{
return Generate.HashCode32(_upper, _lower, _calendar.GetType().FullName);
unchecked
{
var hash = Hash;
var prime = Prime;

hash ^= (ulong)_upper.Ticks;
hash *= prime;

hash ^= (ulong)_lower.Ticks;
hash *= prime;

hash ^= _calendarId;
hash *= prime;

return (int)(hash ^ (hash >> 32));
}
}

/// <summary>
Expand All @@ -199,7 +260,7 @@ public override bool Equals(object obj)
/// <returns><c>true</c> if the current object is equal to the other parameter; otherwise, <c>false</c>. </returns>
public bool Equals(DateSpan other)
{
if ((_upper != other._upper) || (_calendar != other._calendar)) { return false; }
if ((_upper != other._upper) || (_calendarId != other._calendarId)) { return false; }
return (_lower == other._lower);
}

Expand Down Expand Up @@ -251,7 +312,7 @@ public static DateSpan Parse(string start, string end)
/// </summary>
/// <param name="start">A string that specifies the starting date and time value for the <see cref="DateSpan"/> interval.</param>
/// <param name="end">A string that specifies the ending date and time value for the <see cref="DateSpan"/> interval.</param>
/// <param name="culture">A <see cref="CultureInfo"/> to resolve a <see cref="_calendar"/> object from.</param>
/// <param name="culture">A <see cref="CultureInfo"/> to resolve a <see cref="Calendar"/> object from.</param>
/// <returns>A <see cref="DateSpan"/> that corresponds to <paramref name="start"/> and <paramref name="end"/> of the interval.</returns>
public static DateSpan Parse(string start, string end, CultureInfo culture)
{
Expand Down Expand Up @@ -360,4 +421,4 @@ public override string ToString()
/// <value>The total number of years represented by the current <see cref="DateSpan"/> structure.</value>
public double TotalYears { get; }
}
}
}
Loading
Loading