Skip to content

Commit

Permalink
Changed DateTime.Now to use TimeProvider.
Browse files Browse the repository at this point in the history
Added a new TestTimeProvider to test solution, passed in through options.
This allows for easier testing as well as customization for the user in case they want to change provider.

Fixed Age tests (provider always returns first day of month, allowing simplified age calculation in tests).

Signed-off-by: Johannes Tegnér <johannes@jitesoft.com>
  • Loading branch information
Johannestegner committed Dec 29, 2024
1 parent 0831d07 commit ad8dcda
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 17 deletions.
14 changes: 8 additions & 6 deletions Personnummer.Tests/CoordinationNumberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,19 @@ public void TestParseInvalidCn(PersonnummerData ssn)
[ClassData(typeof(ValidCnDataProvider))]
public void TestAgeCn(PersonnummerData ssn)
{
var timeProvider = new TestTimeProvider();

int day = int.Parse(ssn.LongFormat.Substring(ssn.LongFormat.Length - 6, 2)) - 60;
string strDay = day < 10 ? $"0{day}" : day.ToString();

DateTime dt = DateTime.ParseExact(ssn.LongFormat.Substring(0, ssn.LongFormat.Length - 6) + strDay, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
int years = DateTime.Now.Year - dt.Year;
DateTime dt = DateTime.ParseExact(ssn.LongFormat.Substring(0, ssn.LongFormat.Length - 6) + strDay, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
int years = timeProvider.GetLocalNow().Year - dt.Year;

Assert.Equal(years, Personnummer.Parse(ssn.SeparatedLong, new Personnummer.Options { AllowCoordinationNumber = true }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.SeparatedFormat, new Personnummer.Options { AllowCoordinationNumber = true }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.LongFormat, new Personnummer.Options { AllowCoordinationNumber = true }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.SeparatedLong, new Personnummer.Options { AllowCoordinationNumber = true, TimeProvider = timeProvider }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.SeparatedFormat, new Personnummer.Options { AllowCoordinationNumber = true, TimeProvider = timeProvider }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.LongFormat, new Personnummer.Options { AllowCoordinationNumber = true, TimeProvider = timeProvider }).Age);
// Du to age not being possible to fetch from >100 year short format without separator, we aught to check this here.
Assert.Equal(years > 99 ? years - 100 : years, Personnummer.Parse(ssn.ShortFormat, new Personnummer.Options { AllowCoordinationNumber = true }).Age);
Assert.Equal(years > 99 ? years - 100 : years, Personnummer.Parse(ssn.ShortFormat, new Personnummer.Options { AllowCoordinationNumber = true, TimeProvider = timeProvider }).Age);
}


Expand Down
11 changes: 6 additions & 5 deletions Personnummer.Tests/PersonnummerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,15 @@ public void TestParseInvalid(PersonnummerData ssn)
[ClassData(typeof(ValidSsnDataProvider))]
public void TestAge(PersonnummerData ssn)
{
var timeProvider = new TestTimeProvider();
DateTime dt = DateTime.ParseExact(ssn.LongFormat.Substring(0, ssn.LongFormat.Length - 4), "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None);
int years = DateTime.Now.Year - dt.Year;
int years = timeProvider.GetLocalNow().Year - dt.Year;

Assert.Equal(years, Personnummer.Parse(ssn.SeparatedLong, new Personnummer.Options { AllowCoordinationNumber = false }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.SeparatedFormat, new Personnummer.Options { AllowCoordinationNumber = false }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.LongFormat, new Personnummer.Options { AllowCoordinationNumber = false }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.SeparatedLong, new Personnummer.Options { AllowCoordinationNumber = false, TimeProvider = timeProvider }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.SeparatedFormat, new Personnummer.Options { AllowCoordinationNumber = false, TimeProvider = timeProvider }).Age);
Assert.Equal(years, Personnummer.Parse(ssn.LongFormat, new Personnummer.Options { AllowCoordinationNumber = false, TimeProvider = timeProvider }).Age);
// Du to age not being possible to fetch from >100 year short format without separator, we aught to check this here.
Assert.Equal(years > 99 ? years - 100 : years, Personnummer.Parse(ssn.ShortFormat, new Personnummer.Options { AllowCoordinationNumber = false }).Age);
Assert.Equal(years > 99 ? years - 100 : years, Personnummer.Parse(ssn.ShortFormat, new Personnummer.Options { AllowCoordinationNumber = false, TimeProvider = timeProvider }).Age);
}

[Theory]
Expand Down
20 changes: 20 additions & 0 deletions Personnummer.Tests/TestTimeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace Personnummer.Tests;

/// <summary>
/// TimeProvider which always returns the same date: 2025 01 01 00:00:01 with UTC timezone on local time.
/// </summary>
public class TestTimeProvider : TimeProvider
{
public override DateTimeOffset GetUtcNow()
{
return new DateTimeOffset(
new DateOnly(2025, 1, 1),
new TimeOnly(0,0,0, 1),
TimeSpan.Zero
);
}

public override TimeZoneInfo LocalTimeZone { get; } = TimeZoneInfo.Utc;
}
23 changes: 17 additions & 6 deletions Personnummer/Personnummer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ public Options()
public bool AllowCoordinationNumber { get; set; } = true;

public bool AllowInterimNumber { get; set; } = false;

/// <summary>
/// TimeProvider to use in calculations which are time dependent.<br/>
/// Uses `GetLocalNow` method.
/// <remarks>
/// Defaults to System provider.
/// </remarks>
/// </summary>
public TimeProvider TimeProvider { get; set; } = TimeProvider.System;
}

#region Fields and Properties
Expand All @@ -28,7 +37,7 @@ public int Age
{
get
{
var now = DateTime.Now;
var now = _options.TimeProvider.GetLocalNow();
var age = now.Year - Date.Year;

if (now.Month >= Date.Month && now.Day > Date.Day)
Expand Down Expand Up @@ -64,6 +73,8 @@ public int Age

#endregion

private readonly Options _options;

/// <summary>
/// Create a new Personnummber object from a string.
///
Expand All @@ -74,15 +85,15 @@ public int Age
/// <param name="options">Options object.</param>
public Personnummer(string ssn, Options? options = null)
{
options ??= new Options();
_options = options ?? new Options();
if (ssn.Length > 13 || ssn.Length < 10)
{
var state = ssn.Length < 10 ? "short" : "long";
throw new PersonnummerException($"Input string too {state}");
}


if (options.Value.AllowInterimNumber == false && Regex.IsMatch(ssn.Trim(), InterimTest))
if (_options.AllowInterimNumber == false && Regex.IsMatch(ssn.Trim(), InterimTest))
{
throw new PersonnummerException(
$"{ssn} contains non-integer characters and options are set to not allow interim numbers"
Expand Down Expand Up @@ -113,7 +124,7 @@ public Personnummer(string ssn, Options? options = null)
}
else
{
int born = DateTime.Now.Year - int.Parse(decade);
int born = TimeProvider.System.GetLocalNow().Year - int.Parse(decade);
if (groups["separator"].Value.Length != 0 && groups["separator"].Value == "+")
{
born -= 100;
Expand All @@ -124,7 +135,7 @@ public Personnummer(string ssn, Options? options = null)

// Create date time object.
int day = int.Parse(groups["day"].Value);
if (options.Value.AllowCoordinationNumber)
if (_options.AllowCoordinationNumber)
{
IsCoordinationNumber = day > 60;
day = IsCoordinationNumber ? day - 60 : day;
Expand Down Expand Up @@ -153,7 +164,7 @@ public Personnummer(string ssn, Options? options = null)
}

var num = groups["numbers"].Value;
if (options.Value.AllowInterimNumber)
if (_options.AllowInterimNumber)
{
num = Regex.Replace(num, InterimTest, "1");
}
Expand Down

0 comments on commit ad8dcda

Please sign in to comment.