_duration.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. # Copyright (c) 2025-2026 Buf Technologies, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import annotations
  15. from datetime import timedelta
  16. from typing import TYPE_CHECKING, TypeVar
  17. from ._const import MICROSECOND_DELTA, SECOND_AS_NANOS
  18. Self = TypeVar("Self", bound="DurationMixin")
  19. class DurationMixin:
  20. __slots__ = ()
  21. if TYPE_CHECKING:
  22. def __init__(self, *, seconds: int = 0, nanos: int = 0) -> None: ...
  23. seconds: int
  24. nanos: int
  25. @classmethod
  26. def from_nanos(cls: type[Self], nanos: int, /) -> Self:
  27. """Create from a duration in nanoseconds."""
  28. sign = -1 if nanos < 0 else 1
  29. nanos = abs(nanos)
  30. return cls(
  31. seconds=(nanos // SECOND_AS_NANOS) * sign,
  32. nanos=(nanos % SECOND_AS_NANOS) * sign,
  33. )
  34. @classmethod
  35. def from_timedelta(cls: type[Self], td: timedelta, /) -> Self:
  36. """Create from the given timedelta."""
  37. return cls.from_nanos((td // MICROSECOND_DELTA) * 1000)
  38. def to_timedelta(self) -> timedelta:
  39. """Convert to a timedelta."""
  40. return timedelta(seconds=self.seconds, microseconds=round(self.nanos / 1000))
  41. def to_nanos(self) -> int:
  42. """Convert to the number of nanoseconds.
  43. Examples:
  44. >>> from protobuf.wkt import Duration
  45. >>> Duration(nanos=1_000).to_nanos()
  46. 1000
  47. >>> Duration(seconds=10, nanos=100).to_nanos()
  48. 10000000100
  49. """
  50. return self.seconds * SECOND_AS_NANOS + self.nanos
  51. def to_seconds(self) -> float:
  52. """Convert to a number of seconds.
  53. Parts of a second are expressed as fractional values.
  54. Examples:
  55. >>> from protobuf.wkt import Duration
  56. >>> Duration(seconds=10).to_seconds()
  57. 10.0
  58. >>> Duration(nanos=100).to_seconds()
  59. 1e-07
  60. >>> Duration(seconds=10, nanos=100).to_seconds()
  61. 10.0000001
  62. """
  63. return self.seconds + self.nanos / SECOND_AS_NANOS
  64. @classmethod
  65. def from_seconds(cls: type[Self], seconds: float, /) -> Self:
  66. """Create a new Duration from a duration in seconds.
  67. Parts of a second are expressed as fractional values.
  68. Examples:
  69. >>> from protobuf.wkt import Duration
  70. >>> Duration.from_seconds(10)
  71. Duration(seconds=10)
  72. >>> Duration.from_seconds(10.1)
  73. Duration(seconds=10, nanos=100000000)
  74. """
  75. nanos = round(seconds * SECOND_AS_NANOS)
  76. return cls.from_nanos(nanos)