Skip to content

Add HaveMillisecond/NotHaveMillisecond assertion methods for DateTime and DateTimeOffset#3164

Merged
dennisdoomen merged 4 commits intomainfrom
copilot/add-millisecond-assertion
Mar 8, 2026
Merged

Add HaveMillisecond/NotHaveMillisecond assertion methods for DateTime and DateTimeOffset#3164
dennisdoomen merged 4 commits intomainfrom
copilot/add-millisecond-assertion

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 28, 2026

Implementation Complete ✓

  • Add HaveMillisecond and NotHaveMillisecond methods to DateTimeAssertions class
  • Add HaveMillisecond and NotHaveMillisecond methods to DateTimeOffsetAssertions class
  • Create test file for DateTime.HaveMillisecond (6 tests)
  • Create test file for DateTimeOffset.HaveMillisecond (6 tests)
  • Run targeted tests to validate implementation (All 18 tests passed)
  • Run broader test suite to ensure no regressions (355 DateTime/DateTimeOffset tests passed)
  • Manual verification with console app (All 4 manual tests passed)
  • Request code review (Completed - no blocking issues)
  • Update docs/_pages/releases.md with feature summary
  • Update test method naming to follow new convention
  • Run AcceptApiChanges.sh to update approved API
  • Verify all approval tests pass (All 4 approval tests passed)
  • Remove noisy words (should, when) from test names

Summary

Successfully implemented HaveMillisecond and NotHaveMillisecond assertion methods for both DateTime and DateTimeOffset types, following the existing patterns. All tests pass successfully with no regressions detected. Test naming has been refined to follow clean naming conventions without rigid and noisy words.

Original prompt

This section details on the original issue you should resolve

<issue_title>Method to assert the millisecond part of a DateTime / DateTimeOffset instance</issue_title>
<issue_description>### Background and motivation

Currently, FluentAssertions has methods to assert the year, month, day, hour, minute, second and offset of a DateTimeOffset (HaveX), but an equivalent method for the millisecond part is missing. Generally, it would be nice to assert the millisecond part in the style of FluentAssertions instead of using result.Millisecond.Should().Be(milliseconds).

I'm currently working on a library that provides convenience methods based on DateTime/DateTimeOffset and implement date and time related unit tests for it.

This is very easy to implement, because we can extend the DateTimeAssertions class and DateTimeOffsetAssertions class by a method called HaveMillisecond.

The implementation of this method could look like following:

public AndConstraint<DateTimeOffsetAssertions> HaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs)
{
    Execute.Assertion
      .ForCondition(this.Subject != null)
      .BecauseOf(because, becauseArgs)
      .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found <null>.", expectedMilliseconds);

    Execute.Assertion
      .ForCondition(this.Subject!.Value.Millisecond == expectedMilliseconds)
      .BecauseOf(because, becauseArgs)
      .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found {1}.", expectedMilliseconds, this.Subject.Value.Millisecond);

    return new AndConstraint<DateTimeOffsetAssertions>(this);
}

Also the opponent of HaveMillisecond, which would be NotHaveMillisecond, can be implemented this way by inverting / changing the condition to this.Subject!.Value.Millisecond != expectedMilliseconds.

API Proposal

public class DateTimeOffsetAssertions<TAssertions>
{
    public AndConstraint<DateTimeOffsetAssertions> HaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
    public AndConstraint<DateTimeOffsetAssertions> NotHaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
}
public class DateTimeAssertions<TAssertions>
{
    public AndConstraint<DateTimeAssertions> HaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
    public AndConstraint<DateTimeAssertions> NotHaveMillisecond(int expectedMilliseconds, string because = "", params object[] becauseArgs);
}

API Usage

var dateTimeOffset = DateTimeOffset.Parse("2024-08-23T00:00:00.123+00:00");
dateTimeOffset.Should().HaveMilliseconds(123);
dateTimeOffset.Should().NotHaveMilliseconds(124);

Alternative Designs

Alternately, you could write an extension method based on DateTimeOffsetAssertions class and add there the HaveMillisecond method. This would not directly change the API of FluentAssertions, but would not be consistent with the rest of the DateTime/DateTimeOffset method provided by FluentAssertions.

The extension method could be implemented like following:

internal static class DateTimeOffsetAssertionsExtensions
{
    internal static FluentAssertions.Primitives.DateTimeOffsetAssertions HaveMillisecond(
        this FluentAssertions.Primitives.DateTimeOffsetAssertions assertions,
        int expectedMilliseconds,
        string because = "",
        params object[] becauseArgs)
        {
            var subject = assertions.Subject;
            Execute.Assertion
                .ForCondition(subject != null)
                .BecauseOf(because, becauseArgs)
                .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found <null>.", expectedMilliseconds);

            Execute.Assertion
                .ForCondition(subject!.Value.Millisecond == expectedMilliseconds)
                .BecauseOf(because, becauseArgs)
                .FailWith("Expected {context:DateTimeOffset} to have milliseconds {0}{reason}, but found {1}.", expectedMilliseconds, subject.Value.Millisecond);

            return assertions;
        }
}

Risks

None, because it adds an extra method to assert the millisecond part of a DateTime / DateTimeOffset instance.

Are you willing to help with a proof-of-concept (as PR in that or a separate repo) first and as pull-request later on?

Yes, please assign this issue to me.</issue_description>

