Skip to content

Commit

Permalink
Introduction of DateBuilder.
Browse files Browse the repository at this point in the history
  • Loading branch information
Corniel committed Aug 28, 2023
1 parent ced4545 commit 348d8d8
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 67 deletions.
105 changes: 105 additions & 0 deletions Src/FluentAssertions/Extensions/DateBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#nullable enable

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace FluentAssertions.Extensions;

/// <summary>A date builder that implicitly casts both to a date only and a date time.</summary>
public readonly struct DateBuilder : IEquatable<DateBuilder>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly DateTime date;

/// <summary>Initializes a new instance of the <see cref="DateBuilder"/> struct.</summary>
internal DateBuilder(int year, int month, int day)
=> date = new DateTime(year, month, day, 00, 00, 00, DateTimeKind.Unspecified);

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the <paramref name="time"/>.
/// </summary>
public DateTime At(TimeSpan time) => date.Date + time;

/// <summary>
/// Returns a new <see cref="DateTime"/> value for time with the specified
/// <paramref name="hours"/>, <paramref name="minutes"/> and optionally <paramref name="seconds"/>.
/// </summary>
public DateTime At(int hours, int minutes, int seconds = 0, int milliseconds = 0,
int microseconds = 0, int nanoseconds = 0)
{
if (microseconds is < 0 or > 999)
{
throw new ArgumentOutOfRangeException(nameof(microseconds), "Valid values are between 0 and 999");
}

if (nanoseconds is < 0 or > 999)
{
throw new ArgumentOutOfRangeException(nameof(nanoseconds), "Valid values are between 0 and 999");
}

var value = new DateTime(date.Year, date.Month, date.Day, hours, minutes, seconds, milliseconds, date.Kind);

if (microseconds != 0)
{
value += microseconds.Microseconds();
}

if (nanoseconds != 0)
{
value += nanoseconds.Nanoseconds();
}

return value;
}

/// <summary>
/// Returns a new <see cref="DateTime"/> with the kind set to <see cref="DateTimeKind.Utc"/>.
/// </summary>
public DateTime AsUtc() => date.AsUtc();

/// <summary>
/// Returns a new <see cref="DateTimeOffset"/> value without an offset.
/// </summary>
public DateTimeOffset AsOffset() => WithOffset(0.Hours());

/// <summary>
/// Returns a new <see cref="DateTimeOffset"/> value with specified offset.
/// </summary>
public DateTimeOffset WithOffset(TimeSpan offset = default)
=> new DateTimeOffset(date, offset);

Check notice on line 71 in Src/FluentAssertions/Extensions/DateBuilder.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Use preferred style of 'new' expression when created type is evident

Redundant type specification

/// <inheritdoc />
public override string ToString() => date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);

/// <inheritdoc />
public override int GetHashCode() => date.GetHashCode();

/// <inheritdoc />
public override bool Equals([NotNullWhen(true)] object? obj)
=> obj is DateBuilder other && Equals(other);

/// <inheritdoc />
public bool Equals(DateBuilder other)
=> date.Equals(other.date);

/// <summary>True if both <see cref="DateBuilder"/>s represent the same date.</summary>
public static bool operator ==(DateBuilder left, DateBuilder right)
=> left.Equals(right);

/// <summary>False if both <see cref="DateBuilder"/>s represent the same date.</summary>
public static bool operator !=(DateBuilder left, DateBuilder right)
=> !(left == right);

/// <summary>Adds a <see cref="TimeSpan"/> to the <see cref="DateTime"/>.</summary>
public static DateTime operator +(DateBuilder builder, TimeSpan time) => builder.At(+time);

/// <summary>Implicitly casts the <see cref="DateBuilder"/> to a <see cref="DateTime"/>.</summary>
public static implicit operator DateTime(DateBuilder builder) => builder.date;

