Skip to content

Add DateOnly and TimeOnly polyfills - #138

Merged
Tyrrrz merged 12 commits into
primefrom
copilot/add-polyfills-for-dateonly-and-timeonly
Aug 4, 2026
Merged

Tyrrrz merged 12 commits into
primefrom
copilot/add-polyfills-for-dateonly-and-timeonly

Conversation

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

DateOnly and TimeOnly (introduced in .NET 6) had no polyfill, so they were unavailable on older target frameworks.

Type polyfills

  • Added PolyShim/Net60/DateOnly.cs and PolyShim/Net60/TimeOnly.cs, guarded to compile only where the native types are unavailable (older .NET Core, .NET Framework, .NET Standard).
  • Backed by DateTime/ticks internally; cover construction, arithmetic (AddDays/AddMonths/AddYears, Add/AddHours/AddMinutes, IsBetween), comparison/equality, Deconstruct, ToString/Parse/TryParse/ParseExact/TryParseExact (string and ReadOnlySpan<char> overloads), TryFormat, and conversions to/from DateTime/TimeSpan.

Tests

  • Added PolyShim.Tests/Net60/DateOnlyTests.cs and TimeOnlyTests.cs covering the new API surface.

Docs

  • Regenerated Signatures.md.
  • Listed DateOnly/TimeOnly in Readme.md's type polyfills section.
using System;

// Works uniformly across all target frameworks
var date = new DateOnly(2023, 5, 17);
var time = new TimeOnly(13, 45, 30);
var dateTime = date.ToDateTime(time);

@Tyrrrz Tyrrrz added the enhancement New feature or request label Aug 3, 2026
@Tyrrrz
Tyrrrz marked this pull request as ready for review August 3, 2026 14:45
Copilot AI review requested due to automatic review settings August 3, 2026 14:45
@Tyrrrz

Tyrrrz commented Aug 3, 2026

Copy link
Copy Markdown
Owner

@copilot while the types have been introduced in .NET 6, have you checked whether all members you've polyfilled were also introduced in .NET 6 or maybe some of them were introduced later?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds polyfills for the .NET 6 DateOnly and TimeOnly types to PolyShim so consumers can use these APIs on older target frameworks, along with accompanying tests and documentation updates.

Changes:

  • Added System.DateOnly and System.TimeOnly polyfill implementations under PolyShim/Net60/.
  • Added Net60 test coverage for the new types.
  • Updated README and regenerated Signatures.md to include the new polyfilled types.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
Signatures.md Updates generated signature/type counts and lists DateOnly/TimeOnly.
Readme.md Documents DateOnly/TimeOnly as available type polyfills.
PolyShim/Net60/DateOnly.cs Introduces DateOnly polyfill implementation.
PolyShim/Net60/TimeOnly.cs Introduces TimeOnly polyfill implementation.
PolyShim.Tests/Net60/DateOnlyTests.cs Adds tests for DateOnly behavior.
PolyShim.Tests/Net60/TimeOnlyTests.cs Adds tests for TimeOnly behavior.
Suppressed comments (12)

