> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Work with dates

> Calculate a driver age, a policy term and a look-back window from date inputs, and see how month ends and leap days behave.

[Install Relativities](https://docs.canary.effectiveai.app/relativities/setup.md) before running the synthetic examples.

A `Date` is a calendar day. Date functions count the days, months or years between two dates and shift a date by a whole count:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
driver_age: Num = diff_years(self.effective_date, self.date_of_birth),
```

The example rates a personal auto policy from the synthetic carrier Example Mutual.
Its manual rates on driver age, the policy term, incidents in the last three years, months insured and vehicle age.

## Rate a policy from its dates

Save the rater as `dates/rater.rsl`:

> [accordion] Complete example: dates/rater.rsl
>
> ```rsl dates/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Personal Auto",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Types = {
>     Term = {
>         one_month,
>         six_month,
>         annual
>     }
> }
>
> Constants = {
>     annual_premium [cite: manual:p2] = 900,
>     revised_annual_premium [cite: manual:p2] = 945,
>     young_driver_factor [cite: manual:p3] = 1.35,
>     incident_load [cite: manual:p4] = 0.15,
>     persistency_factor [cite: manual:p5] = 0.95,
>     vehicle_age_credit [cite: manual:p6] = 0.02
> }
>
> Records = {
>     Policy = {
>         inputs {
>             effective_date: Date where self in [2026-01-01, 2028-12-31],
>             term: Term,
>             date_of_birth: Date where self in [1926-01-01, 2012-12-31],
>             insured_since: Date where self in [1950-01-01, 2028-12-31],
>             model_year: Num {precision: int} where self in [1990, 2029],
>             incidents: List(Incident)
>                 where len(self) in [0, 10] [code: "INCIDENT-COUNT", message: "A policy lists at most ten incidents.", cite: manual:p4]
>         },
>         step policy_term [cite: manual:p2] {
>             expiration_date: Date =
>                 match self.term {
>                     @Term.one_month => add_months(self.effective_date, 1),
>                     @Term.six_month => add_months(self.effective_date, 6),
>                     @Term.annual => add_years(self.effective_date, 1)
>                 },
>             term_days: Num = diff_days(self.expiration_date, self.effective_date),
>             base_premium: Premium =
>                 if self.effective_date >= 2026-07-01 then revised_annual_premium else annual_premium
>         },
>         step driver [cite: manual:p3] {
>             driver_age: Num = diff_years(self.effective_date, self.date_of_birth),
>             age_factor: Factor = if self.driver_age < 25 then young_driver_factor else 1
>         },
>         step record [cite: manual:p4] {
>             look_back_start: Date = add_years(self.effective_date, -3),
>             chargeable_incidents: Num = count(self.incidents as i, i.occurred >= self.look_back_start),
>             incident_factor: Factor = 1 + incident_load * self.chargeable_incidents
>         },
>         step tenure [cite: manual:p5] {
>             months_insured: Num = diff_months(self.effective_date, self.insured_since),
>             persistency: Factor = if self.months_insured >= 36 then persistency_factor else 1
>         },
>         step vehicle [cite: manual:p6] {
>             rating_year: Num =
>                 if month(self.effective_date) >= 10 then year(self.effective_date) + 1 else year(self.effective_date),
>             vehicle_age: Num = min(max(self.rating_year - self.model_year, 0), 10),
>             vehicle_factor: Factor = 1 - vehicle_age_credit * self.vehicle_age
>         },
>         step premium [cite: manual:p7] {
>             premium: Premium [cite: manual:p7] =
>                 round(self.base_premium * self.age_factor * self.incident_factor * self.persistency * self.vehicle_factor * self.term_days / 365, 2, half_up)
>         }
>     },
>     Incident = {
>         inputs {
>             occurred: Date where self in [2000-01-01, 2028-12-31]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

Send each date as a `YYYY-MM-DD` string:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
from relativities import Rater

rater = Rater.build("dates/rater.rsl")
policy = {
    "effective_date": "2026-08-15",
    "term": "annual",
    "date_of_birth": "2003-09-01",
    "insured_since": "2021-03-20",
    "model_year": 2021,
    "incidents": [{"occurred": "2024-05-02"}, {"occurred": "2022-11-30"}],
}
print(rater.explain(policy).to_string())
#            kind                   name       value
# 0         input         effective_date  2026-08-15
# 1         input                   term      annual
# 2         input          date_of_birth  2003-09-01
# 3         input          insured_since  2021-03-20
# 4         input             model_year        2021
# 5         input  incidents[0].occurred  2024-05-02
# 6         input  incidents[1].occurred  2022-11-30
# 7   calculation        expiration_date  2027-08-15
# 8   calculation              term_days         365
# 9   calculation           base_premium         945
# 10  calculation             driver_age          22
# 11  calculation             age_factor        1.35
# 12  calculation        look_back_start  2023-08-15
# 13  calculation   chargeable_incidents           1
# 14  calculation        incident_factor        1.15
# 15  calculation         months_insured          65
# 16  calculation            persistency        0.95
# 17  calculation            rating_year        2026
# 18  calculation            vehicle_age           5
# 19  calculation         vehicle_factor         0.9
# 20  calculation                premium     1254.38
```

The driver is 22 on the effective date, and one incident falls inside the three-year window.
The annual premium is `945 × 1.35 × 1.15 × 0.95 × 0.9 = 1254.38`.

## Declare a date input

State the earliest and latest date the input accepts:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
effective_date: Date where self in [2026-01-01, 2028-12-31],
```

* Both ends belong to the range.
* Write `_` for an open end, as in `[2026-01-01, _]`.
* A request sends the date as a string, such as `"2026-08-15"`. For a Python `date`, send `date.isoformat()`.

The rater refuses a date outside the range, and a string that names a day the calendar lacks:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
requests = [{**policy, "effective_date": "2029-01-01"}, {**policy, "effective_date": "2026-02-30"}]
print(rater.rate(requests).to_pandas()[["status", "reason_code", "reason"]].to_string())
#            status  reason_code                                                               reason
# 0  invalid_inputs  INPUT-RANGE  effective_date: effective_date is above declared maximum 2028-12-31
# 1  invalid_inputs   INPUT-TYPE           effective_date: effective_date must be a date (YYYY-MM-DD)
```

[Refusals](https://docs.canary.effectiveai.app/rsl/reference/refusals.md) lists each reason code.

## Compare with a date literal

Write a date in a calculation as `YYYY-MM-DD`, with the quotes left out:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
base_premium: Premium =
    if self.effective_date >= 2026-07-01 then revised_annual_premium else annual_premium
```

Dates compare with `=`, `<`, `<=`, `>` and `>=`. A policy effective on 1 July 2026 or later takes the revised premium.

A literal names a real calendar day. The check reports a literal such as `2026-06-31` with
[`E-DATE`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/names#e-date).

## Calculate an age

`diff_years(later, earlier)` counts full years, as an age in years is counted:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
driver_age: Num = diff_years(self.effective_date, self.date_of_birth),
age_factor: Factor = if self.driver_age < 25 then young_driver_factor else 1
```

The driver born on 2003-09-01 is 22 on 2026-08-15 and turns 23 on 2026-09-01.

## Calculate the term and the days in force

`add_months` and `add_years` shift a date by a whole count. `diff_days(later, earlier)` counts the days between two dates:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
expiration_date: Date =
    match self.term {
        @Term.one_month => add_months(self.effective_date, 1),
        @Term.six_month => add_months(self.effective_date, 6),
        @Term.annual => add_years(self.effective_date, 1)
    },
term_days: Num = diff_days(self.expiration_date, self.effective_date),
```

The `premium` calculation multiplies the annual premium by `term_days / 365`.

* Write the count as a whole number in the source, such as `6` or `-3`.
* For a count that depends on an input, write one arm for each filed count, as `expiration_date` does.
* `add_days(date, n)` shifts by days in the same way.

## Count the months between two dates

`diff_months(later, earlier)` counts calendar months and ignores the day of the month:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
months_insured: Num = diff_months(self.effective_date, self.insured_since),
persistency: Factor = if self.months_insured >= 36 then persistency_factor else 1
```

From 2023-02-28, the count is 35 on the last day of January 2026 and 36 one day later:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
for effective_date in ["2026-01-31", "2026-02-01"]:
    values = rater.explain({**policy, "insured_since": "2023-02-28", "effective_date": effective_date})
    values = values.set_index("name")["value"]
    print(effective_date, values["months_insured"], values["persistency"])
# 2026-01-31 35 1
# 2026-02-01 36 0.95
```

For a rule that needs 36 full months, compare two dates:
`self.insured_since <= add_months(self.effective_date, -36)`.

## Count incidents in a look-back window

A negative count shifts a date back. The window starts three years before the effective date:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
look_back_start: Date = add_years(self.effective_date, -3),
chargeable_incidents: Num = count(self.incidents as i, i.occurred >= self.look_back_start),
```

For the policy effective on 2026-08-15, the window starts on 2023-08-15.
The incident on 2024-05-02 counts, and the incident on 2022-11-30 is outside the window.

A shifted date stays inside the calendar, which runs from 0001-01-01 to 9999-12-31.
The check proves this from the range of the input.
A shift back needs an earliest date, and a shift forward needs a latest date.
A shift of an input that lacks that end gets
[`E-DATE-RANGE`](https://docs.canary.effectiveai.app/rsl/reference/check-messages/calculations#e-date-range).

## Read the year and the month

`year(date)` gives the year, and `month(date)` gives the month from 1 to 12:

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rating_year: Num =
    if month(self.effective_date) >= 10 then year(self.effective_date) + 1 else year(self.effective_date),
vehicle_age: Num = min(max(self.rating_year - self.model_year, 0), 10),
```

The manual starts each rating year in October. A policy effective on 2026-10-01 rates a 2021 vehicle as six years old.

## Check month ends and leap days

A month or year shift keeps the day of the month.
When the target month is shorter, the result is the last day of that month.

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
import pandas as pd

rows = []
for effective_date, term in [("2026-01-31", "one_month"), ("2026-08-31", "six_month"), ("2028-02-29", "annual")]:
    values = rater.explain({**policy, "effective_date": effective_date, "term": term}).set_index("name")["value"]
    rows.append(values[["effective_date", "term", "expiration_date", "term_days", "look_back_start"]])
print(pd.DataFrame(rows).to_string(index=False))
# effective_date      term expiration_date  term_days look_back_start
#     2026-01-31 one_month      2026-02-28         28      2023-01-31
#     2026-08-31 six_month      2027-02-28        181      2023-08-31
#     2028-02-29    annual      2029-02-28        365      2025-02-28
```

* 31 January plus one month is 28 February, a term of 28 days.
* 29 February 2028 plus one year is 28 February 2029.
* 29 February 2028 less three years is 28 February 2025.

`diff_years` counts a birthday on 29 February from 1 March in a common year:

```python theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
for effective_date in ["2026-02-28", "2026-03-01", "2028-02-28", "2028-02-29"]:
    values = rater.explain({**policy, "date_of_birth": "2004-02-29", "effective_date": effective_date})
    print(effective_date, values.set_index("name")["value"]["driver_age"])
# 2026-02-28 21
# 2026-03-01 22
# 2028-02-28 23
# 2028-02-29 24
```

Compare these results with the day-count rules in the manual before you rely on them.

## Related

* [Dates](https://docs.canary.effectiveai.app/rsl/reference/functions.md#dates) in the function reference gives the form of each call.
* [Rate a list of items](https://docs.canary.effectiveai.app/rsl/how-to/rate-a-list.md) covers `count` and the other list functions.
* [Round a premium](https://docs.canary.effectiveai.app/rsl/how-to/round-a-premium.md) rounds the pro rata premium.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.