# Date arithmetic — AddDays, AddMonths, AddYears, AddHours, AddMinutes, AddSeconds, AddMilliseconds

> Move a DateTime forward or back. AddDays/AddHours/AddMinutes/AddSeconds/AddMilliseconds take a fractional amount and are exact to the tick. AddMonths and AddYears are CALENDAR-aware and CLAMP the day: Jan 31 + 1 month is Feb 29 in a leap year and Feb 28 otherwise, never March 2nd. A negative amount goes backward. They run wherever the expression does — in the browser, on the server, and **inside a query the database executes**, where each lowers to a real SQL interval.

<!-- id: function-date-arithmetic · area: function · stability: stable · html: https://osysharp.com/reference/function/date-arithmetic/ -->

## Summary        {#summary}
These shift a `DateTime` by an amount. `AddDays`, `AddHours`, `AddMinutes`, `AddSeconds` and `AddMilliseconds`
take a **fractional** amount and are exact time arithmetic. `AddMonths` and `AddYears` are **calendar-aware**: they land on the same
day-of-month where it exists and **clamp** to the last day of the month where it does not. A negative amount
moves backward.

## Signature      {#signature}
```osy syntax
d.AddDays(<number> n)    -> DateTime     // n may be fractional (0.5 = 12 hours)
d.AddHours(<number> n)   -> DateTime
d.AddMinutes(<number> n) -> DateTime
d.AddSeconds(<number> n) -> DateTime     // DateTime only — not on DateOnly or TimeOnly
d.AddMilliseconds(<number> n) -> DateTime   // DateTime only
d.AddMonths(<int> n)     -> DateTime     // calendar month, day CLAMPED
d.AddYears(<int> n)      -> DateTime     // calendar year, Feb 29 CLAMPED
```

## Description    {#description}

### They work INSIDE a query — the database does the arithmetic   {#in-a-query}
`AddDays` and its siblings are not client-only helpers. Written inside a `Where`, each lowers to a real SQL
interval, so the comparison happens **in the database** and only the matching rows come back:

```osy syntax
// the whole due-list, computed by the database — one filtered read
live var due = Plant.Where(p => p.LastWateredAt.AddDays(p.WaterEveryDays) <= DateTime.UtcNow).ToList();

// a freshness window — the readings from the last ten minutes, also one filtered read
var recent = Reading.Where(r => r.TakenAt >= DateTime.UtcNow.AddSeconds(-600)).ToList();
```

