# TimeSpan, DateOnly, TimeOnly

> A duration, a bare date, and a bare time of day. Subtracting two DateTimes gives a TimeSpan; adding one back gives a DateTime. All three are exact, and all three work identically in the browser and on the server.

<!-- id: types-duration-and-parts · area: types · stability: stable · html: https://osysharp.com/reference/types/duration-and-parts/ -->

## Summary        {#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      {#signature}
```osy syntax
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    {#description}

### Totals and parts are different things   {#totals-vs-parts}

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   {#negative}

`TimeSpan.FromHours(-2)` has `Hours` of `-2` — not `22` — and `Minutes` of `0`. Every component carries the sign.

### Durations are exact   {#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   {#dateonly-timeonly}

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       {#examples}
```osy title="how overdue is it?" test app=durations
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";
}
```

```osy title="a bare date and a bare time" test app=durations
entity Appointment {
  DateOnly Day;
  TimeOnly StartsAt;
}

bool IsMorning(Appointment a) {
  return a.StartsAt.Hour < 12;
}
```

## See also       {#see-also}
- [DateTime](https://osysharp.com/reference/types/datetime/) — the date-and-time type these come from
- [execution side](https://osysharp.com/reference/function/execution-side/) — why all of this runs in the browser too