#if NET6_0_OR_GREATER
/// <summary>Implicitly casts the <see cref="DateBuilder"/> to a <see cref="DateOnly"/>.</summary>
public static implicit operator DateOnly(DateBuilder builder) => DateOnly.FromDateTime(builder.date);
#endif
}
70 changes: 35 additions & 35 deletions Src/FluentAssertions/Extensions/FluentDateTimeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,111 +23,111 @@ namespace FluentAssertions.Extensions;
public static class FluentDateTimeExtensions
{
/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month January.
/// </summary>
public static DateTime January(this int day, int year)
public static DateBuilder January(this int day, int year)
{
return new DateTime(year, 1, day);
return new DateBuilder(year, 1, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month February.
/// </summary>
public static DateTime February(this int day, int year)
public static DateBuilder February(this int day, int year)
{
return new DateTime(year, 2, day);
return new DateBuilder(year, 2, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month March.
/// </summary>
public static DateTime March(this int day, int year)
public static DateBuilder March(this int day, int year)
{
return new DateTime(year, 3, day);
return new DateBuilder(year, 3, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month April.
/// </summary>
public static DateTime April(this int day, int year)
public static DateBuilder April(this int day, int year)
{
return new DateTime(year, 4, day);
return new DateBuilder(year, 4, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month May.
/// </summary>
public static DateTime May(this int day, int year)
public static DateBuilder May(this int day, int year)
{
return new DateTime(year, 5, day);
return new DateBuilder(year, 5, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month June.
/// </summary>
public static DateTime June(this int day, int year)
public static DateBuilder June(this int day, int year)
{
return new DateTime(year, 6, day);
return new DateBuilder(year, 6, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month July.
/// </summary>
public static DateTime July(this int day, int year)
public static DateBuilder July(this int day, int year)
{
return new DateTime(year, 7, day);
return new DateBuilder(year, 7, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month August.
/// </summary>
public static DateTime August(this int day, int year)
public static DateBuilder August(this int day, int year)
{
return new DateTime(year, 8, day);
return new DateBuilder(year, 8, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month September.
/// </summary>
public static DateTime September(this int day, int year)
public static DateBuilder September(this int day, int year)
{
return new DateTime(year, 9, day);
return new DateBuilder(year, 9, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month October.
/// </summary>
public static DateTime October(this int day, int year)
public static DateBuilder October(this int day, int year)
{
return new DateTime(year, 10, day);
return new DateBuilder(year, 10, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month November.
/// </summary>
public static DateTime November(this int day, int year)
public static DateBuilder November(this int day, int year)
{
return new DateTime(year, 11, day);
return new DateBuilder(year, 11, day);
}

/// <summary>
/// Returns a new <see cref="DateTime"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// Returns a new <see cref="DateBuilder"/> value for the specified <paramref name="day"/> and <paramref name="year"/>
/// in the month December.
/// </summary>
public static DateTime December(this int day, int year)
public static DateBuilder December(this int day, int year)
{
return new DateTime(year, 12, day);
return new DateBuilder(year, 12, day);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ public void When_a_nested_property_is_equal_based_on_equality_comparer_it_should
public void When_a_nested_property_is_unequal_based_on_equality_comparer_it_should_throw()
{
// Arrange
var subject = new { Timestamp = 22.March(2020) };
var subject = new { Timestamp = 22.March(2020).AsUtc() };

var expectation = new { Timestamp = 1.January(2021) };
var expectation = new { Timestamp = 1.January(2021).AsUtc() };

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation,
Expand Down
10 changes: 5 additions & 5 deletions Tests/FluentAssertions.Specs/Extensions/FluentDateTimeSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public void When_fluently_specifying_a_date_and_time_it_should_return_the_correc
public void When_fluently_specifying_a_datetimeoffset_and_time_it_should_return_the_correct_date_time_value()
{
// Act
DateTimeOffset dateTime = 10.December(2011).ToDateTimeOffset().At(09, 30, 45, 123, 456, 700);
DateTimeOffset dateTime = 10.December(2011).AsOffset().At(09, 30, 45, 123, 456, 700);

// Assert
dateTime.Should().Be(new DateTimeOffset(2011, 12, 10, 9, 30, 45, 123, TimeSpan.Zero).AddMicroseconds(456)
Expand Down Expand Up @@ -213,7 +213,7 @@ public void When_fluently_specifying_a_datetimeoffset_with_out_of_range_microsec
{
// Act
var expectedParameterName = "microseconds";
Action act = () => 10.December(2011).ToDateTimeOffset().At(0, 0, 0, 0, microseconds);
Action act = () => 10.December(2011).AsOffset().At(0, 0, 0, 0, microseconds);

// Assert
act.Should().Throw<ArgumentOutOfRangeException>()
Expand All @@ -226,7 +226,7 @@ public void When_fluently_specifying_a_datetimeoffset_with_out_of_range_microsec
public void When_fluently_specifying_a_datetimeoffset_with_inrange_microseconds_it_should_not_throw(int microseconds)
{
// Act
Action act = () => 10.December(2011).ToDateTimeOffset().At(0, 0, 0, 0, microseconds);
Action act = () => 10.December(2011).AsOffset().At(0, 0, 0, 0, microseconds);

// Assert
act.Should().NotThrow();
Expand All @@ -239,7 +239,7 @@ public void When_fluently_specifying_a_datetimeoffset_with_out_of_range_nanoseco
{
// Act
var expectedParameterName = "nanoseconds";
Action act = () => 10.December(2011).ToDateTimeOffset().At(0, 0, 0, 0, 0, nanoseconds);
Action act = () => 10.December(2011).AsOffset().At(0, 0, 0, 0, 0, nanoseconds);

// Assert
act.Should().Throw<ArgumentOutOfRangeException>()
Expand All @@ -252,7 +252,7 @@ public void When_fluently_specifying_a_datetimeoffset_with_out_of_range_nanoseco
public void When_fluently_specifying_a_datetimeoffset_with_inrange_nanoseconds_it_should_not_throw(int nanoseconds)
{
// Act
Action act = () => 10.December(2011).ToDateTimeOffset().At(0, 0, 0, 0, 0, nanoseconds);
Action act = () => 10.December(2011).AsOffset().At(0, 0, 0, 0, 0, nanoseconds);

// Assert
act.Should().NotThrow();
Expand Down
Loading

0 comments on commit 348d8d8

Please sign in to comment.