Source code for ppsc._timestamp

import math
import typing
from datetime import datetime, timezone

from google.protobuf.message import Message
from google.protobuf.timestamp_pb2 import Timestamp as PbTimestamp

from .parse import ToProtobuf, FromProtobuf


[docs] class Timestamp(ToProtobuf, FromProtobuf): """ This `Timestamp` implementation is functionally equivalent to protobuf's timestamp. Per protobuf API documentation, *A Timestamp represents a point in time independent of any time zone or local calendar, encoded as a count of seconds and fractions of seconds at nanosecond resolution. The count is relative to an epoch at UTC midnight on January 1, 1970, in the proleptic Gregorian calendar which extends the Gregorian calendar backwards to year one.* Consult the `Phenopacket Schema <https://phenopacket-schema.readthedocs.io/en/latest/timestamp.html>`_ documentation for more information. **Examples** Here we show how to create a `Timestamp` from various inputs. >>> from ppsc import Timestamp Let's create a timestamp from a date time string: >>> ts = Timestamp.from_str('1970-01-01T00:00:30Z') >>> ts.seconds, ts.nanos (30, 0) Note, we indicate that the timestamp is in UTC by adding `Z` suffix. We can also create a timestamp from a local time. Let's create the same `Timestamp` but now in Eastern Daylight Time (EDT) which is 4 hours behind UTC: >>> ts_local = Timestamp.from_str('1969-12-31T20:00:30-04:00') >>> ts_local == ts True We can also create timestamp from a datetime object: >>> from datetime import datetime, date, time, timezone >>> d = date(1970, 1, 1) >>> t = time(0, 0, 30) >>> dt = datetime.combine(d, t, tzinfo=timezone.utc) >>> ts_dt = Timestamp.from_datetime(dt) >>> ts_dt == ts True Last, we can create timestamp directly from seconds and nanoseconds: >>> ts_raw = Timestamp(30, 0) >>> ts_raw == ts True and we can convert the timestamp to a UTC date time string: >>> ts_raw.as_str() '1970-01-01T00:00:30Z' """ def __init__( self, seconds: int, nanos: int, ): # Seconds can be positive or negative, # The negative seconds represent the timestamps prior the UNIX epoch. self._seconds = seconds self._nanos = nanos # TODO: check that nanos is a positive `int` @property def seconds(self) -> int: return self._seconds @seconds.setter def seconds(self, value: int): self._seconds = value @property def nanos(self) -> int: return self._nanos @nanos.setter def nanos(self, value: int): self._nanos = value
[docs] def as_datetime(self) -> datetime: """ Convert timestamp into Python's datetime object. The datetime is always in UTC. **Example** >>> from ppsc import Timestamp >>> ts = Timestamp(10, 500) >>> dt = ts.as_datetime() Now we can access the datetime components: >>> dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second (1970, 1, 1, 0, 0, 10) including the time zone: >>> dt.tzname() 'UTC' """ return datetime.fromtimestamp(self._seconds + (self._nanos / 10 ** 9), tz=timezone.utc)
[docs] def as_str(self, fmt: str = '%Y-%m-%dT%H:%M:%SZ') -> str: """ Convert timestamp into a date time string. **Example** >>> from ppsc import Timestamp >>> ts = Timestamp(0, 500_000) >>> ts.as_str() '1970-01-01T00:00:00Z' We can use different formatting: >>> ts.as_str('%Y-%m-%dT%H:%M:%S.%f%Z') '1970-01-01T00:00:00.000500UTC' """ return self.as_datetime().strftime(fmt)
[docs] @staticmethod def from_str(val: str, fmt: str = '%Y-%m-%dT%H:%M:%S%z'): """ Create `Timestamp` from a date time string. :param val: the date time `str`. :param fmt: the date time format string. """ dt = datetime.strptime(val, fmt) return Timestamp.from_datetime(dt)
[docs] @staticmethod def from_datetime(dt: datetime): fractional, integral = math.modf(dt.timestamp()) return Timestamp(seconds=int(integral), nanos=int(fractional * 1_000_000))
[docs] def to_message(self) -> Message: return PbTimestamp(seconds=self._seconds, nanos=self._nanos)
[docs] @classmethod def message_type(cls) -> typing.Type[Message]: return PbTimestamp
[docs] @classmethod def from_message(cls, msg: Message): if isinstance(msg, PbTimestamp): return Timestamp( seconds=msg.seconds, nanos=msg.nanos, ) else: cls.complain_about_incompatible_msg_type(msg)
def __eq__(self, other): return isinstance(other, Timestamp) \ and self._seconds == other._seconds \ and self._nanos == other._nanos def __repr__(self): return f'Timestamp(seconds={self._seconds}, nanos={self._nanos})'