⚠ **The alternative is a full table read.** `Plant.ToList()` followed by an in-memory `Where` fetches every row on
every page load and filters them in the browser — correct on ten rows, and a table scan on ten thousand. If the
predicate can be written over the entity, write it there: see [Where / Single / Count](https://osysharp.com/reference/query/where/).

### AddDays through AddMilliseconds are exact time arithmetic   {#exact-units}
They add a fixed length of time and roll components over: `AddHours(11)` on `13:45` crosses midnight into the
next day, `AddMinutes(90)` adds an hour and a half, `AddSeconds(-600)` goes back ten minutes. The amount may be
**fractional** — `AddDays(0.5)` is twelve hours, `AddMinutes(0.5)` is thirty seconds, `AddMilliseconds(0.25)` is a
quarter of a millisecond — and it may be **negative** to go backward.

A fractional amount becomes whole **ticks** (a tick is 100 nanoseconds) by truncating toward zero, exactly as .NET
does — it is never rounded to a millisecond. One consequence is worth knowing: `1.2` is not exactly representable as a
double, so `AddSeconds(1.2)` lands one tick short of 1.2 seconds. Where that tick matters, say the amount in whole
units of a smaller one — `AddMilliseconds(1200)` is exact.

### AddSeconds and AddMilliseconds belong to DateTime   {#datetime-only}
As in C#, they exist on a `DateTime` only. A `DateOnly` has no time of day to move by a second, and a `TimeOnly`
moves by `AddHours` or `AddMinutes` — so the compiler refuses the call on either, and its message shows the spelling
that works (`t.AddMinutes(n / 60.0)`, or `d.ToDateTime(new TimeOnly(0, 0)).AddSeconds(n)`).

### AddMonths / AddYears CLAMP the day — the rule that surprises people   {#clamping}
A calendar month is not a fixed number of days, so "one month after January 31st" has no exact answer.
`AddMonths` and `AddYears` resolve it by **clamping the day to the last valid day of the target month**:

| Call | Result | Why |
|---|---|---|
| `Jan 31 . AddMonths(1)` | `Feb 29` (2024) | Feb has no 31st; clamp to the last day — and 2024 is a leap year |
| `Jan 31 . AddMonths(13)` | `Feb 28` (2025) | thirteen months on, into a non-leap year |
| `Feb 29 . AddYears(1)` | `Feb 28` | the target year has no Feb 29 |

It never spills over into the next month — you will not get "March 2nd" from adding a month to January 31st.
The **time of day is preserved** across all of these.

The member syntax `d.AddMonths(1)` is the everyday spelling; the compiler knows these as `Date.AddDays`,
`Date.AddHours`, `Date.AddMinutes`, `Date.AddSeconds`, `Date.AddMilliseconds`, `Date.AddMonths` and `Date.AddYears`, and they can also be written in that
call form (`Date.AddMonths(d, 1)`).

Every one is a pure function of the value, so it runs **in the browser** with no round trip
([Crossing the network](https://osysharp.com/reference/function/execution-side/)). To add a duration instead of a fixed unit, add a [TimeSpan (durations)](https://osysharp.com/reference/types/timespan/).

## Examples       {#examples}
```osy title="a due date one month out, honouring month lengths" test app=date-arith
DateTime DueNextMonth(DateTime placed) {
  return placed.AddMonths(1);
}
```

```osy title="ten minutes ago, to the second" test app=date-arith
DateTime FreshSince() {
  return DateTime.UtcNow.AddSeconds(-600);
}
```

```osy title="fractions, negatives, and month-end clamping — pinned" run app=date-arith
[Test]
void Date_arithmetic() {
  var d = DateTime.New(2024, 3, 15, 13, 45, 30);

  // exact, fractional, and reversible
  Assert.Equal(16, d.AddDays(1).Day);
  Assert.Equal(1, d.AddDays(0.5).Hour);       // +12h → 01:45 next day
  Assert.Equal(16, d.AddHours(11).Day);       // crosses midnight
  Assert.Equal(0, d.AddHours(11).Hour);

  // seconds and milliseconds
  Assert.Equal(35, d.AddSeconds(-600).Minute);        // 13:45:30 → 13:35:30
  Assert.Equal(47, d.AddSeconds(90).Minute);          // → 13:47:00
  Assert.Equal(31, d.AddMilliseconds(1500).Second);   // → 13:45:31.5

  // AddMonths / AddYears clamp the day to the month end
  var jan31 = DateTime.New(2024, 1, 31);
  Assert.Equal(2, jan31.AddMonths(1).Month);
  Assert.Equal(29, jan31.AddMonths(1).Day);   // Feb 29 in a leap year, NOT March 2nd
  Assert.Equal(28, jan31.AddMonths(13).Day);  // Feb 28 in 2025

  var feb29 = DateTime.New(2024, 2, 29);
  Assert.Equal(28, feb29.AddYears(1).Day);    // 2025 has no Feb 29
}
```

## See also       {#see-also}
- [Reading a date — Year, Month, Day, Hour, Minute, Second, DayOfWeek, DayOfYear, Date](https://osysharp.com/reference/function/date-parts/) — reading the year/month/day back off the result
- [TimeSpan (durations)](https://osysharp.com/reference/types/timespan/) — adding a *duration* (`d + TimeSpan.FromHours(2)`) rather than a fixed unit
- [Current time (DateTime.UtcNow, DurableClock.Now)](https://osysharp.com/reference/function/current-time/) — the current instant to do arithmetic from
