# Time

> Durations, windows, `now`, and the age of a datetime attribute.

Source: https://www.sendlang.com/docs/sendql/time
Section: SendQL

Time shows up in SendQL in three places, and they are easy to keep apart once you
have seen all three side by side.

**Syntax form, not a complete program:**

```sendql
now - attr.signup_date > 7d          // age:     how long ago was this attribute?
count(open within 30d) >= 3          // window:  bound an event source in time
last(activity.login) < now - 14d     // recency: when did this last happen?
```

## Durations

A duration is an amount and a unit suffix, with no space between them:

**Syntax form, not a complete program:**

```sendql
30d    6mo    12h    2w    1y    90s
```

The units are `s`, `m`, `h`, `d`, `w`, `mo`, `y`. There is one form and only one
form — `30 days` is not a duration, `30 d` is not a duration, and `-1d` is not a
duration. Durations are unsigned; direction comes from the operator around them.

The unit you must be careful with is `m`, which is **minutes**. Months are `mo`.

**Syntax form, not a complete program:**

```sendql
5m     // five minutes
5mo    // five months
```

Months and years are **nominal**: 30 days and 365 days exactly. They are not
calendar-aware, and this is deliberate. A calendar-exact month would make a
literal's meaning depend on when it happened to be evaluated, which would make it
impossible to check a window statically or to fold a schedule at compile time.
`6mo` means 180 days, on every day of the year.

The full table is on the [durations reference](/docs/reference/durations).

## `now`

`now` is the current instant, and it is only useful in two shapes:

**Syntax form, not a complete program:**

```sendql
now              // the instant itself
now - 14d        // an instant, fourteen days ago
```

You can subtract a duration from `now`, and that is the whole of the arithmetic
in SendQL. You cannot add to it, multiply it, or subtract two attributes from
each other. A date literal works anywhere `now` does:

```sendql
last(open) > 2026-01-01
```

## Windows

A window bounds an event source. It comes last inside the parentheses, after any
`where` clause.

**`within <duration>`** — relative to now, looking backwards:

```sendql
count(click within 7d) >= 1
```

**`between <time> and <time>`** — an absolute or relative span:

```sendql
count(open between 2026-01-01 and 2026-02-01) > 0
```

```sendql
count(open between now - 30d and now - 7d) >= 2
```

The second one is the interesting shape: "opened at least twice, between a month
ago and a week ago" — a question about a slice of the past that `within` cannot
express.

Write the bounds backwards and you get an error rather than a query that matches
nobody:

**Rejected — the parser refuses this:**

```sendql
count(open between 2026-02-01 and 2026-01-01) > 0
```

Omit the window and the source means *ever*.

## Age

`now - attr.<datetime>` is the age of a datetime attribute. It produces a
duration, which you compare against a duration:

```sendql
now - attr.signup_date > 7d
```

```sendql
now - attr.trial_ends_at between 3d and 14d
```

Read `now - attr.signup_date > 7d` as "signed up more than seven days ago".
Bigger age means longer ago, which is the intuitive direction — but note that it
is the *opposite* direction from a recency term, where `<` means "longer ago".
That is not an inconsistency: an age is a duration and gets bigger with time,
while `last(...)` is an instant and gets *smaller* the further back it was.

If you only remember one thing:

| Shape | `>` means | `<` means |
|---|---|---|
| `now - attr.x` (a duration) | longer ago | more recently |
| `last(source)` (an instant) | more recently | longer ago |

The attribute must be a `datetime`. An age term on a string is an error, and the
`between` bounds may not be reversed.

## Age is absence-sensitive

Every age term silently excludes contacts where the attribute is unknown, and
SendQL will warn you about it every time:

```sendql
now - attr.signup_date > 7d
```

> `warning: predicate on 'attr.signup_date' excludes contacts where it is unknown`

If that is what you wanted, silence it by saying so:

```sendql
has attr.signup_date and now - attr.signup_date > 7d
```

If it was not, include the absent case explicitly:

```sendql
now - attr.signup_date > 7d or attr.signup_date is unknown
```

See [the absence contract](/docs/sendql/absence) for why this is a warning rather
than a default.
