Summary#
TimeSpan is a duration — an amount of time, with no particular start. DateOnly is a date with no time of
day (a birthday, a due date). TimeOnly is a time of day with no date (an opening hour).
Signature#
due - now -> TimeSpan // subtracting two DateTimes
start + TimeSpan.FromHours(2) -> DateTime // adding a duration back
TimeSpan.FromDays(n) · FromHours(n) · FromMinutes(n) · FromSeconds(n) · FromMilliseconds(n)
new TimeSpan(hours, minutes, seconds) · new TimeSpan(days, hours, minutes, seconds)
ts.TotalHours · ts.Hours (they are not the same — see below)
new DateOnly(2024, 3, 15) · DateOnly.Parse(s) · DateOnly.FromDateTime(d)
new TimeOnly(13, 45) · TimeOnly.Parse(s) · TimeOnly.FromDateTime(d)Description#
Totals and parts are different things#
This is the one that trips people up. For a span of one day, two hours and three minutes:
| Value | What it is | |
|---|---|---|
ts.Days | 1 | the days part |
ts.Hours | 2 | the hours part — never more than 23 |
ts.TotalHours | 26.05 | the whole span, expressed in hours |
Hours is a component of the written-out duration; TotalHours is the duration itself, converted. If you want "how
long was this, in hours", you want TotalHours.
A negative duration is negative all the way down#
TimeSpan.FromHours(-2) has Hours of -2 — not 22 — and Minutes of 0. Every component carries the sign.
Durations are exact#
A TimeSpan holds exact ticks, so accumulating durations never drifts. TimeSpan.FromSeconds(3.5) is three and a
half seconds precisely.
DateOnly and TimeOnly are not DateTimes#
Use DateOnly when a time of day would be meaningless — a birthday is a date, not an instant, and giving it a time
invites a timezone to shift it. DateOnly.FromDateTime(d) takes the date part of a DateTime; TimeOnly.FromDateTime(d)
takes the time part.
You can still read the parts off either one directly: birthday.Year, opensAt.Hour.
Examples#
string OverdueLabel(DateTime due, DateTime now) {
var late = now - due; // a TimeSpan
if (late.TotalHours < 24) {
return $"{late.TotalHours:F1} hours late";
}
return $"{late.Days} days late";
}entity Appointment {
DateOnly Day;
TimeOnly StartsAt;
}
bool IsMorning(Appointment a) {
return a.StartsAt.Hour < 12;
}See also#
- DateTime — the date-and-time type these come from
- execution side — why all of this runs in the browser too