Comments on the Issue (you are @copilot ...


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI changed the title [WIP] Add method to assert millisecond part of DateTimeOffset Add HaveMillisecond/NotHaveMillisecond assertion methods for DateTime and DateTimeOffset Feb 28, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 3, 2026

Test Results

    37 files  ± 0      37 suites  ±0   6m 26s ⏱️ -23s
 6 309 tests +12   6 308 ✅ +12  1 💤 ±0  0 ❌ ±0 
39 200 runs  +72  39 194 ✅ +72  6 💤 ±0  0 ❌ ±0 

Results for commit eee7305. ± Comparison against base commit 27bd2f9.

This pull request removes 10 and adds 20 tests. Note that renamed tests count towards both.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HaveLength ‑ When_a_throwing_stream_should_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+HavePosition ‑ When_a_throwing_stream_should_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHaveLength ‑ When_a_throwing_stream_should_not_have_a_length_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
FluentAssertions.Specs.Streams.StreamAssertionSpecs+NotHavePosition ‑ When_a_throwing_stream_should_not_have_a_position_it_should_fail(exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GetLengthExceptionMessage'.)
Object name: 'GetPositionExceptionMessage'.)
FluentAssertions.Specs.Primitives.DateTimeAssertionSpecs+HaveMillisecond ‑ Fails_for_datetime_with_different_milliseconds
FluentAssertions.Specs.Primitives.DateTimeAssertionSpecs+HaveMillisecond ‑ Fails_for_null_datetime
FluentAssertions.Specs.Primitives.DateTimeAssertionSpecs+HaveMillisecond ‑ Succeeds_for_datetime_with_the_same_milliseconds
FluentAssertions.Specs.Primitives.DateTimeAssertionSpecs+NotHaveMillisecond ‑ Fails_for_datetime_with_the_same_milliseconds
FluentAssertions.Specs.Primitives.DateTimeAssertionSpecs+NotHaveMillisecond ‑ Fails_for_null_datetime
FluentAssertions.Specs.Primitives.DateTimeAssertionSpecs+NotHaveMillisecond ‑ Succeeds_for_datetime_with_different_milliseconds
FluentAssertions.Specs.Primitives.DateTimeOffsetAssertionSpecs+HaveMillisecond ‑ Fails_for_datetimeoffset_with_different_milliseconds
FluentAssertions.Specs.Primitives.DateTimeOffsetAssertionSpecs+HaveMillisecond ‑ Fails_for_null_datetimeoffset
FluentAssertions.Specs.Primitives.DateTimeOffsetAssertionSpecs+HaveMillisecond ‑ Succeeds_for_datetimeoffset_with_the_same_milliseconds
FluentAssertions.Specs.Primitives.DateTimeOffsetAssertionSpecs+NotHaveMillisecond ‑ Fails_for_datetimeoffset_with_the_same_milliseconds
…

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 3, 2026

Qodana for .NET

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at [email protected]

Copy link
Copy Markdown
Member

@jnyrup jnyrup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address the following points:

  • Update docs/_pages/releases.md to include a sub-item under "What's New" succiently summarizing the feature added in this PR
  • Run either of AcceptApiChanges.sh or AcceptApiChanges.ps1 to updates the approved API.
  • Update the naming of the added test methods to the folllow the new naming convention.
  • See e.g. Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNaN.cs

@dennisdoomen
Copy link
Copy Markdown
Member

@copilot please address the following points:

  • Update docs/_pages/releases.md to include a sub-item under "What's New" succiently summarizing the feature added in this PR
  • Run either of AcceptApiChanges.sh or AcceptApiChanges.ps1 to updates the approved API.
  • Update the naming of the added test methods to the folllow the new naming convention.
  • See e.g. Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNaN.cs

@copilot apply the requested changes

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot please address the following points:

  • Update docs/_pages/releases.md to include a sub-item under "What's New" succiently summarizing the feature added in this PR
    ...

All requested changes have been applied in commit fcb2dfc:

  1. ✅ Updated docs/_pages/releases.md with feature summary
  2. ✅ Ran AcceptApiChanges.sh to update approved API for all target frameworks (net47, net6.0, netstandard2.0, netstandard2.1)
  3. ✅ Updated test method naming to follow the new convention (e.g., Milliseconds_are_asserted_when_values_match instead of When_asserting_subject_datetime_should_have_milliseconds_with_the_same_value_it_should_succeed)

All tests pass: 18 HaveMillisecond tests, 4 approval tests, and no regressions in existing DateTime/DateTimeOffset tests.

@coveralls
Copy link
Copy Markdown

coveralls commented Mar 5, 2026

Pull Request Test Coverage Report for Build 22803252634

Details

  • 37 of 37 (100.0%) changed or added relevant lines in 2 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.006%) to 97.174%

Totals Coverage Status
Change from base Build 22318656339: 0.006%
Covered Lines: 12999
Relevant Lines: 13223

💛 - Coveralls

@jnyrup jnyrup marked this pull request as ready for review March 5, 2026 08:49
@jnyrup jnyrup requested a review from dennisdoomen March 7, 2026 17:20
@jnyrup jnyrup added this to the 8.9 milestone Mar 7, 2026
@dennisdoomen dennisdoomen merged commit ef3a1e0 into main Mar 8, 2026
14 checks passed
@dennisdoomen dennisdoomen deleted the copilot/add-millisecond-assertion branch March 8, 2026 07:00
This was referenced Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Method to assert the millisecond part of a DateTime / DateTimeOffset instance

4 participants