adam_core.time.time module¶
Time utilities and a fast, integer-backed timestamp representation.
This module defines Timestamp, a quivr table storing times as integer MJD days plus integer nanoseconds within the day, tagged with a time scale (tai, tt, utc, tdb, ut1).
Timestamp.rescale() implements fast conversions between supported scales. For UT1, we delegate to astropy because UT1 requires IERS tables and interpolation.
### Empirical validation summary (2026-01)
We validated the impact of the rescale() implementation choice (default implementation vs an astropy-backed baseline) on an Asteroid Institute “real world” pipeline:
Rubin X05 MPC observation timestamps (UTC) + RA/Dec astrometry
SBDB orbit queries (for known objects)
Ephemeris generation with adam_assist.ASSISTPropagator
Residuals computed via adam_core.coordinates.residuals.Residuals on equatorial spherical coordinates (RA/Dec as lon/lat)
Two runs: default Timestamp.rescale vs a global override where Timestamp.rescale dispatches to Timestamp.rescale_astropy for all internal conversions
Results (largest run: 500 objects, max 100 observations/object; 43,919 ephemeris points):
Predicted position delta (default vs astropy baseline): max ~0.001 mas.
Residuals to Rubin astrometry (arcsec): p50 ~0.031, p90 ~0.088 for both methods.
Residual delta (default - baseline): ~0 mas at p50/p90 (no measurable change).
We also confirmed these UTC observation times exhibit a default-vs-astropy UTC→TDB offset in the expected tens-of-microseconds range (~14.6–21.8 µs for a sampled subset).
- class adam_core.time.time.Timestamp(table: Table, **kwargs: int | float | str)[source]¶
Bases:
Table- scale¶
StringAttribute represents a string which is stored as UTF-8 bytes in Table metadata.
- Parameters:
default – The default value for this attribute. If no default is provided, then the attribute must be set whenever constructing a table that uses it.
- days¶
A column for storing 64-bit integers.
- nanos¶
A column for storing 64-bit integers.
- micros() Int64Array[source]¶
- millis() Int64Array[source]¶
- seconds() Int64Array[source]¶
- key(*, scale: str | None = 'tdb') ndarray[source]¶
Return an int64 key for each timestamp: (days * NANOS_IN_DAY + nanos).
This is useful for fast grouping/uniquing and as a stable cache key when paired with a specific time scale.
- signature(*, scale: str | None = 'tdb') tuple[int, int, int, int][source]¶
Return a cheap signature for this Timestamp array.
The signature is (n, first_key, last_key, sum_mod) where keys are produced by key().
- cache_digest(*, scale: str | None = 'tdb') int[source]¶
Return an order-sensitive 64-bit digest of timestamp keys.
This is intended for cache keys where row alignment matters.
- mjd() DoubleArray[source]¶
- jd() DoubleArray[source]¶
- et() DoubleArray[source]¶
Returns the times as ET seconds in a pyarrow array.
- to_iso8601() StringArray[source]¶
Returns the times as ISO 8601 strings in a pyarrow array.
- classmethod from_iso8601(iso: StringArray | list[str], scale='utc') Timestamp[source]¶
Create a Timestamp from ISO 8601 strings (for example, ‘2020-01-02T14:15:16’).
- classmethod from_mjd(mjd: DoubleArray, scale: str = 'tai') Timestamp[source]¶
- classmethod from_jd(jd: DoubleArray, scale: str = 'tai') Timestamp[source]¶
- classmethod from_et(et: DoubleArray, scale: str = 'tdb') Timestamp[source]¶
- fractional_days() DoubleArray[source]¶
- equals(other: Timestamp, precision: str = 'ns') BooleanArray[source]¶
- equals_array(other: Timestamp, precision: str = 'ns') BooleanArray[source]¶
Compare two Timestamps, returning a BooleanArray indicating whether each element is equal.
The Timestamps must have the same scale, and the same length.
- max() Timestamp[source]¶
Compute the maximum time.
- Returns:
max_time – The maximum time. If there are multiple maximum times, one of them is returned.
- Return type:
~adam_core.time.Timestamp
- min() Timestamp[source]¶
Compute the minimum time.
- Returns:
min_time – The minimum time. If there are multiple minimum times, one of them is returned.
- Return type:
~adam_core.time.Timestamp
- classmethod from_astropy(astropy_time: Time) Timestamp[source]¶
Convert an astropy time to a quivr timestamp.
This is a lossy conversion, since astropy uses floating point to represent times, while quivr uses integers.
The astropy time must use a scale supported by quivr. The supported scales are “tai”, “tt”, “ut1”, “utc”, and “tdb”.
- add_nanos(nanos: Int64Array | int, check_range: bool = True) Timestamp[source]¶
Add nanoseconds to the timestamp. Negative nanoseconds are allowed.
- Parameters:
nanos (The nanoseconds to add. Can be a scalar or an array of) – the same length as the timestamp. Must be in the range [-86400e9, 86400e9).
check_range (If True, check that the nanoseconds are in the) – range [-86400e9, 86400e9). If False, the caller is responsible for ensuring that the nanoseconds are in the correct range.
- add_seconds(seconds: Int64Array | int | DoubleArray | float) Timestamp[source]¶
Add seconds to the timestamp. Negative seconds are supported.
- Parameters:
seconds (The seconds to add. Can be a scalar or an array of) – the same length as the timestamp. Must be in the range [-86400, 86400).
See also
add_nanosAdd nanoseconds to the timestamp. This method includes a ‘check_range’ parameter that allows the caller to disable range checking for performance reasons.
- add_millis(millis: Int64Array | int) Timestamp[source]¶
Add milliseconds to the timestamp. Negative milliseconds are supported.
- Parameters:
millis (The milliseconds to add. Can be a scalar or an array of) – the same length as the timestamp. Must be in the range [-86400e3, 86400e3).
See also
add_nanosAdd nanoseconds to the timestamp. This method includes a ‘check_range’ parameter that allows the caller to disable range checking for performance reasons.
- add_micros(micros: Int64Array | int) Timestamp[source]¶
Add microseconds to the timestamp. Negative microseconds are supported.
- Parameters:
micros (The microseconds to add. Can be a scalar or an array of) – the same length as the timestamp. Must be in the range [-86400e6, 86400e6).
See also
add_nanosAdd nanoseconds to the timestamp. This method includes a ‘check_range’ parameter that allows the caller to disable range checking for performance reasons.
- add_days(days: Int64Array | int) Timestamp[source]¶
Add days to the timestamp.
- Parameters:
days (The days to add. Can be a scalar or an array of the) – same length as the timestamp. Use negative values to subtract days.
- add_fractional_days(fractional_days: DoubleArray | float) Timestamp[source]¶
Add fractional days to the timestamp.
- Parameters:
fractional_days (The fractional days to add. Can be a scalar) – or an array of the same length as the timestamp. Use negative values to subtract fractional days.
- difference_scalar(days: int, nanos: int) tuple[Int64Array, Int64Array][source]¶
Compute the difference between this timestamp and a scalar timestamp.
The difference is computed as (self - scalar). The result is presented as a tuple of (days, nanos). The nanos value is always non-negative, in the range [0, 86400e9).
- Parameters:
days (The days of the scalar timestamp.)
nanos (The nanoseconds of the scalar timestamp.)
- Returns:
days (The difference in days. This value can be negative.)
nanos (The difference in nanoseconds. This value is always) – non-negative, in the range [0, 86400e9).
Examples
>>> from adam_core.time import Timestamp >>> ts = Timestamp.from_kwargs(days=[0, 1, 2], nanos=[200, 0, 100]) >>> have_days, have_nanos = ts.difference_scalar(1, 100) >>> have_days.to_pylist() [-1, -1, 1] >>> have_nanos.to_pylist() [100, 86399999999900, 0]
- difference(other: Timestamp) tuple[Int64Array, Int64Array][source]¶
Compute the element-wise difference between this timestamp and another.
- unique() Timestamp[source]¶
Return a new Timestamp table containing only the unique elements from self. Order is not necessarily preserved.
- rescale(new_scale: str) Timestamp[source]¶
Convert this Timestamp to new_scale.
Notes
For ut1 conversions, we delegate to rescale_astropy() because UT1 requires IERS tables and interpolation that astropy manages.
For conversions involving TDB, this implementation uses a different (non-Earth-location- dependent) approximation than astropy/ERFA, and differences of 10-30 microseconds versus astropy can occur. Round-trip conversions within this implementation are designed to be stable (see tests).
See also
src/adam_core/time/tests/test_time.py: correctness + benchmark tests for rescale.
- link(other: Timestamp, precision: str = 'ns') MultiKeyLinkage[Timestamp, Timestamp][source]¶
Link this Timestamp to another. The default precision is nanoseconds, but if other precisions are desired then both this class and the other Timestamp will be rounded to the desired precision.
If the timescales are different, the other Timestamp will be rescaled to this Timestamp’s timescale.
- Parameters:
other (The Timestamp to link to.)
precision (The precision to use when linking. The default is 'ns'.)
- Returns:
linkage
- Return type:
A MultiKeyLinkage object that can be used to join the two Timestamps.
- schema: ClassVar[pa.Schema] = days: int64 not null nanos: int64 not null¶
- table: pa.Table¶