# Copyright (c) 2025-2026 Buf Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations from datetime import datetime, timedelta, timezone from time import time_ns from typing import TYPE_CHECKING, TypeVar from ._const import ( EPOCH_DATETIME, MAX_NANOS, MICROSECOND_DELTA, MIN_NANOS, SECOND_AS_NANOS, ) Self = TypeVar("Self", bound="TimestampMixin") class TimestampMixin: __slots__ = () if TYPE_CHECKING: def __init__(self, *, seconds: int = 0, nanos: int = 0) -> None: ... seconds: int nanos: int @classmethod def now(cls: type[Self]) -> Self: """Create for the current time.""" return cls.from_nanos(time_ns()) @classmethod def from_nanos(cls: type[Self], nanos: int, /) -> Self: """Create from a Unix timestamp in nanoseconds.""" if nanos > MAX_NANOS or nanos < MIN_NANOS: msg = "seconds out of range" raise OverflowError(msg) return cls(seconds=nanos // SECOND_AS_NANOS, nanos=nanos % SECOND_AS_NANOS) @classmethod def from_datetime(cls: type[Self], dt: datetime, /) -> Self: """Create from the given datetime.""" return cls.from_nanos( ((dt.astimezone(timezone.utc) - EPOCH_DATETIME) // MICROSECOND_DELTA) * 1000 ) def to_datetime(self) -> datetime: """Convert to a datetime.""" return EPOCH_DATETIME + timedelta( seconds=self.seconds, microseconds=round(self.nanos / 1000) ) def to_nanos(self) -> int: """Convert to the number of nanoseconds since Unix epoch 1970-01-01T00:00:00Z. Examples: >>> from protobuf.wkt import Timestamp >>> Timestamp(nanos=1_000).to_nanos() 1000 >>> Timestamp(seconds=10, nanos=100).to_nanos() 10000000100 """ return self.seconds * SECOND_AS_NANOS + self.nanos def to_seconds(self) -> float: """Convert to a number of seconds since Unix epoch 1970-01-01T00:00:00Z. Parts of a second are expressed as fractional values. Examples: >>> from protobuf.wkt import Timestamp >>> Timestamp(seconds=10).to_seconds() 10.0 >>> Timestamp(nanos=100).to_seconds() 1e-07 >>> Timestamp(seconds=10, nanos=100).to_seconds() 10.0000001 """ return self.seconds + self.nanos / SECOND_AS_NANOS @classmethod def from_seconds(cls: type[Self], seconds: float, /) -> Self: """Create a new Timestamp from a timestamp expressed in seconds since Unix epoch 1970-01-01T00:00:00Z. Parts of a second are expressed as fractional values. Examples: >>> from protobuf.wkt import Timestamp >>> Timestamp.from_seconds(10) Timestamp(seconds=10) >>> Timestamp.from_seconds(10.1) Timestamp(seconds=10, nanos=100000000) >>> Timestamp.from_seconds(-10.1) Timestamp(seconds=-11, nanos=900000000) """ nanos = round(seconds * SECOND_AS_NANOS) return cls.from_nanos(nanos) # OverflowError: date value out of range