# DateTime

> A date and time. It is a wall-clock value, not an instant on a timeline, so it is never shifted by anybody's timezone. Reading its parts and doing calendar arithmetic on it work identically in the browser and on the server; reading the current time is a server operation.

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

## Summary        {#summary}
`DateTime` is a date and a time of day — `2024-03-15T13:45:30` — held exactly, to 100 nanoseconds. You read its parts
(`d.Year`, `d.DayOfWeek`), do calendar arithmetic on it (`d.AddMonths(1)`), and construct or parse one
(`new DateTime(2024, 3, 15)`, `DateTime.Parse(s)`).

## Signature      {#signature}
```osy syntax
new DateTime(<int> year, <int> month, <int> day)
new DateTime(<int> year, <int> month, <int> day, <int> hour, <int> minute, <int> second)
DateTime.Parse(<string> s) -> DateTime

DateTime.MinValue -> DateTime         // the earliest representable value
DateTime.MaxValue -> DateTime         // the latest representable value

d.Year · d.Month · d.Day · d.Hour · d.Minute · d.Second · d.DayOfWeek · d.Date
d.AddDays(n) · d.AddMonths(n) · d.AddYears(n) · d.AddHours(n) · d.AddMinutes(n) · d.AddSeconds(n) · d.AddMilliseconds(n)
d.Millisecond · d.Microsecond · d.Nanosecond · d.TimeOfDay -> TimeSpan · d.AddMicroseconds(n)

DateTime.Compare(<DateTime> a, <DateTime> b) -> int     // also d.CompareTo(other)
DateTime.DaysInMonth(<int> year, <int> month) -> int
DateTime.IsLeapYear(<int> year) -> bool
DateTime.FromOADate(<double> oa) -> DateTime            // d.ToOADate() -> double
DateTime.FromFileTimeUtc(<long> fileTime) -> DateTime   // d.ToFileTimeUtc() -> long
DateTime.FromBinary(<long> data) -> DateTime
d.GetDateTimeFormats() · d.GetDateTimeFormats(<char> format) -> List<string>
d.GetHashCode() -> int · d.GetTypeCode() -> TypeCode
```

## Description    {#description}

### What are `DateTime.MinValue` and `MaxValue`? {#range-endpoints}
`DateTime.MinValue` and `DateTime.MaxValue` are the earliest and latest values a `DateTime` can hold. They are
ordinary values, so they compare and sort like any other date — which is what makes them useful as a starting point
for a running comparison (`var earliest = DateTime.MaxValue;` then keep the smaller of each candidate).

⚠ **They are not a "no date" marker.** A `DateTime?` says "no date" precisely and reads as `null`; a sentinel says
it by convention and every reader has to know the convention. Prefer the nullable type — a `MinValue` that leaks
into a UI renders as a real date in the year 1, and a `MinValue` that reaches a comparison silently sorts first.

### It is a wall-clock value, not an instant   {#wall-clock}

`2024-03-15T13:45:30` means *that reading on a clock face*. It is not "a moment in time as seen from a timezone", and
nothing in the platform will shift it by one. A date you store is the date you get back — the same one, in the same
digits, whether it is read on a server in Frankfurt or in a browser in São Paulo.

That is worth stating plainly because most date libraries do the opposite, and quietly.

### Calendar arithmetic clamps the day   {#clamping}

`AddMonths` and `AddYears` move along the **calendar**, and clamp the day to the target month rather than overflowing
it:

```osy syntax
new DateTime(2024, 1, 31).AddMonths(1)     // 2024-02-29  — a leap year
new DateTime(2025, 1, 31).AddMonths(1)     // 2025-02-28
new DateTime(2024, 2, 29).AddYears(1)      // 2025-02-28
```

None of those becomes March 2nd. If you want exactly thirty days later, say `AddDays(30)`.

`AddDays`, `AddHours`, `AddMinutes`, `AddSeconds` and `AddMilliseconds` take a fractional amount and truncate it to
whole ticks (100 nanoseconds) — never rounding it to a millisecond — so
`AddDays(0.5)` is exactly twelve hours.

### How do I read below a second, or ask about the calendar? {#fine-parts}

`d.Millisecond`, `d.Microsecond` and `d.Nanosecond` are the parts below a second, as in C#: the millisecond
(0–999), the microsecond within that millisecond (0–999), and the nanosecond within that microsecond — always a
multiple of 100, because a tick is 100 nanoseconds. `d.AddMicroseconds(n)` moves by microseconds. `d.TimeOfDay` is
the time since midnight as a `TimeSpan`.

`DateTime.Compare(a, b)` and `a.CompareTo(b)` answer `-1`, `0` or `1`, the way a sort comparer wants; for a plain
question, `a < b` reads better. `DateTime.DaysInMonth(year, month)` and `DateTime.IsLeapYear(year)` ask the
Gregorian calendar.

### Can I read a date another system wrote? {#interop}

Three encodings other software hands you, each with its pair:

