jaytwo.TimeExpression 0.1.0-beta-20251102202452

This is a prerelease version of jaytwo.TimeExpression.
dotnet add package jaytwo.TimeExpression --version 0.1.0-beta-20251102202452
                    
NuGet\Install-Package jaytwo.TimeExpression -Version 0.1.0-beta-20251102202452
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="jaytwo.TimeExpression" Version="0.1.0-beta-20251102202452" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="jaytwo.TimeExpression" Version="0.1.0-beta-20251102202452" />
                    
Directory.Packages.props
<PackageReference Include="jaytwo.TimeExpression" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add jaytwo.TimeExpression --version 0.1.0-beta-20251102202452
                    
#r "nuget: jaytwo.TimeExpression, 0.1.0-beta-20251102202452"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package jaytwo.TimeExpression@0.1.0-beta-20251102202452
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=jaytwo.TimeExpression&version=0.1.0-beta-20251102202452&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=jaytwo.TimeExpression&version=0.1.0-beta-20251102202452&prerelease
                    
Install as a Cake Tool

jaytwo.TimeExpression

NuGet Version NuGet Downloads License: MIT

jaytwo.TimeExpression provides human-readable time formatting utilities.

View source on GitHub β†’

🧭 Overview

This library offers a few complementary formatting systems:

Area Description
Go-style durations Human-readable and Go-compatible output like "1h2m3.5s", with options for non-canonical (but still Go-like) ASCII-only or compact forms.
Exact seconds Culture-invariant decimal seconds with full tick precision (e.g. "1.2345678", never "1E-3").
Exact milliseconds Same as Exact seconds, but in milliseconds (tick precision).

It’s tiny, dependency-free, and great for logs, metrics, or debugging output.

✨ Features

  • βœ… Go-compatible duration syntax Output identical to Go’s time.Duration.String() β€” compact, human-readable (1h2m3.5s, 1.23ms, 500Β΅s, 100ns).

  • βš™οΈ ASCII-safe output option Switch Β΅s β†’ us for compatibility with HTTP headers, log transport, or any ASCII-only system.

  • 🧩 Adaptive sub-second precision Compact mode automatically trims noisy fractional digits for cleaner log output (e.g. 9.9999999s β†’ 9.99s).

  • ⏱ ExactSeconds formatting Provides culture-invariant decimal seconds, at full tick precision (7 decimal places), and never uses scientific notation. Ideal for metrics or structured logs where consistent machine-readable values matter.

  • ⏱ ExactMilliseconds formatting Same idea as Exact Seconds, but in milliseconds (up to 4 decimals, still tick-precise).

  • πŸ’― Deterministic and dependency-free No runtime dependencies, no culture surprises, no rounding errors β€” just consistent, predictable output on every platform.

  • πŸ§ͺ Tiny and test-friendly Fast, allocation-light, and fully deterministic, perfect for loggers, middleware, benchmarks, or performance tracing.

πŸ“¦ Installation

Add via NuGet:

PM> Install-Package jaytwo.TimeExpression

Or using the .NET CLI:

dotnet add package jaytwo.TimeExpression

πŸš€ Usage

Go-style duration formatting

using jaytwo.TimeExpression.Golang;

var sampleTime = TimeSpan.FromMinutes(1.5);
var microseconds = TimeSpan.FromTicks(11); // 1.1 Β΅s

var oneTick = TimeSpan.FromTicks(1);
var almost10s = TimeSpan.FromSeconds(10) - oneTick;
var almost1m = TimeSpan.FromMinutes(1) - oneTick;
var almost1h = TimeSpan.FromHours(1) - oneTick;
var almost1d = TimeSpan.FromDays(1) - oneTick;
var almost1w = TimeSpan.FromDays(7) - oneTick;

// ToGoString (canonical Go behavior)
Console.WriteLine(GoDuration.ToGoString(TimeSpan.Zero));    // β†’ 0s
Console.WriteLine(GoDuration.ToGoString(sampleTime));       // β†’ 1m30s
Console.WriteLine(GoDuration.ToGoString(microseconds));     // β†’ 1.1Β΅s
Console.WriteLine(GoDuration.ToGoString(almost10s));        // β†’ 9.9999999s
Console.WriteLine(GoDuration.ToGoString(almost1m));         // β†’ 59.9999999s
Console.WriteLine(GoDuration.ToGoString(almost1h));         // β†’ 59m59.9999999s
Console.WriteLine(GoDuration.ToGoString(almost1d));         // β†’ 23h59m59.9999999s
Console.WriteLine(GoDuration.ToGoString(almost1w));         // β†’ 167h59m59.9999999s

// ToAsciiString (only affects microseconds: Β΅s β†’ us)
Console.WriteLine(GoDuration.ToAsciiString(microseconds));  // β†’ 1.1us

// ToCompactString (adaptive subsecond decimal precision)
Console.WriteLine(GoDuration.ToCompactString(almost10s));   // β†’ 9.99s
Console.WriteLine(GoDuration.ToCompactString(almost1m));    // β†’ 59.9s
Console.WriteLine(GoDuration.ToCompactString(almost1h));    // β†’ 59m59s
Console.WriteLine(GoDuration.ToCompactString(almost1d));    // β†’ 23h59m59s
Console.WriteLine(GoDuration.ToCompactString(almost1w));    // β†’ 167h59m59s

