Description
Background and motivation
Custom TimeSpan format strings have limitations which limit their practicality. Notably, it is currently impossible to express any of the Standard TimeSpan format strings as a Custom TimeSpan format string, the biggest limitation being that there is currently no way to express the negative sign for negative TimeSpans, a fact that is explicitly called out on the docs page. The main shortcoming is that TimeSpan custom format strings do not support the section separator, unlike custom numeric format strings.
This limitation makes it difficult to build variations of the standard TimeSpan format strings. Instead, the user must resort to custom TimeSpan formatting code, which is cumbersome and incompatible with APIs that only accept format strings. For example, data annotations such as the DisplayFormatAttribute.
Additionally, there is no way to express the TimeSpan.Total*
properties of the TimeSpan
, such as TimeSpan.TotalHours
, TimeSpan.TotalMinutes
, or TimeSpan.TotalSeconds
. This limits the flexibility of TimeSpan formatting since users do not always want the number of days to be shown (instead preferring total hours), or for simple use cases with small TimeSpan values, total seconds are often desirable.
These limitations are also motivating other issues such as #88062 which suggests a new standard format specifier to workaround an issue that could be overcome with improved custom format string support.
API Proposal
I suggest adding additional custom format specifiers:
;
: The section separator, to allow defining sections with separate format strings for positive, negative, and zero TimeSpans. This would allow the user to easily dictate the behaviour of the positive and negative sign, and bring custom TimeSpan formats into parity with Custom numeric format strings.H
: The number of whole hours in the time interval. This differs from 'h' in that it represents the total number of hours and can therefore be > 24. It is equivalent to the absolute integer floor ofTimeSpan.TotalHours
.M
: The number of whole minutes in the time interval. This differs from 'm' in that it represents the total number of minutes and can therefore be > 60. It is equivalent to the absolute integer floor ofTimeSpan.TotalMinutes
.S
: The number of whole seconds in the time interval. This differs from 's' in that it represents the total number of seconds and can therefore be > 60. It is equivalent to the absolute integer floor ofTimeSpan.TotalSeconds
.
Additionally, leading zero variations of the total hours, total minutes, and total seconds specifiers should be available:
HH
,HHH
,HHHH
, ... -> 00, 000, 0000, ...MM
,MMM
,MMMM
, ... -> 00, 000, 0000, ...SS
,SSS
,SSSS
, ... -> 00, 000, 0000, ...
API Usage
var exampleNegative = TimeSpan.FromHours(-27.12345);
exampleNegative.ToString("+H:mm:ss.fff;-H:mm:ss.fff;H:mm:ss.fff"); // -27:07:24.419
exampleNegative.ToString("+M:ss.fff;-M:ss.fff;M:ss.fff"); // -1627:22.680
exampleNegative.ToString("+S.fff;-S.fff;S.fff"); // -97644.419
var examplePositive = TimeSpan.FromHours(27.12345);
examplePositive.ToString("+H:mm:ss.fff;-H:mm:ss.fff;H:mm:ss.fff"); // +27:07:24.419
examplePositive.ToString("+M:ss.fff;-M:ss.fff;M:ss.fff"); // +1627:22.680
examplePositive.ToString("+S.fff;-S.fff;S.fff"); // +97644.419
var exampleZero = TimeSpan.Zero;
exampleZero.ToString("+H:mm:ss.fff;-H:mm:ss.fff;H:mm:ss.fff"); // 0:00:00.000
exampleZero.ToString("+M:ss.fff;-M:ss.fff;M:ss.fff"); // 0:00.000
exampleZero.ToString("+S.fff;-S.fff;S.fff"); // 0.000
Alternative Designs
A dedicated format specifier for the TimeSpan
sign could be used, instead of the section separator. However, the section separator has the following advantages:
- Consistency with the existing behaviour in custom numeric format strings.
- Flexibility in prepending a '+' character for positive values.
- Flexibility in formatting of the zero condition.
Additional format specifiers could be added for TimeSpan.TotalMilliseconds
, TimeSpan.TotalMicroseconds
, and TimeSpan.Nanoseconds
:
-
A format specifier for
TimeSpan.TotalMilliseconds
has some obvious use cases, however it is unclear to me which letter should be used to represent it, since both 'f' and 'F' are already taken. -
The usefulness of format specifiers for
TimeSpan.TotalMicroseconds
andTimeSpan.Nanoseconds
is less clear to me, so I have omitted them.
Risks
Changing the way that custom format strings are interpreted for TimeSpan
is potentially breaking, however I believe that any un-escaped/unquoted use of ;
, H
, M
, S
in a TimeSpan
format string leads to the "Input string was not in a correct format." FormatException
, so the impact should be minimal.