PolyShim/Net60/TimeOnly.cs:259

  • TimeOnly.TryParse currently accepts strings that include a date component because it relies on DateTime.TryParse and then drops the date. Add a validation that the parsed date is the default (0001-01-01) produced by NoCurrentDateDefault, otherwise return false.
        if (
            s is not null
            && DateTime.TryParse(
                s,
                provider,

PolyShim/Net60/TimeOnly.cs:212

  • TimeOnly.ParseExact(string, string, ...) uses DateTime.ParseExact and then drops the date, which can allow date components to sneak in via a format that includes them. Implement ParseExact in terms of TryParseExact so the same date-component validation is applied consistently.
    public static TimeOnly ParseExact(
        string s,
        string format,
        IFormatProvider? provider,
        DateTimeStyles style = DateTimeStyles.None

PolyShim/Net60/TimeOnly.cs:225

  • TimeOnly.ParseExact(string, string[], ...) uses DateTime.ParseExact and then drops the date, which can allow date components to sneak in via a format that includes them. Implement ParseExact in terms of TryParseExact so the same date-component validation is applied consistently.
    public static TimeOnly ParseExact(
        string s,
        string[] formats,
        IFormatProvider? provider,
        DateTimeStyles style = DateTimeStyles.None

PolyShim/Net60/TimeOnly.cs:301

  • TimeOnly.TryParseExact should reject inputs that include a date component. Add a check that the parsed date is 0001-01-01 (the default date produced by NoCurrentDateDefault when only a time is present).
        if (
            s is not null
            && format is not null
            && DateTime.TryParseExact(
                s,

PolyShim/Net60/TimeOnly.cs:336

  • TimeOnly.TryParseExact (formats overload) should reject inputs that include a date component. Add a check that the parsed date is 0001-01-01 (the default date produced by NoCurrentDateDefault when only a time is present).
        if (
            s is not null
            && formats is not null
            && DateTime.TryParseExact(
                s,
                formats,

PolyShim/Net60/DateOnly.cs:145

  • DateOnly.ParseExact uses DateTime.ParseExact and then drops the time. That allows date+time formats/inputs to parse successfully and silently discard the time. Implement ParseExact in terms of TryParseExact so the same time-component validation is applied consistently.
    public static DateOnly ParseExact(
        string s,
        string format,
        IFormatProvider? provider,
        DateTimeStyles style = DateTimeStyles.None

PolyShim/Net60/DateOnly.cs:155

  • DateOnly.ParseExact (formats overload) uses DateTime.ParseExact and then drops the time. That allows date+time formats/inputs to parse successfully and silently discard the time. Implement ParseExact in terms of TryParseExact so the same time-component validation is applied consistently.
    public static DateOnly ParseExact(
        string s,
        string[] formats,
        IFormatProvider? provider,
        DateTimeStyles style = DateTimeStyles.None

PolyShim/Net60/DateOnly.cs:163

  • DateOnly.ParseExact(ReadOnlySpan, ...) currently parses via DateTime.ParseExact directly and bypasses any validation added to the string-based ParseExact/TryParseExact. Delegate span overloads to the string overloads to keep behavior consistent.
    public static DateOnly ParseExact(
        ReadOnlySpan<char> s,
        ReadOnlySpan<char> format,
        IFormatProvider? provider = null,
        DateTimeStyles style = DateTimeStyles.None
    ) =>

PolyShim/Net60/DateOnly.cs:178

  • DateOnly.ParseExact(ReadOnlySpan, string[] ...) currently parses via DateTime.ParseExact directly and bypasses any validation added to the string-based ParseExact/TryParseExact. Delegate span overloads to the string overloads to keep behavior consistent.
    public static DateOnly ParseExact(
        ReadOnlySpan<char> s,
        string[] formats,
        IFormatProvider? provider = null,
        DateTimeStyles style = DateTimeStyles.None
    ) => FromDateTime(DateTime.ParseExact(new string(s.ToArray()), formats, provider, style));

PolyShim/Net60/DateOnly.cs:194

  • DateOnly.TryParse should reject inputs that include a time component. Add a check that the parsed time-of-day is exactly 00:00:00.000 (otherwise return false).
        if (s is not null && DateTime.TryParse(s, provider, style, out var dateTime))
        {
            result = FromDateTime(dateTime);
            return true;
        }

PolyShim/Net60/DateOnly.cs:228

  • DateOnly.TryParseExact should reject inputs/formats that include a time component. Add a check that the parsed time-of-day is exactly 00:00:00.000 (otherwise return false).
        if (
            s is not null
            && format is not null
            && DateTime.TryParseExact(s, format, provider, style, out var dateTime)
        )

PolyShim/Net60/DateOnly.cs:256

  • DateOnly.TryParseExact (formats overload) should reject inputs/formats that include a time component. Add a check that the parsed time-of-day is exactly 00:00:00.000 (otherwise return false).
        if (
            s is not null
            && formats is not null
            && DateTime.TryParseExact(s, formats, provider, style, out var dateTime)
        )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread PolyShim/Net60/DateOnly.cs
Comment thread PolyShim/Net60/TimeOnly.cs Outdated
Comment thread PolyShim/Net60/TimeOnly.cs Outdated
Comment thread PolyShim/Net60/TimeOnly.cs Outdated
Comment thread PolyShim/Net60/TimeOnly.cs Outdated
Comment thread PolyShim/Net60/DateOnly.cs Outdated
Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs
Comment thread PolyShim.Tests/Net60/TimeOnlyTests.cs

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot while the types have been introduced in .NET 6, have you checked whether all members you've polyfilled were also introduced in .NET 6 or maybe some of them were introduced later?

Good catch — not all polyfilled members were from .NET 6. Here's what I found and fixed:

Deconstruct methods — introduced in .NET 8 (dotnet/runtime#79499, merged January 2023):

  • DateOnly.Deconstruct(out int year, out int month, out int day)
  • TimeOnly.Deconstruct(out int hour, out int minute) (and the 3- and 4-param overloads)

These were previously placed in Net60/ (incorrect). I've moved them to Net80/ as extension methods using the standard PolyShim pattern, with a !NET8_0_OR_GREATER guard. The type polyfills in Net60/ no longer include them.

TimeOnly.ToDateTime() (no parameters) — not a public .NET API at all: This method is internal in the .NET runtime and was never part of the public surface. I changed it from public to private (it's only used internally by the ToString overloads).

All other polyfilled members (IsBetween, AddHours, AddMinutes, Add, FromDateTime, FromTimeSpan, ToTimeSpan, Parse/TryParse/ParseExact/TryParseExact, TryFormat, constructors, properties, operators) were correctly introduced in .NET 6.

The Signatures.md was regenerated automatically and now correctly marks Deconstruct entries as .NET 8.0. Commit: 78e24a5

Copilot AI requested a review from Tyrrrz August 3, 2026 15:10
Tyrrrz and others added 3 commits August 3, 2026 18:15
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@Tyrrrz

Tyrrrz commented Aug 3, 2026

Copy link
Copy Markdown
Owner

@copilot please see other review comments and also fix build errors

Copilot AI added 2 commits August 3, 2026 15:22
…yfills

- Add missing private readonly DateTime _dateTime field to DateOnly struct
- Fix DateOnly.TryParse/TryParseExact to reject inputs with time components
- Fix DateOnly.Parse/ParseExact to delegate to TryParse/TryParseExact and throw FormatException on failure
- Fix TimeOnly.TryParse/TryParseExact to reject inputs with date components
- Fix TimeOnly.Parse/ParseExact to delegate to TryParse/TryParseExact and throw FormatException on failure
- Remove non-BCL ToDateTime(DateOnly) overloads from TimeOnly (BCL does not expose these)
- Add DateOnly test: TryParse rejects input with time component
- Add TimeOnly test: TryParse rejects input with date component

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please see other review comments and also fix build errors

Fixed in commit Fix build errors and address review comments in DateOnly/TimeOnly polyfills:

  • Build error: Added the missing private readonly DateTime _dateTime field declaration to DateOnly struct.
  • DateOnly parsing: TryParse/TryParseExact now reject inputs with a non-zero time component; Parse/ParseExact delegate to the Try variants and throw FormatException on failure.
  • TimeOnly parsing: TryParse/TryParseExact now reject inputs where the parsed date differs from DateTime.MinValue.Date (i.e., an explicit date was present); Parse/ParseExact delegate to the Try variants and throw FormatException on failure.
  • TimeOnly.ToDateTime(DateOnly): Not added — the native BCL System.TimeOnly (verified on .NET 10) exposes no ToDateTime methods. The correct BCL API for combining is DateOnly.ToDateTime(TimeOnly), which is already in the polyfill. The parameterless ToDateTime() helper remains private.
  • Tests: Added TryParse_WithTimeComponent_Failure_Test to DateOnlyTests and TryParse_WithDateComponent_Failure_Test to TimeOnlyTests.

@codecov

codecov Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.24%. Comparing base (0d900be) to head (9ab8750).

Additional details and impacted files
@@            Coverage Diff             @@
##            prime     #138      +/-   ##
==========================================
+ Coverage   99.20%   99.24%   +0.03%     
==========================================
  Files         154      158       +4     
  Lines        4170     4383     +213     
  Branches       89       89              
==========================================
+ Hits         4137     4350     +213     
  Misses         13       13              
  Partials       20       20              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs Outdated
Copilot AI requested a review from Tyrrrz August 3, 2026 16:29

@Tyrrrz Tyrrrz left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Also apply the same suggestions to other test classes (not just DateOnly), where it's applicable @copilot

Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs Outdated
Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs Outdated
Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs Outdated
Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs Outdated
Comment thread PolyShim.Tests/Net60/DateOnlyTests.cs Outdated

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Also apply the same suggestions to other test classes (not just DateOnly), where it's applicable @copilot

Done — applied all the same consolidations to TimeOnlyTests: Constants_Test at top, Add_Test merges Add/Add_WrapsAroundMidnight/AddHours/AddMinutes, IsBetween_Test merges the midnight-wrapping case, From_Test merges FromDateTime/FromTimeSpan, and Equals_Test absorbs Operators_Test and SubtractionOperator_WrapsAroundMidnight_Test.

Copilot AI requested a review from Tyrrrz August 3, 2026 17:48
Comment thread PolyShim/Net60/TimeOnly.cs
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Copilot AI requested a review from Tyrrrz August 3, 2026 19:47
Comment thread PolyShim/Net60/TimeOnly.cs Outdated
…uctor like DateOnly

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Copilot AI requested a review from Tyrrrz August 3, 2026 20:53
@Tyrrrz
Tyrrrz requested a lite review from Copilot August 4, 2026 07:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

PolyShim/Net60/TimeOnly.cs:114

  • FromTimeSpan(TimeSpan) currently accepts values outside the valid time-of-day range (e.g. >= 24h). That can produce TimeOnly instances whose Ticks/ToTimeSpan() represent more than a day, which is inconsistent with TimeOnly semantics (and differs from the BCL API, which throws for out-of-range inputs).
    public static TimeOnly FromDateTime(DateTime dateTime) =>
        new(new DateTime(dateTime.TimeOfDay.Ticks));

    public static TimeOnly FromTimeSpan(TimeSpan timeSpan) => new(new DateTime(timeSpan.Ticks));

@Tyrrrz
Tyrrrz merged commit a13c658 into prime Aug 4, 2026
8 checks passed
@Tyrrrz
Tyrrrz deleted the copilot/add-polyfills-for-dateonly-and-timeonly branch August 4, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants