Proposal: TimeSpan
literals
#7982
Replies: 9 comments 49 replies
-
Doesn't feel really necessary given you could always write static helper methods that are as terse as you desire and use them throughout your project. var ts = secs(5); You could make them function more like literal suffixes via extension methods: var ts = 5.sec(); And, with extension everything, that could be further reduced to property syntax: var ts = 5.sec; I expect the "suffix namespacing" component of your proposal would run into parsing issues with ternary conditional expressions. |
Beta Was this translation helpful? Give feedback.
-
C# arguably continues evolving towards a form that favors more compact representations of logic. New literal suffixes could be a way to move this theme forward even further. However, rather than adding suffixes for To borrow from other languages: C++ 11 added support for user defined literals via operator overloading. Any suffix is then represented via user code that transforms a literal of some type into another type when the suffix in question is given, e.g. to represent the concept of seconds. I think this could also be an interesting approach for C#. However, due to differences to C++ the feature would have to be designed in a way so developers can resolve suffix clashes when a specific literal type is bound to the same suffix name multiple times. |
Beta Was this translation helpful? Give feedback.
-
Why is |
Beta Was this translation helpful? Give feedback.
-
Related: |
Beta Was this translation helpful? Give feedback.
-
Why not to use well known specifications from the ISO8601 (also used in XML world) with prefix Examples:
References: |
Beta Was this translation helpful? Give feedback.
-
I'm not opposed to idea but this seems to be such a can of worms without user define literals so I imagine the syntax needs to be something that wouldn't block their ability to evolve the language, allow the BCL to take advantage over this and still allows users to customize the behaviour to their specific cases.
I missed @HaloFour's comment above I agree that the upcoming extension feature should cover this for the most part where you could do something like |
Beta Was this translation helpful? Give feedback.
-
To every rule there is an exception and if you organize your extensions well like |
Beta Was this translation helpful? Give feedback.
-
Would be great if this also would allow it to be used as default parameters like this: public void M(TimeSpan t = 100ms)
{
} |
Beta Was this translation helpful? Give feedback.
-
I think it should be like this. Following the existing approach, we now have two ways to format numbers: binary and hexadecimal. By extending this approach, we don't need to invent anything new, which adds more mental burden to the already rich and even cluttered features of C #; The following is the existing number format, 1:0b010101:010101; 2: 0x3a8f07; On this basis, it is entirely possible to develop a solution for TimeSpan. Of course, according to me, all numerical suffixes should also be included, as this is not convenient in itself. Only int is the default, and there are options for suffixes such as f/d/l, short/byte/half/decimal? Some seem to be missing, which brings great trouble and inconvenience to use. It is recommended to add optional suffixes to all numbers as soon as possible |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Intro: ubiquitous and rock solid
There's a common value type that has become ubiquitous when working with anything from database calls, network calls, rest/graphql apis, caches, delays, timeouts and so on:
TimeSpan
.This type is used in a way or another everywhere in the .net ecosystem, both internally in the runtime/BCL and externally in almost any library.
Not only it is ubiquitous, it also stood the test of time without any reasonable change.
With
DateTime
there has been historically a need for a better representation, so we saw the rise ofDateTimeOffset
and libraries like Noda Time with types likeLocalDateTime
,OffsetDateTime
andZonedDateTime
.With
TimeSpan
instead, that is not the case: apart from some minor extensions and internal optimizations (which would still be possible anyway), nothing changed.Nitpicking corner: yes, in Noda Time there's the
Duration
type, but that is not as needed as theDateTime
alternatives and it's "just" a biggerTimeSpan
, allowing greater slices of time.Problem
So, the issue I'm highlighting here is that initializing a
TimeSpan
takes longer than probably needed in terms of characters that need to be typed, and later read.Some examples:
As can be seen, a big chunk of the space on each line is taken by the
TimeSpan
s initialization: considering again how ubiquitousTimeSpan
s have become, each time we write or read them there's somewhat of a waste, and this seems like a good opportunity for a potential improvement.Considerations about time
Any discussion about time should begin by referring to the common falsehoods involved: local vs UTC, dates in the past vs in the future, and in general timezones are a notoriously complex subject with a lot of details to get right and traps difficult to avoid.
In this case though we are talking about timespans, and in this regard they are an exception since they are "pure".
As an example "42 seconds are 42 seconds are 42 seconds", so to speak, and unless we talk about more complex units like a week or a month (which should be probably avoided), there are no issues involved, and each value cannot be misinterpreted.
Proposal
This proposal is about adding some of the common units as suffixes usable in the literal syntax.
For how specifically, please see the 2 proposals below.
Proposal 1: normal suffixes
The first proposal is about adding the common units, specifically:
us
(microseconds)ms
(milliseconds)s
(seconds)min
(minutes)h
(hours)I explicitely excluded
d
fordays
, even though in theTimeSpan
type there is theFromDays(...)
static method, because I'm thinking about things like daylight savings etc, which may confuse some cases of what "a full day" means since a "full day" sometimes may be23h
long or25h
long, on top of the canonical24h
.Having said that, theoretically the same can be said about "a full hour" in the case of a leap second, so by explicitely defining these units as their canonical version we can simply avoid all this problems and use even days without any issue (and, btw, this is already what happens when using the
FromDays(...)
method).Another missing unit here is
nanoseconds
(ns
): this can be easily added, theoretically, but practically theTimeSpan
works with ticks which have a resolution of100ns
: theTimeSpan
type in fact - at least in recent versions of .NET - has aNanoseconds
property (and aTotalNanoseconds()
method) but they are get-only, since setting a value that would be automatically rounded or truncated is something risky and counter-intuitive.Because of this I would suggest not adding
ns
support, at least for now.The same abbreviations are already used by the known chrono API, so another point to consider.
The previous example would then become:
Something to note is that although we can theoretically pick either
s
orsec
for seconds (although in the SI usess
so I would stick with that), we cannot use eitherm
ormin
for minutes, sincem
is already used for theDecimal
type.This problem can be summarized as the known "global namespace pollution" already seen multiple times in different areas, the most famous of which is probably the one in the JS world, where multiple scripts injected in a page may generate collisions in the global namespace by declaring different variables with the same name at the global level.
The problem here is similar, although not the same, since the .NET team is the one who controls what literal suffixes (or prefixes, like with the binary notation) are added: having said that, the idea of not risking future collisions and allowing for more extensibility is something that may be useful to at least talk about, even though it may not be used.
Proposal 2 tries to branistorm this.
Proposal 2: suffix family (a.k.a. namespacing)
Instead of adding new suffixes directly in the global literal suffix namespace, an idea may be to add the concept of a literal suffix namespace or family, where
t
may represent the one related to "time": in this way it would be possible to avoid collisions by virtue of using a grouping prefix.A possible syntax may be "{family}:{unit}", to get something like:
t:us
(microseconds)t:ms
(milliseconds)t:s
(seconds)t:min
(minutes)t:h
(hours)With this approach the only suffix "consumed" in the global suffix namespace would be
t
, leaving the room for others to be added in the future without creating collisions.This approach is more complex than the previous one (and maybe overkill), but is something to at least think about, imho.
The previous example would then become:
Time in general and the
TimeSpan
type in particular are so core that we may even bite the bullet, consume some entries in the global namespace, and go without the family prefix: not sure about that, leaving this to the discussion that may emerge from this.Parsing/Formatting
In case this will be done, support for parsing/formatting should also be added to "close the loop".
I'm thinking about something like this:
This would also be usable in config files (typically JSON, like appsettings) so that the intent would be clearer, imho.
On the formatting side, maybe some Custom Format Strings can also be created to express "total units", for example with
"ts"
(total seconds),"tus"
(total microseconds),"tmin"
(total minutes) and so on.With this, a timespan of "1 minute and 50 seconds" can be formatted with a
"ts"
format as "110s".On top of this, a new Standard Format String may be created, like
"wu"
(whole units), that would automatically use the biggest possible whole unit.An example can be a timespan of "10 seconds" which can be formatted as
"10s"
, whereas one of "120 seconds" would be formatted as"2min"
.Still not sure about these to be honest, since more brainstorming should be put into all the details and corner cases, like what should happen when the total value is fractional? Probably there's some more reasoning to be made about fractional values and formats, but this is a starting point.
Alternatives
The usual alternative suggested is to just use some extension methods on
int
s (like42.Seconds()
), either from already existing libraries (like Humanizer, probably the most famous) or by rolling our own.The main problem with this approach is that working with
TimeSpan
is so ubiquitous that having to do either of those things in each and every project seems really overkill.Being forced to use a library for such a small and core need also creates a dependency on a 3rd party project which, as seen in the Humanizer case, can then become somewhat abandoned and not a solid solution (meanwhile the Humanizer project seems to be getting back on track, to be fair).
On top of this, creating such wide ext methods on core types like
int
has always been discouraged by the .NET team, if I remember correctly.Also, such approaches do not allow for a wider usage (see parsing/formatting above), and would not be usable in tutorials, samples, blog posts, etc and that would reduce the advantages of a native approach.
Finally, all calls would then be an additional method call, so by doing
42.Seconds()
the calls would actually be42.Seconds()
->TimeSpan.FromSeconds(42)
->new TimeSpan(...)
etc.Possible optimization?
I didn't want to create a separate discussion for this (yet), but while we are here: since a
TimeSpan
in the end is stored in aticks
field of typelong
, for which there's also a directly accessible ctornew TimeSpan(long)
, and since any possible calculation of "seconds to ticks" or "minutes tu ticks" would be just a "calculatable const" wouldn't it be possible to generate at compile time a lowered C# which is directly that?What I mean is that any call with a constant input value like
var foo = TimeSpan.FromMilliseconds(123)
could be automatically lowered tovar foo = new TimeSpan(1230000)
, I think, which is what is being done internally inside theFromMilliseconds(...)
method anyway.Looking at the code I see that in some of the ctors and static methods there are some extra checks for min/max values, but the same is not there in the basic
new TimeSpan(ticks)
one, which seems strange.Am I missing something?
In case this makes sense I can open a separate discussion or issue for that.
🔔 Update 2024-03-04
A lot of comments pointed out about a more generic "user defined literals" feature: although I feel it can be something useful to think about, I also have mixed feelings about the practicality of it all things considered. I think it's something that should be discussed, but not necessarily implemented right away since that would take most probably years to get to something, if at all.
A nice balance would be to pick a
TimeSpan
syntax that would be compatible with an hypothetical extension in the future, and doesn't clash with that is alrady there (my Proposal 1 I think follows these principles).Because of this and more, I'd like to keep this proposal only for the very specific case of the
TimeSpan
type, because I think it is "different", it has a special place in .net as a whole and therefore may deserves a special treatment.To summarize in a single place:
TimeSpan.FromMilliseconds(42)
vs42ms
Beta Was this translation helpful? Give feedback.
All reactions