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

> Move a DateTime forward or back. AddDays/AddHours/AddMinutes take a fractional amount and are exact. 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` and `AddMinutes` 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.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();
```

⚠ **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 / AddHours / AddMinutes 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. The amount may be **fractional** — `AddDays(0.5)` is
twelve hours, `AddMinutes(0.5)` is thirty seconds — and it may be **negative** to go backward.

### 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.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
([execution side](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="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);

  // 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, 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
