# `MPP.Expires`
[🔗](https://github.com/ZenHive/mpp/blob/v0.10.0/lib/mpp/expires.ex#L1)

Expiration timestamp helpers for MPP challenges.

Duration helpers return ISO 8601 datetime strings offset from `DateTime.utc_now/0`.
`months/1` uses 30-day months (mppx-compatible). `assert!/1` validates that a
timestamp is present, well-formed, and not in the past.

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `assert!` | 2 | Asserts that `expires` is present, well-formed, and not in the past. | `expires: value`, `challenge_id: value` |
| `years` | 1 | Returns an ISO 8601 datetime string `n` years (365 days) from now. | `n: value` |
| `months` | 1 | Returns an ISO 8601 datetime string `n` months (30 days) from now. | `n: value` |
| `weeks` | 1 | Returns an ISO 8601 datetime string `n` weeks from now. | `n: value` |
| `days` | 1 | Returns an ISO 8601 datetime string `n` days from now. | `n: value` |
| `hours` | 1 | Returns an ISO 8601 datetime string `n` hours from now. | `n: value` |
| `minutes` | 1 | Returns an ISO 8601 datetime string `n` minutes from now. | `n: value` |
| `seconds` | 1 | Returns an ISO 8601 datetime string `n` seconds from now. | `n: value` |

# `assert!`

```elixir
@spec assert!(term()) :: :ok
```

Asserts that `expires` is present, well-formed, and not in the past.

## Parameters

  * `expires` - RFC 3339 expiration timestamp (value)
  * `challenge_id` - Optional challenge ID for error context (value)

## Returns

`:ok` when valid (`atom`)

## Errors

  * `:invalid_challenge`
  * `:payment_expired`

```elixir
# descripex:contract
%{
  params: %{
    expires: %{description: "RFC 3339 expiration timestamp", kind: :value},
    challenge_id: %{
      description: "Optional challenge ID for error context",
      kind: :value
    }
  },
  errors: [:invalid_challenge, :payment_expired],
  returns: %{type: :atom, description: "`:ok` when valid"}
}
```

# `assert!`

```elixir
@spec assert!(term(), String.t() | nil) :: :ok
```

Asserts that `expires` is present, well-formed, and not in the past.

When `challenge_id` is provided, it is included in invalid-challenge errors.

# `days`

```elixir
@spec days(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` days from now.

## Parameters

  * `n` - Number of days (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of days", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

# `hours`

```elixir
@spec hours(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` hours from now.

## Parameters

  * `n` - Number of hours (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of hours", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

# `minutes`

```elixir
@spec minutes(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` minutes from now.

## Parameters

  * `n` - Number of minutes (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of minutes", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

# `months`

```elixir
@spec months(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` months (30 days) from now.

## Parameters

  * `n` - Number of 30-day months (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of 30-day months", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

# `seconds`

```elixir
@spec seconds(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` seconds from now.

## Parameters

  * `n` - Number of seconds (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of seconds", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

# `weeks`

```elixir
@spec weeks(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` weeks from now.

## Parameters

  * `n` - Number of weeks (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of weeks", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

# `years`

```elixir
@spec years(number()) :: String.t()
```

Returns an ISO 8601 datetime string `n` years (365 days) from now.

## Parameters

  * `n` - Number of 365-day years (value)

## Returns

RFC 3339 expiration timestamp (`string`)

```elixir
# descripex:contract
%{
  params: %{n: %{description: "Number of 365-day years", kind: :value}},
  returns: %{type: :string, description: "RFC 3339 expiration timestamp"}
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
