Skip to content

fix: parse day and fractional-second components in FromTimeSpan - #435

Open
Scott-Emberson wants to merge 1 commit into
OctopusDeploy:mainfrom
Scott-Emberson:fix/timespan-parsing
Open

fix: parse day and fractional-second components in FromTimeSpan#435
Scott-Emberson wants to merge 1 commit into
OctopusDeploy:mainfrom
Scott-Emberson:fix/timespan-parsing

Conversation

@Scott-Emberson

Copy link
Copy Markdown

Fixes #434.

FromTimeSpan read the time span fields at fixed offsets and took the day component from timeSpan[0:0], which is always the empty string. Every value carrying a day component parsed to zero, fractional seconds were dropped, and an empty string panicked on a slice bound. Only the plain hh:mm:ss form worked.

The fixed offsets also assumed a single-digit day, and the server does not pad the day component, so "7.12:30:00" and "07.12:30:00" could not both be read correctly even with the days segment fixed.

What it returned before

FromTimeSpan("00:00:00")       = 0s          correct
FromTimeSpan("01:00:00")       = 1h0m0s      correct
FromTimeSpan("1.00:00:00")     = 0s          want 24h
FromTimeSpan("02.00:00:00")    = 0s          want 48h
FromTimeSpan("7.12:30:00")     = 12h30m0s    want 180h30m
FromTimeSpan("37500.00:00:00") = 50h0m0s     want 900000h
FromTimeSpan("00:00:00.50000") = 0s          want 500ms
FromTimeSpan("")               = panic: slice bounds out of range [:4] with length 0

"1.00:00:00" is the health check interval on the default machine policy, so this sits on a common path.

The change

Split on the separators rather than slicing at fixed offsets. Both uses of . are ambiguous (d.hh:mm:ss against hh:mm:ss.fffffff), so a leading segment is only treated as the day component when what follows still holds a complete hh:mm:ss. The fractional part is read as a decimal fraction of a second, which handles both the five-digit form this package writes and the seven-digit form .NET produces. Malformed input returns a zero duration instead of panicking.

Applied to pkg/machinepolicies/ and pkg/machines/, which each carry their own copy of the function. Patching only one would leave consumers of the two packages parsing the same payload differently.

Tests

pkg/machines/duration_formatter_test.go called FromTimeSpan seven times and logged each result without asserting anything. All seven returned 0s and the test passed, which is why this went unnoticed. It now asserts, along with a round trip check over ToTimeSpan, and the same file is added to pkg/machinepolicies/.

Verified against a 2026.x server: a policy with a seven day interval now reads back as 168h0m0s instead of 0s.

ToTimeSpan is unchanged. The server accepts its zero-padded day output and normalises it on read.

Impact

Any time.Duration read back through FromTimeSpan was affected. In MachinePolicy that covers ConnectionConnectTimeout, ConnectionRetrySleepInterval, ConnectionRetryTimeLimit, PollingRequestQueueTimeout and PollingRequestMaximumMessageProcessingTimeout, plus MachineHealthCheckPolicy.HealthCheckInterval and MachineCleanupPolicy.DeleteMachinesElapsedTimeSpan.

Callers who were compensating for the zero values will see real durations after this. I could not find any such workaround in this repository.

FromTimeSpan read the time span fields at fixed offsets and took the day
component from timeSpan[0:0], which is always the empty string. Every value
carrying a day component therefore parsed to zero, including "1.00:00:00" —
the interval on the default machine policy — and any fractional seconds were
dropped. An empty string panicked on a slice bound.

Parse the components by separator instead. The day and fractional-second
parts are both optional, and the server does not pad the day component to a
fixed width, so offsets cannot be assumed. Malformed input now yields a zero
duration rather than a panic.

The existing tests only logged their results and asserted nothing, which is
why this went unnoticed; they now assert, and every case they already covered
was returning zero.

Closes OctopusDeploy#434

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FromTimeSpan returns 0 for any time span with a day component

1 participant