Table of Contents

Time-of-day and DST (testing) — System Clock stack

Deterministic DST tests under a virtual PrimeTestClock. The System Clock stack does not bundle a TZDB zone library, so the examples lean on environment probes for invalid/ambiguous calendar samples plus a stable virtual-clock advancement check.

/// <summary>
/// When the machine exposes a spring-forward gap example, documents that the BCL marks that
/// wall-clock value as invalid for <see cref="TimeZoneInfo.Local"/>.
/// </summary>
[Fact]
public void MachineProbe_InvalidExample_WhenPresent_IsInvalidLocalTime ()
{
    TestEnvironmentDescriptor descriptor = TestEnvironmentDescriptor.FromLocalMachine();
    if (descriptor.InvalidLocalWallClockExample is not DateTime invalid)
    {
        return;
    }

    TimeZoneInfo.Local.IsInvalidTime(invalid).Should().BeTrue();
}

/// <summary>
/// When the machine exposes a fall-back ambiguous example, documents that the BCL reports two
/// distinct UTC offsets for the same local wall-clock calendar value.
/// </summary>
[Fact]
public void MachineProbe_AmbiguousExample_WhenPresent_HasTwoOffsets ()
{
    TestEnvironmentDescriptor descriptor = TestEnvironmentDescriptor.FromLocalMachine();
    if (descriptor.AmbiguousLocalWallClockExample is not DateTime ambiguous)
    {
        return;
    }

    TimeZoneInfo.Local.IsAmbiguousTime(ambiguous).Should().BeTrue();
    TimeSpan[] offsets = TimeZoneInfo.Local.GetAmbiguousTimeOffsets(ambiguous);
    offsets.Should().HaveCount(2);
}

/// <summary>
/// Verifies advancing virtual UTC time across a long step does not throw for the default test clock,
/// providing a stable baseline on hosts without rich DST probe data.
/// </summary>
[Fact]
public void VirtualClock_AdvanceLargeStep_Completes ()
{
    IPrimeTestClock clock = new PrimeTestClock(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero));
    Action act = () => clock.Advance(TimeSpan.FromDays(400));
    act.Should().NotThrow();
}

What to notice

  • TestEnvironmentDescriptor.FromLocalMachine() wraps the host's TimeZoneInfo.Local. It returns null for the invalid/ambiguous samples on hosts without DST data, so each test early-returns instead of asserting against a missing sample.
  • The third test demonstrates a stable baseline: advancing the virtual clock 400 days never throws, regardless of DST behavior of the host.
  • For deterministic DST behavior independent of the host, prefer the PrimeTime / NodaTime stack, which can construct a PrimeTestClock against a fixed TZDB DateTimeZone.