Skip to content

Escape one character trimmed date formats - #1858

Merged
SimonCropp merged 1 commit into
mainfrom
fix-trimmed-fraction-format
Aug 17, 2026
Merged

Escape one character trimmed date formats#1858
SimonCropp merged 1 commit into
mainfrom
fix-trimmed-fraction-format

Conversation

@SimonCropp

Copy link
Copy Markdown
Member

DateMatchers.BuildForFormats builds a second scrubber for the trimmed format when a format ends in an upper case fraction, since those render as empty when the fraction is zero. TryGetFormatWithUpperMillisecondsTrimmed handed back the trimmed string raw, and a one character format string is read as a standard format specifier rather than the custom one it was written as:

  • ScrubInlineDateTimes("s.F") trimmed to "s", which expands to the culture's sortable pattern — so the second scrubber scrubbed every full sortable date-time in the output.
  • ScrubInlineDateTimes("H.F") trimmed to "H", which is not a standard specifier at all — Invalid format: H at registration, after the format had already passed the up-front validation.

Now % escapes the trimmed format when one character is left, which is what forces it to be read as a custom specifier, and no second scrubber is built when trimming leaves nothing (".F").

Tests in the new TrimmedFractionTests cover all three cases; all three fail on main. ApplyScrubbersTests (114), Verify.Tests (1299) and StaticSettingsTests pass.

Unrelated observation while in here: the trim only recognises .F through .FFFF, so .FFFFF through .FFFFFFF never get a trimmed scrubber. Left alone since TryParseExact treats the fraction (and its period) as optional anyway, which makes the second scrubber look redundant rather than missing — worth a separate look at whether it earns its keep at all.

Formats ending in an upper case fraction get a second scrubber built for the
trimmed format, but a one character format string is a standard format specifier
rather than the custom one it was written as. So ScrubInlineDateTimes("s.F")
trimmed to "s", the sortable pattern, and scrubbed every full sortable date-time
in the output, while "H.F" trimmed to "H" and threw "Invalid format: H" at
registration despite passing the up-front validation.
The trimmed format is now escaped with `%` when a single character is left, and
no second scrubber is built when nothing is left at all.
@SimonCroppSimonCropp added this to the 32.0.0 milestone Aug 17, 2026
@SimonCropp
SimonCropp merged commit 724341b into mainAug 17, 2026
7 checks passed
@SimonCropp
SimonCropp deleted the fix-trimmed-fraction-format branch August 17, 2026 12:26
This was referenced Aug 26, 2026
legrab added a commit to legrab/pocok that referenced this pull request Sep 1, 2026
Updated [Verify.NUnit](https://github.com/VerifyTests/Verify) from
31.28.0 to 32.0.0.
<details>
<summary>Release notes</summary>
_Sourced from [Verify.NUnit's
releases](https://github.com/VerifyTests/Verify/releases)._
## 32.0.0
- [x] [#​1832](VerifyTests/Verify#1832) Add
Inline snpashot support
- [x] [#​1835](VerifyTests/Verify#1835) Add
SettingsTask.AddContext
- [x] [#​1847](VerifyTests/Verify#1847) Honor
explicit DefaultValueHandling for bools
- [x] [#​1850](VerifyTests/Verify#1850) Stray
extension-less snapshot file crashes every run of its test
- [x] [#​1851](VerifyTests/Verify#1851) Static
UniqueForTargetFramework(Assembly) overloads ignore the assembly passed
to them
- [x] [#​1852](VerifyTests/Verify#1852)
Appended files skip scrubbing and newline normalization when a stream
converter is registered
- [x] [#​1853](VerifyTests/Verify#1853)
IgnoreParameters() ignore-all sentinel is lost when combined with
another ignore source
- [x] [#​1854](VerifyTests/Verify#1854) Format
UTC offsets with the invariant culture
- [x] [#​1855](VerifyTests/Verify#1855) Format
UTC offsets with invariant culture
- [x] [#​1856](VerifyTests/Verify#1856) Use
ticks to decide date precision
- [x] [#​1857](VerifyTests/Verify#1857) Clean #
out of file name parameters
- [x] [#​1858](VerifyTests/Verify#1858) Escape
one character trimmed date formats
- [x] [#​1859](VerifyTests/Verify#1859) Prefer
the exact type when resolving a member converter
- [x] [#​1860](VerifyTests/Verify#1860) Keep
empty collections ignored alongside instance predicates
- [x] [#​1861](VerifyTests/Verify#1861) Key the
combinations name cache by reference
- [x] [#​1862](VerifyTests/Verify#1862) Match
MSTest overloads against the test data
- [x] [#​1863](VerifyTests/Verify#1863) Report
deletes as directory relative paths
- [x] [#​1864](VerifyTests/Verify#1864) Name the
guarded API in ThrowIfVerifyHasBeenRun
- [x] [#​1865](VerifyTests/Verify#1865) Keep
stack frames that have no parameter list
- [x] [#​1866](VerifyTests/Verify#1866) Generate
the TestContext property for record test classes
- [x] [#​1867](VerifyTests/Verify#1867) Fix two
CombinationResultsConverter edge cases
- [x] [#​1868](VerifyTests/Verify#1868) Sign
sub-hour UTC offsets
- [x] [#​1869](VerifyTests/Verify#1869) Write
the received file from the stream for handle based FileStreams
- [x] [#​1870](VerifyTests/Verify#1870) Compare
prefixes ignoring case
- [x] [#​1871](VerifyTests/Verify#1871) Guard
the counter value caches with a lock
- [x] [#​1872](VerifyTests/Verify#1872) Sign
sub-hour UTC offsets
## 32.0.0-beta.16
## 32.0.0-beta.15
## 32.0.0-beta.14
## 32.0.0-beta.13
## 32.0.0-beta.12
## 32.0.0-beta.10
## 32.0.0-beta.8
## 32.0.0-beta.7
## 32.0.0-beta.6
## 32.0.0-beta.5
## 32.0.0-beta.4
## 32.0.0-beta.3
## 32.0.0-beta.2
## 32.0.0-beta.1
Commits viewable in [compare
view](VerifyTests/Verify@31.28.0...32.0.0).
</details>
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Verify.NUnit&package-manager=nuget&previous-version=31.28.0&new-version=32.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@SimonCropp