| encoding | read it | write it |
|---|---|---|
| OLE Automation date — what Excel and older COM software store: days since 1899-12-30 as a `double` | `DateTime.FromOADate(oa)` | `d.ToOADate()` |
| Windows FILETIME — 100-nanosecond ticks since 1601-01-01 UTC, as a `long` | `DateTime.FromFileTimeUtc(ft)` | `d.ToFileTimeUtc()` |
| .NET's `DateTime.ToBinary()` serialisation, as a `long` | `DateTime.FromBinary(data)` | — |

`ToBinary`, `ToFileTime` and `ToLocalTime` are refused by name: they read the host's timezone or a `DateTimeKind`,
and a `DateTime` here has neither — it is a wall-clock value (above). The UTC forms are the honest ones.

`d.GetDateTimeFormats()` lists the date rendered in every standard format, and `d.GetDateTimeFormats('d')` in one —
under the invariant culture, so `'d'` gives `"03/15/2024"`. `d.GetHashCode()` is equal for equal dates and
`d.GetTypeCode()` is `TypeCode.DateTime`, as in C#.

### DayOfWeek is the `DayOfWeek` enum   {#day-of-week}

`d.DayOfWeek` is a `DayOfWeek`, as in C# — `DayOfWeek.Sunday` through `DayOfWeek.Saturday`, printing as its name.
`(int)d.DayOfWeek` is `0` for Sunday through `6` for Saturday.

### Reading the current time   {#now}

`DurableClock.Now`, `DurableClock.UtcNow` and `DurableClock.Today` give the current instant, and they run **on the client** — reading
"now" costs no round trip. That is safe here for a reason worth knowing: a `DateTime` is a UTC *instant*, not a
wall-clock reading, so the browser and the server name the same value (9am in Frankfurt *is* 5pm in Tokyo). Test
pinning still applies, and a durable flow that pauses and resumes still sees the instant it saw before, because the
engine resumes from a saved point rather than re-running the body from the top.

⚠ **`DateTime.Now` is local time, and local is the APP's zone** — `app.DefaultTimeZone`, UTC when none is declared,
never the host machine's. So in an app that declares no zone, `Now` and `UtcNow` are the same instant; in one that
declares Stockholm, `Now` is Stockholm's wall clock ([Current time (DateTime.UtcNow, DurableClock.Now)](https://osysharp.com/reference/function/current-time/)). Anybody else's wall clock is
`TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, zone)` ([Time zones — TimeZoneInfo](https://osysharp.com/reference/stdlib/zones/)). `DateTime.Parse` of text with an offset
(`"…+01:00"`, `"…Z"`) answers on the same local clock, as C#'s does — the instant in the app's zone, the same instant in
UTC when none is declared — and the machine's own zone never enters it.

Everything else about a date — its parts, its arithmetic, parsing and formatting it — runs wherever you are, with no
round trip.

## Examples       {#examples}
```osy title="a due date, and whether it has passed" test app=text-search
bool IsOverdue(DateTime due) {
  return due < DurableClock.UtcNow;
}

DateTime NextBillingDate(DateTime start) {
  return start.AddMonths(1);
}
// NextBillingDate(new DateTime(2024, 1, 31))  ->  2024-02-29
```

```osy title="the parts below a second, the calendar, and other systems' encodings, pinned" run app=types-datetime-members
[Test]
void Date_members_answer_what_csharp_answers() {
  var d = new DateTime(2024, 3, 15, 13, 45, 30);
  var fine = d.AddMicroseconds(1500);
  Assert.Equal(1, fine.Millisecond);
  Assert.Equal(500, fine.Microsecond);
  Assert.Equal(0, fine.Nanosecond);
  Assert.Equal(new TimeSpan(13, 45, 30), d.TimeOfDay);

  Assert.Equal(-1, DateTime.Compare(d, d.AddDays(1)));
  Assert.Equal(1, d.CompareTo(d.AddDays(-1)));
  Assert.Equal(29, DateTime.DaysInMonth(2024, 2));
  Assert.False(DateTime.IsLeapYear(2023));

  Assert.Equal(45366.57326388889, d.ToOADate());
  Assert.Equal(new DateTime(2024, 3, 15, 12, 0, 0), DateTime.FromOADate(45366.5));
  Assert.Equal(133549839300000000, d.ToFileTimeUtc());
  Assert.Equal(d, DateTime.FromFileTimeUtc(d.ToFileTimeUtc()));
  Assert.Equal(d, DateTime.FromBinary(d.Ticks));

  Assert.Equal("03/15/2024", d.GetDateTimeFormats('d')[0]);
  Assert.Equal(d.GetHashCode(), new DateTime(2024, 3, 15, 13, 45, 30).GetHashCode());
  Assert.Equal(TypeCode.DateTime, d.GetTypeCode());
}
```

## See also       {#see-also}
- [decimal](https://osysharp.com/reference/types/decimal/) — the other exact value type, and the same reasoning behind it
- [format specifiers](https://osysharp.com/reference/function/format-specifiers/) — rendering a date or a number to a string
- [Crossing the network](https://osysharp.com/reference/function/execution-side/) — why the clock is a server operation and the arithmetic is not
- [DateTimeOffset](https://osysharp.com/reference/types/datetimeoffset/) — the same instant plus the offset it was recorded at, when the local time is part of the fact
