Dates and time
rigour.dates
Compare prefix dates without pretending they are exact instants.
A prefix date such as 2026 or 2026-06 represents an interval, rather
than the first instant of that year or month. Use the string wrappers for
one-off comparisons, or parse a DateInterval once
when comparing the same value repeatedly.
Age and recency checks use the same comparisons with a shifted reference:
DateInterval
dataclass
Represent every possible instant described by an imprecise date.
Use this parsed form when applying multiple comparisons to the same prefix
date. Both bounds are timezone-aware UTC datetimes and end is exclusive.
Attributes:
| Name | Type | Description |
|---|---|---|
start |
datetime
|
Earliest instant represented by the date. |
end |
datetime
|
First instant after the represented interval. |
Source code in rigour/dates.py
ended_before(value, reference)
Check whether a prefix date has completely elapsed.
Use this string wrapper for one-off end-date and age-cutoff checks. Parse once with prefix_interval when making several comparisons against the same value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Canonical prefix date, from year through second precision. |
required |
reference
|
datetime
|
UTC point in time. Legacy naive values are interpreted as UTC; aware values are converted to UTC. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
reference. |
Source code in rigour/dates.py
interval_ended_before(value, reference)
Check whether an interval has completely elapsed before a point in time.
Use this for end dates and age cutoffs when the prefix date has already been parsed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
DateInterval
|
Interval to compare. |
required |
reference
|
datetime
|
Timezone-aware UTC point in time. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
reference. |
Source code in rigour/dates.py
interval_starts_after(value, reference)
Check whether an interval begins strictly after a point in time.
Use this for start dates and future-date guardrails when the prefix date has already been parsed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
DateInterval
|
Interval to compare. |
required |
reference
|
datetime
|
Timezone-aware UTC point in time. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in rigour/dates.py
parse_utc(value)
Parse an exact timestamp as an aware UTC datetime.
Use this at a prefix-date boundary where an exact timestamp may carry an offset. Naive timestamps use the ecosystem convention of implicit UTC.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Canonical second-precision timestamp. |
required |
Returns:
| Type | Description |
|---|---|
datetime
|
The represented instant as a timezone-aware UTC datetime. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The timestamp is invalid or is not precise to the second. |
Source code in rigour/dates.py
prefix_interval(value)
cached
Expand a canonical prefix date into its represented UTC interval.
Use this at the boundary between prefix-date strings and interval comparison. Feed it normalized property values only, not uncleaned source strings. Exact timestamps with offsets are converted to UTC before their one-second interval is constructed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Canonical prefix date, from year through second precision. |
required |
Returns:
| Type | Description |
|---|---|
DateInterval
|
The timezone-aware UTC, half-open interval represented by the value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
The value is invalid or non-canonical, or an imprecise value carries a timezone. |
Source code in rigour/dates.py
starts_after(value, reference)
Check whether a prefix date begins after a point in time.
Use this string wrapper for one-off start-date and future-date checks. Parse once with prefix_interval when reusing the same value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Canonical prefix date, from year through second precision. |
required |
reference
|
datetime
|
UTC point in time. Legacy naive values are interpreted as UTC; aware values are converted to UTC. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
reference. |
Source code in rigour/dates.py
rigour.time
datetime_iso(dt)
Format a datetime as a timestamp in the FollowTheMoney wire format.
Use this at every point where a timestamp is serialized for publication, so
that the same field never ships in two different shapes. The output is
'YYYY-MM-DDTHH:MM:SS' in UTC with no offset suffix, which
iso_datetime reads back exactly.
Naive datetimes are taken to be UTC already, matching the convention of naive_now. An aware datetime in another zone is converted to UTC before truncation - dropping the offset without converting would shift the timestamp - and a warning is emitted, since carrying local time this far usually means the value was built without a timezone in mind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dt
|
datetime
|
The datetime to serialize, or None. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The formatted timestamp, or None if the input was None. |
Source code in rigour/time.py
iso_datetime(value)
cached
Parse a timestamp in the FollowTheMoney wire format into an aware UTC datetime.
This is the inverse of datetime_iso: it reads
'YYYY-MM-DDTHH:MM:SS' and ignores anything after the seconds, so it also
accepts the fractional-second and offset-bearing forms that older writers in
the ecosystem emitted. It is not a general ISO 8601 parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str | None
|
The timestamp to parse, or None. |
required |
Returns:
| Type | Description |
|---|---|
datetime | None
|
An aware datetime in UTC, or None if the input was None or empty. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the leading 19 characters are not a date and time. |