Source code for ursa.time
"""Canonical timestamp helpers for Ursa.
Absolute timestamps in the catalog are ``int64`` nanoseconds since the Unix
epoch (1970-01-01T00:00:00 UTC). This is the native unit for ``pa.timestamp(
"ns", tz="UTC")``, ``numpy.datetime64[ns]``, and ``pandas.Timestamp``, and
int64 covers 1677-09-21 through 2262-04-11.
See the Constellation Research Stack architecture plan ยง6.12 for the
cross-package convention.
"""
from __future__ import annotations
import time
from datetime import UTC, datetime
__all__ = [
"INT64_MAX",
"INT64_MIN",
"datetime_to_ns",
"now_ns",
"ns_to_datetime",
]
INT64_MIN = -(2**63)
INT64_MAX = 2**63 - 1
[docs]
def now_ns() -> int:
"""Current wall-clock time as int64 nanoseconds since the Unix epoch."""
return time.time_ns()
[docs]
def ns_to_datetime(ns: int) -> datetime:
"""Convert int64 ns since Unix epoch to an aware UTC ``datetime``.
Lossy past microsecond precision: Python's ``datetime`` cannot represent
sub-microsecond values, so the final 3 digits of ``ns`` are truncated.
For the ns-exact view, work with the integer directly.
"""
seconds, remainder_ns = divmod(ns, 1_000_000_000)
# datetime takes microseconds; integer-divide off the last 3 ns digits.
microseconds = remainder_ns // 1_000
return datetime.fromtimestamp(seconds, tz=UTC).replace(microsecond=microseconds)
_EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
[docs]
def datetime_to_ns(dt: datetime) -> int:
"""Convert an aware ``datetime`` to int64 ns since Unix epoch.
Naive datetimes are rejected. Any aware timezone is normalized to UTC
before the multiply. Pre-1970 datetimes produce negative ns values.
"""
if dt.tzinfo is None:
raise ValueError(f"datetime must be timezone-aware to convert to ns; got naive {dt!r}")
# ``timedelta`` arithmetic floors correctly for negative deltas (unlike
# ``int(timestamp())`` which truncates toward zero).
delta = dt.astimezone(UTC) - _EPOCH
return (delta.days * 86_400 + delta.seconds) * 1_000_000_000 + delta.microseconds * 1_000