This is in part an emergency hotfix release to fix an issue on macOS where Studio was unable to build apps for local deployment. This was a regression introduced in 26.8.1 caused by a change in how Studio signed built macOS desktop applications. This issue is now resolved.
The sharp-eyed amongst you will also notice that we jumped from 26.8.1 to 26.8.3, that was due to a slight wrinkle with packaging which is now resolved.
I couldn’t release Studio however without adding something special, so I’d like to talk about Objo’s new Duration type.
Introducing Duration
As always, all known and reported bugs have been fixed. This release also brings a new built-in type that touches a great deal of the standard library, along with tooling in Studio to help you move to it. Because it changes several long-standing APIs, 26.8.3 is a breaking release — please read the migration notes below before upgrading your projects.
Objo now has a proper Duration value type that represents an amount of elapsed time. It stores a signed number of 100-nanosecond ticks and can be negative, zero, or positive. Every unassigned Duration defaults to Duration.Zero and can never be Nothing.
The point of the type is that a duration now carries its own unit with it. Instead of writing bare numbers and hoping you remember whether an API expected milliseconds or seconds, you write it exactly as you mean it:
Var retryDelay As Duration = 250.Milliseconds()
Var requestTimeout As Duration = 30.Seconds()
Var cacheLifetime As Duration = 2.Days()
Var animationStep As Duration = 0.08.Seconds()
Await Task.Delay(retryDelay)
Because there is no implicit conversion from Integer or Double to Duration, a bare number is no longer accepted where a duration belongs:
Var delay As Duration = 500 # ERROR - choose a unit
Var delay As Duration = 500.Milliseconds() # OK
Creating and inspecting durations
You can call a unit method on any Integer or Double (Milliseconds(), Seconds(), Minutes(), Hours(), and Days()) or use a shared factory such as Duration.FromMilliseconds(value). Duration.Zero is the default value, and Duration.Infinite is a no-timeout sentinel for the few APIs that explicitly support it.
Durations support the usual arithmetic (+, -, *, /), comparison, equality, and hashing, and expose read-only properties such as Ticks, TotalSeconds, TotalHours, TotalDays, and IsInfinite. See the Duration reference for the full surface and the rounding and range rules.
API changes
Every genuine elapsed-time API in the standard library now uses Duration where it previously took or returned a bare Integer or Double. This is the breaking change: existing calls must be updated to express a unit.
Inputs that now take a Duration include System.Sleep, Task.Delay, the CancellationTokenSource and Timer constructors, Timer.Interval, Shell.Timeout, the Timeout property on Database (and the SQLite, MySQL, PostgreSQL, SQL Server and ODBC subclasses), SerialConnection.ReadTimeout and WriteTimeout, the Timeout property on URLConnection, TCPClient, UDPSocket and WebSocket, AudioPlayer.Position, and the frame duration of Sprite.AddAnimation.
Outputs that now return a Duration include DateTime.Subtract, AudioPlayer.Duration, and the time-zone values below.
Renamed members:
| Removed member | Replacement |
|---|---|
DateTime.AddDays(value) |
DateTime.Add(value.Days()) |
DateTime.AddHours(value) |
DateTime.Add(value.Hours()) |
DateTime.AddMinutes(value) |
DateTime.Add(value.Minutes()) |
DateTime.AddSeconds(value) |
DateTime.Add(value.Seconds()) |
TimeZone.BaseOffsetSeconds |
TimeZone.BaseOffset |
TimeZone.OffsetSecondsAt(date) |
TimeZone.OffsetAt(date) |
DateTime now has a single elapsed-time addition method, Add(Duration). Month and year arithmetic remains separate, because calendar months and years do not have a fixed elapsed-time length.
The “zero-means-no-timeout” convention is gone
In the old numeric APIs, several properties used 0 to mean “no timeout”: Shell.Timeout, and SerialConnection.ReadTimeout and WriteTimeout. That implicit convention is replaced by an explicit sentinel:
shell.Timeout = Duration.Infinite
serial.ReadTimeout = Duration.Infinite
serial.WriteTimeout = Duration.Infinite
Duration.Zero now always means a real zero duration (an immediate timeout). Duration.Infinite means no timeout, and only for the APIs that document support for it.
Moving your projects across
Studio ships a guided migration to make the switch as painless as possible. Choose Solution > Migrate Project to Duration API… and Studio analyses your application and test sources, grouping each change:
- Safe items preserve the old behaviour and can be applied together as one undoable change.
- Review required items need a decision, such as changing a variable’s type to
Durationor preserving a possible zero sentinel without evaluating an expression twice.
You can apply the safe group from the preview, use Apply Fix for a single diagnostic, or use Fix All Safe Duration Migrations for the currently reported safe fixes. Studio re-analyses after each change and never edits generated or external sources, and it refuses to apply a fix if the source changed since analysis.
New analyser diagnostics report each kind of migration: a legacy numeric input needing the old member’s unit, a Duration output used where a number was expected, a removed or renamed time API, a possible legacy zero sentinel, and a numeric/Duration mismatch with no safely inferable unit.
After migrating, run the project and its tests, and check any timeout that previously used zero. For output values, prefer keeping elapsed time as Duration through your calculations rather than repeatedly converting to and from numbers:
Var elapsedSeconds As Double = finishedAt.Subtract(startedAt).TotalSeconds
Full guidance, including the complete before/after table, is in the migration guide.