diff --git a/README.md b/README.md index ce9b319..0ab82a0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This project aims to provide an improved experience when using Protobuf / gRPC i - Enums - Dataclasses - `async`/`await` - - Timezone-aware `datetime` and `timedelta` objects + - Timezone-aware `datetime` and `timedelta` objects, including nanosecond-precision `Timestamp` values - Relative imports - Mypy type checking - [Pydantic Models](https://docs.pydantic.dev/) generation diff --git a/betterproto2/docs/getting-started.md b/betterproto2/docs/getting-started.md index 68d0749..2cb3621 100644 --- a/betterproto2/docs/getting-started.md +++ b/betterproto2/docs/getting-started.md @@ -225,6 +225,12 @@ Message objects include `betterproto.Message.to_json` and `betterproto.Message.to_dict`, `betterproto.Message.from_dict` for converting back and forth from JSON serializable dicts. +`google.protobuf.Timestamp` fields use timezone-aware `datetime.datetime` +values. When binary or JSON data contains sub-microsecond precision, +betterproto2 preserves it by returning a `betterproto2.nano_datetime.NanoDatetime`, +which is a `datetime.datetime` subclass. Timestamp JSON accepts and emits +RFC 3339 strings with up to 9 fractional second digits. + For compatibility the default is to convert field names to `betterproto.Casing.CAMEL`. You can control this behavior by passing a different casing value, e.g: diff --git a/betterproto2/docs/index.md b/betterproto2/docs/index.md index 11fbfd6..fa9864e 100644 --- a/betterproto2/docs/index.md +++ b/betterproto2/docs/index.md @@ -12,5 +12,6 @@ Python code, using modern language features. - Generated messages are both binary & JSON serializable - Messages use relevant python types, e.g. ``Enum``, ``datetime`` and ``timedelta`` objects +- ``Timestamp`` values preserve nanosecond precision when binary or JSON data contains it - ``async``/``await`` support for gRPC Clients and Servers - Generates modern, readable, idiomatic python code diff --git a/betterproto2/src/betterproto2/nano_datetime.py b/betterproto2/src/betterproto2/nano_datetime.py new file mode 100644 index 0000000..c2bebab --- /dev/null +++ b/betterproto2/src/betterproto2/nano_datetime.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +import datetime +import re +from typing import Any + +import dateutil.parser +from typing_extensions import Self + +_UTC = datetime.timezone.utc +_TIMESTAMP_ZERO = datetime.datetime(1970, 1, 1, tzinfo=_UTC) +_NANOS_PER_MICROSECOND = 1000 +_MICROS_PER_SECOND = 10**6 +_TIMESTAMP_RE = re.compile( + r"^" + r"(?P\d{4}-\d{2}-\d{2})" + r"T" + r"(?P