Why does ASCII-safety matter? The first use of this package was adding human-readable response times to HTTP headers for easier debugging. Everything was working great until a sub-millisecond response time appeared, and suddenly the middleware crashed.

Turns out the Β΅ in Β΅s was the problem (HTTP headers require ASCII-only). Fix: enable GoDurationFormatOptions.AsciiMicroseconds to emit us.

Isn't Go's canonical implementation compact enough? While Go’s format is concise, the .NET TimeSpan representation includes full tick precision (7 decimal places).

Remember, my primary use case is human-readable logs and metrics. For humans, this level of detail often adds unnecessary noise. Even multi-minute operations end up displaying sub-microsecond-level fractions.

I might care that something took 1.02ms versus 1.92ms, but probably not that it took 12m23.59192s instead of 12m23.59102s.

Exact seconds

The ExactSeconds API provides culture-invariant decimal seconds, at full tick precision (7 decimal places), and never formats in scientific notation.

using jaytwo.TimeExpression.Seconds;

var twoMinutes = TimeSpan.FromMinutes(2);

var oneTick = TimeSpan.FromTicks(1);
var almost1m = TimeSpan.FromMinutes(1) - oneTick;
var almost1h = TimeSpan.FromHours(1) - oneTick;
var almost1d = TimeSpan.FromDays(1) - oneTick;
var almost1w = TimeSpan.FromDays(7) - oneTick;

// never scientific notation
Console.WriteLine(oneTick.TotalSeconds);                 // β†’ 1E-7
Console.WriteLine(ExactSeconds.ToString(oneTick));       // β†’ 0.0000001

// trims leading and trailing zeroes
Console.WriteLine(ExactSeconds.ToString(TimeSpan.Zero)); // β†’ 0
Console.WriteLine(ExactSeconds.ToString(twoMinutes));    // β†’ 120

// full tick precision
Console.WriteLine(ExactSeconds.ToString(almost1m));      // β†’ 59.9999999
Console.WriteLine(ExactSeconds.ToString(almost1h));      // β†’ 3599.9999999
Console.WriteLine(ExactSeconds.ToString(almost1d));      // β†’ 86399.9999999
Console.WriteLine(ExactSeconds.ToString(almost1w));      // β†’ 604799.9999999

This seems entirely unnecessary. And for most people, it probably is. But even in the machine-friendly parts of logs, I found it frustrating to track and/or validate full-precision values like 2.7E-4 at a glance.

Exact milliseconds

using jaytwo.TimeExpression.Milliseconds;

var twoMinutes = TimeSpan.FromMinutes(2);

var oneTick = TimeSpan.FromTicks(1);
var almost1m = TimeSpan.FromMinutes(1) - oneTick;
var almost1h = TimeSpan.FromHours(1) - oneTick;
var almost1d = TimeSpan.FromDays(1) - oneTick;
var almost1w = TimeSpan.FromDays(7) - oneTick;

// never scientific notation
Console.WriteLine(oneTick.TotalMilliseconds);                 // β†’ 0.0001
Console.WriteLine(ExactMilliseconds.ToString(oneTick));       // β†’ 0.0001

// trims leading and trailing zeroes
Console.WriteLine(ExactMilliseconds.ToString(TimeSpan.Zero)); // β†’ 0
Console.WriteLine(ExactMilliseconds.ToString(twoMinutes));    // β†’ 120000

// full tick precision
Console.WriteLine(ExactMilliseconds.ToString(almost1m));      // β†’ 59999.9999
Console.WriteLine(ExactMilliseconds.ToString(almost1h));      // β†’ 3599999.9999
Console.WriteLine(ExactMilliseconds.ToString(almost1d));      // β†’ 86399999.9999
Console.WriteLine(ExactMilliseconds.ToString(almost1w));      // β†’ 604799999.9999

Why Exact Milliseconds? Many latency and UI timings are reasoned about in milliseconds, not seconds. While scientific notation is less of an issue with only 4 decimal places, but this gives the same benefits as the other API's in this package: tick precision, culture-invariant formatting, and predictable output.

🧩 API Summary

Type Description
GoDuration Static helper class exposing convenience methods: ToGoString(), ToAsciiString(), and ToCompactString().
GoDurationFormatter Core formatter implementing Go-style duration output with configurable GoDurationFormatOptions.
GoDurationFormatOptions Bit flags controlling output format.
ExactSeconds Static helper class exposing convenience method: ToString()
SecondsFormatter Core formatter implementing low-level seconds formatting.
ExactMilliseconds Static helper class exposing convenience method: ToString()
MillisecondsFormatter Core formatter implementing low-level milliseconds formatting.

Made with ❀️ by Jake β€” Licensed under the MIT License

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.6.2

    • No dependencies.
  • .NETStandard 2.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on jaytwo.TimeExpression:

Package Downloads
jaytwo.DistributedLocks

Package Description

jaytwo.AspNetCore.ResponseTime

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-beta-20251102202452 169 11/3/2025
0.1.0-beta-20251101211754 197 11/2/2025
0.1.0-beta-20251101163444 105 11/1/2025
0.1.0-beta-20251101160839 112 11/1/2025
0.1.0-beta-20251031003304 148 10/31/2025