Skip to content

feat: implement mock TimeZoneInfo in testing package#1049

Merged
vbreuss merged 5 commits into
mainfrom
feat/timezoneinfo-mock
Jul 11, 2026
Merged

feat: implement mock TimeZoneInfo in testing package#1049
vbreuss merged 5 commits into
mainfrom
feat/timezoneinfo-mock

Conversation

@vbreuss

@vbreuss vbreuss commented Jul 11, 2026

Copy link
Copy Markdown
Member

Adds the mock implementation of ITimeZoneInfo to MockTimeSystem, making the local time zone deterministically controllable in tests.

  • Implement TimeZoneInfoMock and expose it via MockTimeSystem.TimeZoneInfo.
  • Add LocalTimeZone plus a seeded, overridable time-zone registry (RegisterTimeZone / FindSystemTimeZoneById / GetSystemTimeZones) to ITimeProvider; the registry is seeded from the host and can be extended.
  • TimeProviderFactory: Now/Use default the local time zone to TimeZoneInfo.Local, Random picks from a curated, cross-platform-stable set (incl. a daylight-saving zone), and a new Use(time, localTimeZone) overload allows an explicit zone.
  • Route DateTimeMock.Now/Today through the configured local time zone and resolve the ToTimeProvider().LocalTimeZone bridge.
  • MockTimeSystem.DateTimeOffset is temporarily stubbed; the mock is added in the follow-up DateTimeOffset change.
  • Reset BuildScope to Default and reference the 10.3.0-pre.1 interface pre-release.

Adds the mock implementation of `ITimeZoneInfo` to `MockTimeSystem`, making
the local time zone deterministically controllable in tests.

- Implement `TimeZoneInfoMock` and expose it via `MockTimeSystem.TimeZoneInfo`.
- Add `LocalTimeZone` plus a seeded, overridable time-zone registry
  (`RegisterTimeZone` / `FindSystemTimeZoneById` / `GetSystemTimeZones`) to
  `ITimeProvider`; the registry is seeded from the host and can be extended.
- `TimeProviderFactory`: `Now`/`Use` default the local time zone to
  `TimeZoneInfo.Local`, `Random` picks from a curated, cross-platform-stable
  set (incl. a daylight-saving zone), and a new `Use(time, localTimeZone)`
  overload allows an explicit zone.
- Route `DateTimeMock.Now`/`Today` through the configured local time zone and
  resolve the `ToTimeProvider().LocalTimeZone` bridge (closes #1039).
- `MockTimeSystem.DateTimeOffset` is temporarily stubbed; the mock is added
  in the follow-up `DateTimeOffset` change.
- Reset `BuildScope` to `Default` and reference the `10.3.0-pre.1` interface
  pre-release.
@vbreuss vbreuss self-assigned this Jul 11, 2026
@vbreuss vbreuss added the enhancement New feature or request label Jul 11, 2026
vbreuss added 2 commits July 11, 2026 02:41
…ption`

- Move the runtime-agnostic `ITimeZoneInfo` tests into
  `Testably.Abstractions.Tests` so they run against the real and the mock
  implementation and are verified to behave identically.
- `FindSystemTimeZoneById` for an unknown id now verifies the exact
  exception message and HResult.
- Centralize the `TimeZoneNotFoundException` in `ExceptionFactory` with an
  English message (matching the real BCL) instead of throwing inline.
- Force the test run to `InvariantCulture` so localized exception messages
  (on .NET Framework) stay deterministic and English.
- Document the local time zone configuration in the time-system docs.
`SetTo` stored the value as-is, so an `Unspecified`-kind value was later
treated as host-local, disagreeing with the constructor (`Use`), which
treats `Unspecified` as UTC. Normalize `SetTo` the same way (Unspecified
-> UTC, otherwise convert to UTC) so the simulated instant is canonical
UTC and the local-time-zone conversions read a true UTC input.

`Utc`/`Local` inputs are unaffected; only the `Unspecified` case changes,
aligning it with the documented `Use` semantics.
@vbreuss vbreuss force-pushed the feat/timezoneinfo-mock branch from 9754280 to b75d476 Compare July 11, 2026 00:52
@github-actions

Copy link
Copy Markdown

Test Results

     84 files  +     83       84 suites  +83   1h 59m 23s ⏱️ + 1h 59m 20s
107 237 tests +107 225   94 760 ✅ + 94 748  12 473 💤 +12 473   4 ❌ + 4 
246 938 runs  +246 926  210 984 ✅ +210 972  35 938 💤 +35 938  16 ❌ +16 

For more details on these failures, see this check.

Results for commit b75d476. ± Comparison against base commit 43a4cb6.

vbreuss added 2 commits July 11, 2026 09:29
Verify (against both the real and the mock time system) that
`DateTime.Now` and `DateTime.UtcNow` represent the same instant - i.e.
`Now.ToUniversalTime()` falls between two surrounding `UtcNow` reads - and
that `Now` has `DateTimeKind.Local`.
Defer allocation and host seeding of the TimeProviderMock time-zone
registry until FindSystemTimeZoneById/GetSystemTimeZones/RegisterTimeZone
is actually used, so mocks that only read the time pay no allocation and
never enumerate the host time zones. Add a Dictionary.TryAdd polyfill for
netstandard2.0.

Also:
- Guard TimeProviderFactory.Use(DateTime, TimeZoneInfo) against a null
  time zone and cover it with a test.
- Trim the thread-aware provider docs example to the members relevant to
  the per-thread clock.
- Restyle the time tests and tighten the NowAndUtcNow tolerance.
@vbreuss vbreuss force-pushed the feat/timezoneinfo-mock branch from 6b03723 to 7d9ad75 Compare July 11, 2026 07:46
@vbreuss vbreuss merged commit 6742d72 into main Jul 11, 2026
9 of 10 checks passed
@vbreuss vbreuss deleted the feat/timezoneinfo-mock branch July 11, 2026 07:51
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

This is addressed in release v7.0.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request state: released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant