sain.time API documentation

# sain.time

Temporal quantification

View Source

      1# BSD 3-Clause License
      2#
      3# Copyright (c) 2022-Present, nxtlo
      4# All rights reserved.
      5#
      6# Redistribution and use in source and binary forms, with or without
      7# modification, are permitted provided that the following conditions are met:
      8#
      9# * Redistributions of source code must retain the above copyright notice, this
     10#   list of conditions and the following disclaimer.
     11#
     12# * Redistributions in binary form must reproduce the above copyright notice,
     13#   this list of conditions and the following disclaimer in the documentation
     14#   and/or other materials provided with the distribution.
     15#
     16# * Neither the name of the copyright holder nor the names of its
     17#   contributors may be used to endorse or promote products derived from
     18#   this software without specific prior written permission.
     19#
     20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
     24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     26# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     27# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     28# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30
     31"""Temporal quantification"""
     32
     33from __future__ import annotations
     34
     35import typing
     36
     37__all__ = ("Duration",)
     38
     39import datetime
     40import sys
     41
     42NANOS_PER_SEC = 1_000_000_000
     43NANOS_PER_MILLI = 1_000_000
     44NANOS_PER_MICRO = 1_000
     45MILLIS_PER_SEC = 1_000
     46MICROS_PER_SEC = 1_000_000
     47SECS_PER_MINUTE = 60
     48MINS_PER_HOUR = 60
     49HOURS_PER_DAY = 24
     50DAYS_PER_WEEK = 7
     51
     52
     53@typing.final
     54class Duration:
     55    """A `Duration` type, used to represent a span of time, usually used for system timeouts.
     56
     57    Each `Duration` is composed of a number of seconds and a fractional part represented
     58    as `nanoseconds`.
     59
     60    It includes core methods inherited from `std::time::Duration`.
     61
     62    `Duration` implement many common operators such as `+`, `-`, `*` and many more.
     63
     64    Example
     65    -------
     66    ```py
     67    five_seconds = Duration(5, 0)
     68    ten_seconds = five_seconds * Duration(2, 0)
     69    assert ten_seconds.as_secs() == 10
     70
     71    ten_milli_secs = Duration.from_millis(10)
     72    ```
     73    """
     74
     75    __slots__ = ("_secs", "_nanos")
     76    _secs: int
     77    _nanos: int
     78
     79    # These are lazily initialized at the end.
     80
     81    SECOND: typing.ClassVar[Duration]
     82    """The duration of one second.
     83
     84    Example
     85    -------
     86    ```py
     87    from sain.time import Duration
     88
     89    assert Duration.SECOND, Duration.from_secs(1)
     90    ```
     91    """
     92
     93    MILLISECOND: typing.ClassVar[Duration]
     94    """The duration of one millisecond.
     95
     96    Example
     97    -------
     98    ```py
     99    from sain.time import Duration
    100
    101    assert Duration.MILLISECOND == Duration.from_millis(1)
    102    ```
    103    """
    104
    105    MICROSECOND: typing.ClassVar[Duration]
    106    """The duration of one microsecond.
    107
    108    Example
    109    -------
    110    ```py
    111    from sain.time import Duration
    112
    113    assert Duration.MICROSECOND == Duration.from_micros(1)
    114    ```
    115    """
    116
    117    NANOSECOND: typing.ClassVar[Duration]
    118    """The duration of one nanosecond.
    119
    120    Example
    121    -------
    122    ```py
    123    from sain.time import Duration
    124
    125    assert Duration.NANOSECOND == Duration.from_nanos(1)
    126    ```
    127    """
    128
    129    ZERO: typing.ClassVar[Duration]
    130    """The zero duration.
    131
    132    Example
    133    -------
    134    ```py
    135    from sain.time import Duration
    136
    137    assert Duration.ZERO == Duration(0, 0)
    138    ```
    139    """
    140
    141    MAX: typing.ClassVar[Duration]
    142    """The maximum possible duration.
    143
    144    Example
    145    -------
    146    ```py
    147    from sain.time import Duration
    148
    149    assert Duration.MAX.as_secs() == sys.maxsize
    150    ```
    151    """
    152
    153    def __init__(self, secs: int, nanos: int, /) -> None:
    154        self._secs = secs
    155        self._nanos = nanos
    156
    157    def __new__(cls, secs: int, nanos: int, /) -> Duration:
    158        if secs < 0:
    159            raise ValueError("`secs` must be non-negative.")
    160
    161        new = super().__new__(cls)
    162        if nanos < NANOS_PER_SEC:
    163            new._secs = secs
    164            new._nanos = nanos
    165        else:
    166            extra_secs = nanos // NANOS_PER_SEC
    167            try:
    168                new._secs = secs + extra_secs
    169            except OverflowError as e:
    170                raise OverflowError("overflow in Duration.__new__") from e
    171            new._nanos = nanos % NANOS_PER_SEC
    172
    173        return new
    174
    175    @classmethod
    176    def from_timedelta(cls, delta: datetime.timedelta) -> Duration:
    177        """Creates a `Duration` new from a `timedelta`.
    178
    179        Example
    180        -------
    181        ```py
    182        duration = Duration.from_timedelta(datetime.timedelta(minutes=1, seconds=30))
    183        assert duration.as_secs() == 90
    184        ```
    185        """
    186        nanos = int(delta.total_seconds() * NANOS_PER_SEC)
    187        return Duration.from_nanos(nanos)
    188
    189    @classmethod
    190    def from_secs(cls, secs: int) -> Duration:
    191        """Creates a `Duration` new representing the given number of seconds.
    192
    193        Example
    194        -------
    195        ```py
    196        duration = Duration.from_secs(10)
    197        assert duration.as_secs() == 10
    198        ```
    199        """
    200        return Duration(secs, 0)
    201
    202    @classmethod
    203    def from_millis(cls, n: int) -> Duration:
    204        """Creates a `Duration` new representing the given number of milliseconds.
    205
    206        Example
    207        -------
    208        ```py
    209        duration = Duration.from_millis(1500)
    210        assert duration.as_secs() == 1
    211        assert duration.subsec_millis() == 500
    212        ```
    213        """
    214        secs = n // MILLIS_PER_SEC
    215        nanos = (n % MILLIS_PER_SEC) * NANOS_PER_MILLI
    216        return cls(secs, nanos)
    217
    218    @classmethod
    219    def from_micros(cls, n: int) -> Duration:
    220        """Creates a `Duration` new representing the given number of microseconds.
    221
    222        Example
    223        -------
    224        ```py
    225        duration = Duration.from_micros(1_500_000)
    226        assert duration.as_secs() == 1
    227        assert duration.subsec_micros() == 500_000
    228        ```
    229        """
    230        secs = n // MICROS_PER_SEC
    231        nanos = (n % MICROS_PER_SEC) * NANOS_PER_MICRO
    232        return cls(secs, nanos)
    233
    234    @classmethod
    235    def from_nanos(cls, n: int) -> Duration:
    236        """Creates a `Duration` new representing the given number of nanoseconds.
    237
    238        Example
    239        -------
    240        ```py
    241        duration = Duration.from_nanos(1_500_000_000)
    242        assert duration.as_secs() == 1
    243        assert duration.subsec_nanos() == 500_000_000
    244        ```
    245        """
    246        secs = n // NANOS_PER_SEC
    247        nanos = n % NANOS_PER_SEC
    248        return cls(secs, nanos)
    249
    250    @classmethod
    251    def from_weeks(cls, n: int) -> Duration:
    252        """Creates a `Duration` new representing the given number of weeks.
    253
    254        Example
    255        -------
    256        ```py
    257        duration = Duration.from_weeks(2)
    258        assert duration.as_secs() == 2 * 7 * 24 * 60 * 60
    259        ```
    260        """
    261        return cls(
    262            n * DAYS_PER_WEEK * HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MINUTE, 0
    263        )
    264
    265    @classmethod
    266    def from_days(cls, n: int) -> Duration:
    267        """Creates a `Duration` new representing the given number of days.
    268
    269        Example
    270        -------
    271        ```py
    272        duration = Duration.from_days(3)
    273        assert duration.as_secs() == 3 * 24 * 60 * 60
    274        ```
    275        """
    276        return cls(n * HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MINUTE, 0)
    277
    278    @classmethod
    279    def from_hours(cls, n: int) -> Duration:
    280        """Creates a `Duration` new representing the given number of hours.
    281
    282        Example
    283        -------
    284        ```py
    285        duration = Duration.from_hours(5)
    286        assert duration.as_secs() == 5 * 60 * 60
    287        ```
    288        """
    289        return cls(n * MINS_PER_HOUR * SECS_PER_MINUTE, 0)
    290
    291    @classmethod
    292    def from_mins(cls, n: int) -> Duration:
    293        """Creates a `Duration` new representing the given number of minutes.
    294
    295        Example
    296        -------
    297        ```py
    298        duration = Duration.from_mins(3)
    299        assert duration.as_secs() == 3 * 60
    300        ```
    301        """
    302        return cls(n * SECS_PER_MINUTE, 0)
    303
    304    def is_zero(self) -> bool:
    305        """Returns True if the duration is zero (both seconds and nanoseconds are zero).
    306
    307        Example
    308        -------
    309        ```py
    310        duration = Duration(0, 0)
    311        assert duration.is_zero() is True
    312
    313        duration = Duration(1, 0)
    314        assert duration.is_zero() is False
    315        ```
    316        """
    317        return self._secs == 0 and self._nanos == 0
    318
    319    def as_secs(self) -> int:
    320        """Returns the number of whole seconds in the duration as an integer.
    321
    322        This does not return the included fractional (nanosecond) part of the
    323        duration, this can be returned by using `subsec_nanos`.
    324
    325        Example
    326        -------
    327        ```py
    328        duration = Duration(5, 500_000_000)
    329        assert duration.as_secs() == 5
    330        ```
    331        """
    332        return self._secs
    333
    334    def subsec_millis(self) -> float:
    335        """Returns the fractional part of this `Duration` in a whole milliseconds.
    336
    337        Example
    338        -------
    339        ```py
    340        duration = Duration(1, 500_000_000)
    341        assert duration.subsec_millis() == 500.0
    342        ```
    343        """
    344        return self._nanos / NANOS_PER_MILLI
    345
    346    def subsec_micros(self) -> float:
    347        """Returns the fractional part of this `Duration` in a whole microseconds.
    348
    349        Example
    350        -------
    351        ```py
    352        duration = Duration(1, 500_000)
    353        assert duration.subsec_micros() == 500.0
    354        ```
    355        """
    356        return self._nanos / NANOS_PER_MICRO
    357
    358    def subsec_nanos(self) -> int:
    359        """Returns the fractional part of this `Duration` in a whole nanoseconds.
    360
    361        Example
    362        -------
    363        ```py
    364        duration = Duration.from_millis(5_010)
    365        assert duration.subsec_nanos() == 10_000_000
    366        ```
    367        """
    368        return self._nanos
    369
    370    def as_millis(self) -> float:
    371        """Returns the total duration in milliseconds as a floating-point number.
    372
    373        This includes both the whole seconds and the fractional nanoseconds
    374        converted into milliseconds.
    375
    376        Example
    377        -------
    378        ```py
    379        duration = Duration(1, 500_000_000)
    380        assert duration.as_millis() == 1500.0
    381        ```
    382        """
    383        return self._secs * MILLIS_PER_SEC + (self._nanos / NANOS_PER_MILLI)
    384
    385    def as_micros(self) -> float:
    386        """Returns the total duration in microseconds as a floating-point number.
    387
    388        This includes both the whole seconds and the fractional nanoseconds
    389        converted into microseconds.
    390
    391        Example
    392        -------
    393        ```py
    394        duration = Duration(1, 500_000)
    395        assert duration.as_micros() == 1_000_500.0
    396        ```
    397        """
    398        return self._secs * MICROS_PER_SEC + (self._nanos / NANOS_PER_MICRO)
    399
    400    def as_nanos(self) -> int:
    401        """Returns the total duration in nanoseconds as an integer.
    402
    403        This includes both the whole seconds and the fractional nanoseconds.
    404
    405        Example
    406        -------
    407        ```py
    408        duration = Duration(1, 500_000_000)
    409        assert duration.as_nanos() == 1_500_000_000
    410        ```
    411        """
    412        return self._secs * NANOS_PER_SEC + self._nanos
    413
    414    def __add__(self, rhs: Duration) -> Duration:
    415        if type(rhs) is not Duration:
    416            raise TypeError("rhs must be of type `Duration`")
    417
    418        secs = self._secs + rhs._secs
    419        nanos = self._nanos + rhs._nanos
    420        return Duration(secs, nanos)
    421
    422    def __sub__(self, rhs: Duration) -> Duration:
    423        if type(rhs) is not Duration:
    424            raise TypeError("rhs must be of type `Duration`")
    425
    426        if self._nanos >= rhs._nanos:
    427            return Duration(self._secs - rhs._secs, self._nanos - rhs._nanos)
    428        return Duration(
    429            self._secs - rhs._secs - 1, NANOS_PER_SEC + self._nanos - rhs._nanos
    430        )
    431
    432    def __mul__(self, rhs: Duration) -> Duration:
    433        if type(rhs) is not Duration:
    434            raise TypeError("rhs must be of type `Duration`")
    435
    436        total_nanos = (self._secs * NANOS_PER_SEC + self._nanos) * (
    437            rhs._secs * NANOS_PER_SEC + rhs._nanos
    438        )
    439        secs = total_nanos // NANOS_PER_SEC
    440        nanos = total_nanos % NANOS_PER_SEC
    441        return Duration(secs, nanos)
    442
    443    def __iadd__(self, rhs: Duration) -> Duration:
    444        if type(rhs) is not Duration:
    445            raise TypeError("rhs must be of type `Duration`")
    446
    447        self._secs += rhs._secs
    448        self._nanos += rhs._nanos
    449        if self._nanos >= NANOS_PER_SEC:
    450            self._secs += 1
    451            self._nanos -= NANOS_PER_SEC
    452        return self
    453
    454    def __isub__(self, rhs: Duration) -> Duration:
    455        if type(rhs) is not Duration:
    456            raise TypeError("rhs must be of type `Duration`")
    457
    458        if self._nanos >= rhs._nanos:
    459            self._nanos -= rhs._nanos
    460            self._secs -= rhs._secs
    461        else:
    462            self._nanos = NANOS_PER_SEC + self._nanos - rhs._nanos
    463            self._secs = self._secs - rhs._secs - 1
    464        return self
    465
    466    def __imul__(self, rhs: Duration) -> Duration:
    467        if type(rhs) is not Duration:
    468            raise TypeError("rhs must be of type `Duration`")
    469        total_nanos = (self._secs * NANOS_PER_SEC + self._nanos) * (
    470            rhs._secs * NANOS_PER_SEC + rhs._nanos
    471        )
    472        self._secs = total_nanos // NANOS_PER_SEC
    473        self._nanos = total_nanos % NANOS_PER_SEC
    474        return self
    475
    476    def __truediv__(self, rhs: Duration) -> float:
    477        if type(rhs) is not Duration:
    478            raise TypeError("rhs must be of type `Duration`")
    479        self_nanos = self._secs * NANOS_PER_SEC + self._nanos
    480        rhs_nanos = rhs._secs * NANOS_PER_SEC + rhs._nanos
    481        if rhs_nanos == 0:
    482            raise ZeroDivisionError("division by zero")
    483        return self_nanos / rhs_nanos
    484
    485    def __itruediv__(self, rhs: Duration) -> float:
    486        if type(rhs) is not Duration:
    487            raise TypeError("rhs must be of type `Duration`")
    488
    489        return self.__truediv__(rhs)
    490
    491    def __repr__(self) -> str:
    492        return f"Duration(secs={self._secs}, nanos={self._nanos})"
    493
    494
    495Duration.SECOND = Duration.from_secs(1)
    496Duration.MILLISECOND = Duration.from_millis(1)
    497Duration.MICROSECOND = Duration.from_micros(1)
    498Duration.NANOSECOND = Duration.from_nanos(1)
    499Duration.ZERO = Duration(0, 0)
    500Duration.MAX = Duration(sys.maxsize, NANOS_PER_SEC - 1)

@typing.final
class Duration: View Source

     54@typing.final
     55class Duration:
     56    """A `Duration` type, used to represent a span of time, usually used for system timeouts.
     57
     58    Each `Duration` is composed of a number of seconds and a fractional part represented
     59    as `nanoseconds`.
     60
     61    It includes core methods inherited from `std::time::Duration`.
     62
     63    `Duration` implement many common operators such as `+`, `-`, `*` and many more.
     64
     65    Example
     66    -------
     67    ```py
     68    five_seconds = Duration(5, 0)
     69    ten_seconds = five_seconds * Duration(2, 0)
     70    assert ten_seconds.as_secs() == 10
     71
     72    ten_milli_secs = Duration.from_millis(10)
     73    ```
     74    """
     75
     76    __slots__ = ("_secs", "_nanos")
     77    _secs: int
     78    _nanos: int
     79
     80    # These are lazily initialized at the end.
     81
     82    SECOND: typing.ClassVar[Duration]
     83    """The duration of one second.
     84
     85    Example
     86    -------
     87    ```py
     88    from sain.time import Duration
     89
     90    assert Duration.SECOND, Duration.from_secs(1)
     91    ```
     92    """
     93
     94    MILLISECOND: typing.ClassVar[Duration]
     95    """The duration of one millisecond.
     96
     97    Example
     98    -------
     99    ```py
    100    from sain.time import Duration
    101
    102    assert Duration.MILLISECOND == Duration.from_millis(1)
    103    ```
    104    """
    105
    106    MICROSECOND: typing.ClassVar[Duration]
    107    """The duration of one microsecond.
    108
    109    Example
    110    -------
    111    ```py
    112    from sain.time import Duration
    113
    114    assert Duration.MICROSECOND == Duration.from_micros(1)
    115    ```
    116    """
    117
    118    NANOSECOND: typing.ClassVar[Duration]
    119    """The duration of one nanosecond.
    120
    121    Example
    122    -------
    123    ```py
    124    from sain.time import Duration
    125
    126    assert Duration.NANOSECOND == Duration.from_nanos(1)
    127    ```
    128    """
    129
    130    ZERO: typing.ClassVar[Duration]
    131    """The zero duration.
    132
    133    Example
    134    -------
    135    ```py
    136    from sain.time import Duration
    137
    138    assert Duration.ZERO == Duration(0, 0)
    139    ```
    140    """
    141
    142    MAX: typing.ClassVar[Duration]
    143    """The maximum possible duration.
    144
    145    Example
    146    -------
    147    ```py
    148    from sain.time import Duration
    149
    150    assert Duration.MAX.as_secs() == sys.maxsize
    151    ```
    152    """
    153
    154    def __init__(self, secs: int, nanos: int, /) -> None:
    155        self._secs = secs
    156        self._nanos = nanos
    157
    158    def __new__(cls, secs: int, nanos: int, /) -> Duration:
    159        if secs < 0:
    160            raise ValueError("`secs` must be non-negative.")
    161
    162        new = super().__new__(cls)
    163        if nanos < NANOS_PER_SEC:
    164            new._secs = secs
    165            new._nanos = nanos
    166        else:
    167            extra_secs = nanos // NANOS_PER_SEC
    168            try:
    169                new._secs = secs + extra_secs
    170            except OverflowError as e:
    171                raise OverflowError("overflow in Duration.__new__") from e
    172            new._nanos = nanos % NANOS_PER_SEC
    173
    174        return new
    175
    176    @classmethod
    177    def from_timedelta(cls, delta: datetime.timedelta) -> Duration:
    178        """Creates a `Duration` new from a `timedelta`.
    179
    180        Example
    181        -------
    182        ```py
    183        duration = Duration.from_timedelta(datetime.timedelta(minutes=1, seconds=30))
    184        assert duration.as_secs() == 90
    185        ```
    186        """
    187        nanos = int(delta.total_seconds() * NANOS_PER_SEC)
    188        return Duration.from_nanos(nanos)
    189
    190    @classmethod
    191    def from_secs(cls, secs: int) -> Duration:
    192        """Creates a `Duration` new representing the given number of seconds.
    193
    194        Example
    195        -------
    196        ```py
    197        duration = Duration.from_secs(10)
    198        assert duration.as_secs() == 10
    199        ```
    200        """
    201        return Duration(secs, 0)
    202
    203    @classmethod
    204    def from_millis(cls, n: int) -> Duration:
    205        """Creates a `Duration` new representing the given number of milliseconds.
    206
    207        Example
    208        -------
    209        ```py
    210        duration = Duration.from_millis(1500)
    211        assert duration.as_secs() == 1
    212        assert duration.subsec_millis() == 500
    213        ```
    214        """
    215        secs = n // MILLIS_PER_SEC
    216        nanos = (n % MILLIS_PER_SEC) * NANOS_PER_MILLI
    217        return cls(secs, nanos)
    218
    219    @classmethod
    220    def from_micros(cls, n: int) -> Duration:
    221        """Creates a `Duration` new representing the given number of microseconds.
    222
    223        Example
    224        -------
    225        ```py
    226        duration = Duration.from_micros(1_500_000)
    227        assert duration.as_secs() == 1
    228        assert duration.subsec_micros() == 500_000
    229        ```
    230        """
    231        secs = n // MICROS_PER_SEC
    232        nanos = (n % MICROS_PER_SEC) * NANOS_PER_MICRO
    233        return cls(secs, nanos)
    234
    235    @classmethod
    236    def from_nanos(cls, n: int) -> Duration:
    237        """Creates a `Duration` new representing the given number of nanoseconds.
    238
    239        Example
    240        -------
    241        ```py
    242        duration = Duration.from_nanos(1_500_000_000)
    243        assert duration.as_secs() == 1
    244        assert duration.subsec_nanos() == 500_000_000
    245        ```
    246        """
    247        secs = n // NANOS_PER_SEC
    248        nanos = n % NANOS_PER_SEC
    249        return cls(secs, nanos)
    250
    251    @classmethod
    252    def from_weeks(cls, n: int) -> Duration:
    253        """Creates a `Duration` new representing the given number of weeks.
    254
    255        Example
    256        -------
    257        ```py
    258        duration = Duration.from_weeks(2)
    259        assert duration.as_secs() == 2 * 7 * 24 * 60 * 60
    260        ```
    261        """
    262        return cls(
    263            n * DAYS_PER_WEEK * HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MINUTE, 0
    264        )
    265
    266    @classmethod
    267    def from_days(cls, n: int) -> Duration:
    268        """Creates a `Duration` new representing the given number of days.
    269
    270        Example
    271        -------
    272        ```py
    273        duration = Duration.from_days(3)
    274        assert duration.as_secs() == 3 * 24 * 60 * 60
    275        ```
    276        """
    277        return cls(n * HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MINUTE, 0)
    278
    279    @classmethod
    280    def from_hours(cls, n: int) -> Duration:
    281        """Creates a `Duration` new representing the given number of hours.
    282
    283        Example
    284        -------
    285        ```py
    286        duration = Duration.from_hours(5)
    287        assert duration.as_secs() == 5 * 60 * 60
    288        ```
    289        """
    290        return cls(n * MINS_PER_HOUR * SECS_PER_MINUTE, 0)
    291
    292    @classmethod
    293    def from_mins(cls, n: int) -> Duration:
    294        """Creates a `Duration` new representing the given number of minutes.
    295
    296        Example
    297        -------
    298        ```py
    299        duration = Duration.from_mins(3)
    300        assert duration.as_secs() == 3 * 60
    301        ```
    302        """
    303        return cls(n * SECS_PER_MINUTE, 0)
    304
    305    def is_zero(self) -> bool:
    306        """Returns True if the duration is zero (both seconds and nanoseconds are zero).
    307
    308        Example
    309        -------
    310        ```py
    311        duration = Duration(0, 0)
    312        assert duration.is_zero() is True
    313
    314        duration = Duration(1, 0)
    315        assert duration.is_zero() is False
    316        ```
    317        """
    318        return self._secs == 0 and self._nanos == 0
    319
    320    def as_secs(self) -> int:
    321        """Returns the number of whole seconds in the duration as an integer.
    322
    323        This does not return the included fractional (nanosecond) part of the
    324        duration, this can be returned by using `subsec_nanos`.
    325
    326        Example
    327        -------
    328        ```py
    329        duration = Duration(5, 500_000_000)
    330        assert duration.as_secs() == 5
    331        ```
    332        """
    333        return self._secs
    334
    335    def subsec_millis(self) -> float:
    336        """Returns the fractional part of this `Duration` in a whole milliseconds.
    337
    338        Example
    339        -------
    340        ```py
    341        duration = Duration(1, 500_000_000)
    342        assert duration.subsec_millis() == 500.0
    343        ```
    344        """
    345        return self._nanos / NANOS_PER_MILLI
    346
    347    def subsec_micros(self) -> float:
    348        """Returns the fractional part of this `Duration` in a whole microseconds.
    349
    350        Example
    351        -------
    352        ```py
    353        duration = Duration(1, 500_000)
    354        assert duration.subsec_micros() == 500.0
    355        ```
    356        """
    357        return self._nanos / NANOS_PER_MICRO
    358
    359    def subsec_nanos(self) -> int:
    360        """Returns the fractional part of this `Duration` in a whole nanoseconds.
    361
    362        Example
    363        -------
    364        ```py
    365        duration = Duration.from_millis(5_010)
    366        assert duration.subsec_nanos() == 10_000_000
    367        ```
    368        """
    369        return self._nanos
    370
    371    def as_millis(self) -> float:
    372        """Returns the total duration in milliseconds as a floating-point number.
    373
    374        This includes both the whole seconds and the fractional nanoseconds
    375        converted into milliseconds.
    376
    377        Example
    378        -------
    379        ```py
    380        duration = Duration(1, 500_000_000)
    381        assert duration.as_millis() == 1500.0
    382        ```
    383        """
    384        return self._secs * MILLIS_PER_SEC + (self._nanos / NANOS_PER_MILLI)
    385
    386    def as_micros(self) -> float:
    387        """Returns the total duration in microseconds as a floating-point number.
    388
    389        This includes both the whole seconds and the fractional nanoseconds
    390        converted into microseconds.
    391
    392        Example
    393        -------
    394        ```py
    395        duration = Duration(1, 500_000)
    396        assert duration.as_micros() == 1_000_500.0
    397        ```
    398        """
    399        return self._secs * MICROS_PER_SEC + (self._nanos / NANOS_PER_MICRO)
    400
    401    def as_nanos(self) -> int:
    402        """Returns the total duration in nanoseconds as an integer.
    403
    404        This includes both the whole seconds and the fractional nanoseconds.
    405
    406        Example
    407        -------
    408        ```py
    409        duration = Duration(1, 500_000_000)
    410        assert duration.as_nanos() == 1_500_000_000
    411        ```
    412        """
    413        return self._secs * NANOS_PER_SEC + self._nanos
    414
    415    def __add__(self, rhs: Duration) -> Duration:
    416        if type(rhs) is not Duration:
    417            raise TypeError("rhs must be of type `Duration`")
    418
    419        secs = self._secs + rhs._secs
    420        nanos = self._nanos + rhs._nanos
    421        return Duration(secs, nanos)
    422
    423    def __sub__(self, rhs: Duration) -> Duration:
    424        if type(rhs) is not Duration:
    425            raise TypeError("rhs must be of type `Duration`")
    426
    427        if self._nanos >= rhs._nanos:
    428            return Duration(self._secs - rhs._secs, self._nanos - rhs._nanos)
    429        return Duration(
    430            self._secs - rhs._secs - 1, NANOS_PER_SEC + self._nanos - rhs._nanos
    431        )
    432
    433    def __mul__(self, rhs: Duration) -> Duration:
    434        if type(rhs) is not Duration:
    435            raise TypeError("rhs must be of type `Duration`")
    436
    437        total_nanos = (self._secs * NANOS_PER_SEC + self._nanos) * (
    438            rhs._secs * NANOS_PER_SEC + rhs._nanos
    439        )
    440        secs = total_nanos // NANOS_PER_SEC
    441        nanos = total_nanos % NANOS_PER_SEC
    442        return Duration(secs, nanos)
    443
    444    def __iadd__(self, rhs: Duration) -> Duration:
    445        if type(rhs) is not Duration:
    446            raise TypeError("rhs must be of type `Duration`")
    447
    448        self._secs += rhs._secs
    449        self._nanos += rhs._nanos
    450        if self._nanos >= NANOS_PER_SEC:
    451            self._secs += 1
    452            self._nanos -= NANOS_PER_SEC
    453        return self
    454
    455    def __isub__(self, rhs: Duration) -> Duration:
    456        if type(rhs) is not Duration:
    457            raise TypeError("rhs must be of type `Duration`")
    458
    459        if self._nanos >= rhs._nanos:
    460            self._nanos -= rhs._nanos
    461            self._secs -= rhs._secs
    462        else:
    463            self._nanos = NANOS_PER_SEC + self._nanos - rhs._nanos
    464            self._secs = self._secs - rhs._secs - 1
    465        return self
    466
    467    def __imul__(self, rhs: Duration) -> Duration:
    468        if type(rhs) is not Duration:
    469            raise TypeError("rhs must be of type `Duration`")
    470        total_nanos = (self._secs * NANOS_PER_SEC + self._nanos) * (
    471            rhs._secs * NANOS_PER_SEC + rhs._nanos
    472        )
    473        self._secs = total_nanos // NANOS_PER_SEC
    474        self._nanos = total_nanos % NANOS_PER_SEC
    475        return self
    476
    477    def __truediv__(self, rhs: Duration) -> float:
    478        if type(rhs) is not Duration:
    479            raise TypeError("rhs must be of type `Duration`")
    480        self_nanos = self._secs * NANOS_PER_SEC + self._nanos
    481        rhs_nanos = rhs._secs * NANOS_PER_SEC + rhs._nanos
    482        if rhs_nanos == 0:
    483            raise ZeroDivisionError("division by zero")
    484        return self_nanos / rhs_nanos
    485
    486    def __itruediv__(self, rhs: Duration) -> float:
    487        if type(rhs) is not Duration:
    488            raise TypeError("rhs must be of type `Duration`")
    489
    490        return self.__truediv__(rhs)
    491
    492    def __repr__(self) -> str:
    493        return f"Duration(secs={self._secs}, nanos={self._nanos})"

A `Duration` type, used to represent a span of time, usually used for system timeouts.

Each `Duration` is composed of a number of seconds and a fractional part represented as `nanoseconds`.

It includes core methods inherited from `std::time::Duration`.

`Duration` implement many common operators such as `+`, `-`, `*` and many more.

###### Example

    five_seconds = Duration(5, 0)
    ten_seconds = five_seconds * Duration(2, 0)
    assert ten_seconds.as_secs() == 10

    ten_milli_secs = Duration.from_millis(10)

Duration(secs: int, nanos: int, /) View Source

    154    def __init__(self, secs: int, nanos: int, /) -> None:
    155        self._secs = secs
    156        self._nanos = nanos

SECOND: ClassVar[Duration] = Duration(secs=1, nanos=0)

The duration of one second.

###### Example

    from sain.time import Duration

    assert Duration.SECOND, Duration.from_secs(1)

MILLISECOND: ClassVar[Duration] = Duration(secs=0, nanos=1000000)

The duration of one millisecond.

###### Example

    from sain.time import Duration

    assert Duration.MILLISECOND == Duration.from_millis(1)

MICROSECOND: ClassVar[Duration] = Duration(secs=0, nanos=1000)

The duration of one microsecond.

###### Example

    from sain.time import Duration

    assert Duration.MICROSECOND == Duration.from_micros(1)

NANOSECOND: ClassVar[Duration] = Duration(secs=0, nanos=1)

The duration of one nanosecond.

###### Example

    from sain.time import Duration

    assert Duration.NANOSECOND == Duration.from_nanos(1)

ZERO: ClassVar[Duration] = Duration(secs=0, nanos=0)

The zero duration.

###### Example

    from sain.time import Duration

    assert Duration.ZERO == Duration(0, 0)

MAX: ClassVar[Duration] = Duration(secs=9223372036854775807, nanos=999999999)

The maximum possible duration.

###### Example

    from sain.time import Duration

    assert Duration.MAX.as_secs() == sys.maxsize

@classmethod
def from_timedelta(cls, delta: datetime.timedelta) -> Duration: View Source

    176    @classmethod
    177    def from_timedelta(cls, delta: datetime.timedelta) -> Duration:
    178        """Creates a `Duration` new from a `timedelta`.
    179
    180        Example
    181        -------
    182        ```py
    183        duration = Duration.from_timedelta(datetime.timedelta(minutes=1, seconds=30))
    184        assert duration.as_secs() == 90
    185        ```
    186        """
    187        nanos = int(delta.total_seconds() * NANOS_PER_SEC)
    188        return Duration.from_nanos(nanos)

Creates a `Duration` new from a `timedelta`.

###### Example

    duration = Duration.from_timedelta(datetime.timedelta(minutes=1, seconds=30))
    assert duration.as_secs() == 90

@classmethod
def from_secs(cls, secs: int) -> Duration: View Source

    190    @classmethod
    191    def from_secs(cls, secs: int) -> Duration:
    192        """Creates a `Duration` new representing the given number of seconds.
    193
    194        Example
    195        -------
    196        ```py
    197        duration = Duration.from_secs(10)
    198        assert duration.as_secs() == 10
    199        ```
    200        """
    201        return Duration(secs, 0)

Creates a `Duration` new representing the given number of seconds.

###### Example

    duration = Duration.from_secs(10)
    assert duration.as_secs() == 10

@classmethod
def from_millis(cls, n: int) -> Duration: View Source

    203    @classmethod
    204    def from_millis(cls, n: int) -> Duration:
    205        """Creates a `Duration` new representing the given number of milliseconds.
    206
    207        Example
    208        -------
    209        ```py
    210        duration = Duration.from_millis(1500)
    211        assert duration.as_secs() == 1
    212        assert duration.subsec_millis() == 500
    213        ```
    214        """
    215        secs = n // MILLIS_PER_SEC
    216        nanos = (n % MILLIS_PER_SEC) * NANOS_PER_MILLI
    217        return cls(secs, nanos)

Creates a `Duration` new representing the given number of milliseconds.

###### Example

    duration = Duration.from_millis(1500)
    assert duration.as_secs() == 1
    assert duration.subsec_millis() == 500

@classmethod
def from_micros(cls, n: int) -> Duration: View Source

    219    @classmethod
    220    def from_micros(cls, n: int) -> Duration:
    221        """Creates a `Duration` new representing the given number of microseconds.
    222
    223        Example
    224        -------
    225        ```py
    226        duration = Duration.from_micros(1_500_000)
    227        assert duration.as_secs() == 1
    228        assert duration.subsec_micros() == 500_000
    229        ```
    230        """
    231        secs = n // MICROS_PER_SEC
    232        nanos = (n % MICROS_PER_SEC) * NANOS_PER_MICRO
    233        return cls(secs, nanos)

Creates a `Duration` new representing the given number of microseconds.

###### Example

    duration = Duration.from_micros(1_500_000)
    assert duration.as_secs() == 1
    assert duration.subsec_micros() == 500_000

@classmethod
def from_nanos(cls, n: int) -> Duration: View Source

    235    @classmethod
    236    def from_nanos(cls, n: int) -> Duration:
    237        """Creates a `Duration` new representing the given number of nanoseconds.
    238
    239        Example
    240        -------
    241        ```py
    242        duration = Duration.from_nanos(1_500_000_000)
    243        assert duration.as_secs() == 1
    244        assert duration.subsec_nanos() == 500_000_000
    245        ```
    246        """
    247        secs = n // NANOS_PER_SEC
    248        nanos = n % NANOS_PER_SEC
    249        return cls(secs, nanos)

Creates a `Duration` new representing the given number of nanoseconds.

###### Example

    duration = Duration.from_nanos(1_500_000_000)
    assert duration.as_secs() == 1
    assert duration.subsec_nanos() == 500_000_000

@classmethod
def from_weeks(cls, n: int) -> Duration: View Source

    251    @classmethod
    252    def from_weeks(cls, n: int) -> Duration:
    253        """Creates a `Duration` new representing the given number of weeks.
    254
    255        Example
    256        -------
    257        ```py
    258        duration = Duration.from_weeks(2)
    259        assert duration.as_secs() == 2 * 7 * 24 * 60 * 60
    260        ```
    261        """
    262        return cls(
    263            n * DAYS_PER_WEEK * HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MINUTE, 0
    264        )

Creates a `Duration` new representing the given number of weeks.

###### Example

    duration = Duration.from_weeks(2)
    assert duration.as_secs() == 2 * 7 * 24 * 60 * 60

@classmethod
def from_days(cls, n: int) -> Duration: View Source

    266    @classmethod
    267    def from_days(cls, n: int) -> Duration:
    268        """Creates a `Duration` new representing the given number of days.
    269
    270        Example
    271        -------
    272        ```py
    273        duration = Duration.from_days(3)
    274        assert duration.as_secs() == 3 * 24 * 60 * 60
    275        ```
    276        """
    277        return cls(n * HOURS_PER_DAY * MINS_PER_HOUR * SECS_PER_MINUTE, 0)

Creates a `Duration` new representing the given number of days.

###### Example

    duration = Duration.from_days(3)
    assert duration.as_secs() == 3 * 24 * 60 * 60

@classmethod
def from_hours(cls, n: int) -> Duration: View Source

    279    @classmethod
    280    def from_hours(cls, n: int) -> Duration:
    281        """Creates a `Duration` new representing the given number of hours.
    282
    283        Example
    284        -------
    285        ```py
    286        duration = Duration.from_hours(5)
    287        assert duration.as_secs() == 5 * 60 * 60
    288        ```
    289        """
    290        return cls(n * MINS_PER_HOUR * SECS_PER_MINUTE, 0)

Creates a `Duration` new representing the given number of hours.

###### Example

    duration = Duration.from_hours(5)
    assert duration.as_secs() == 5 * 60 * 60

@classmethod
def from_mins(cls, n: int) -> Duration: View Source

    292    @classmethod
    293    def from_mins(cls, n: int) -> Duration:
    294        """Creates a `Duration` new representing the given number of minutes.
    295
    296        Example
    297        -------
    298        ```py
    299        duration = Duration.from_mins(3)
    300        assert duration.as_secs() == 3 * 60
    301        ```
    302        """
    303        return cls(n * SECS_PER_MINUTE, 0)

Creates a `Duration` new representing the given number of minutes.

###### Example

    duration = Duration.from_mins(3)
    assert duration.as_secs() == 3 * 60

def is_zero(self) -> bool: View Source

    305    def is_zero(self) -> bool:
    306        """Returns True if the duration is zero (both seconds and nanoseconds are zero).
    307
    308        Example
    309        -------
    310        ```py
    311        duration = Duration(0, 0)
    312        assert duration.is_zero() is True
    313
    314        duration = Duration(1, 0)
    315        assert duration.is_zero() is False
    316        ```
    317        """
    318        return self._secs == 0 and self._nanos == 0

Returns True if the duration is zero (both seconds and nanoseconds are zero).

###### Example

    duration = Duration(0, 0)
    assert duration.is_zero() is True

    duration = Duration(1, 0)
    assert duration.is_zero() is False

def as_secs(self) -> int: View Source

    320    def as_secs(self) -> int:
    321        """Returns the number of whole seconds in the duration as an integer.
    322
    323        This does not return the included fractional (nanosecond) part of the
    324        duration, this can be returned by using `subsec_nanos`.
    325
    326        Example
    327        -------
    328        ```py
    329        duration = Duration(5, 500_000_000)
    330        assert duration.as_secs() == 5
    331        ```
    332        """
    333        return self._secs

Returns the number of whole seconds in the duration as an integer.

This does not return the included fractional (nanosecond) part of the duration, this can be returned by using `subsec_nanos`.

###### Example

    duration = Duration(5, 500_000_000)
    assert duration.as_secs() == 5

def subsec_millis(self) -> float: View Source

    335    def subsec_millis(self) -> float:
    336        """Returns the fractional part of this `Duration` in a whole milliseconds.
    337
    338        Example
    339        -------
    340        ```py
    341        duration = Duration(1, 500_000_000)
    342        assert duration.subsec_millis() == 500.0
    343        ```
    344        """
    345        return self._nanos / NANOS_PER_MILLI

Returns the fractional part of this `Duration` in a whole milliseconds.

###### Example

    duration = Duration(1, 500_000_000)
    assert duration.subsec_millis() == 500.0

def subsec_micros(self) -> float: View Source

    347    def subsec_micros(self) -> float:
    348        """Returns the fractional part of this `Duration` in a whole microseconds.
    349
    350        Example
    351        -------
    352        ```py
    353        duration = Duration(1, 500_000)
    354        assert duration.subsec_micros() == 500.0
    355        ```
    356        """
    357        return self._nanos / NANOS_PER_MICRO

Returns the fractional part of this `Duration` in a whole microseconds.

###### Example

    duration = Duration(1, 500_000)
    assert duration.subsec_micros() == 500.0

def subsec_nanos(self) -> int: View Source

    359    def subsec_nanos(self) -> int:
    360        """Returns the fractional part of this `Duration` in a whole nanoseconds.
    361
    362        Example
    363        -------
    364        ```py
    365        duration = Duration.from_millis(5_010)
    366        assert duration.subsec_nanos() == 10_000_000
    367        ```
    368        """
    369        return self._nanos

Returns the fractional part of this `Duration` in a whole nanoseconds.

###### Example

    duration = Duration.from_millis(5_010)
    assert duration.subsec_nanos() == 10_000_000

def as_millis(self) -> float: View Source

    371    def as_millis(self) -> float:
    372        """Returns the total duration in milliseconds as a floating-point number.
    373
    374        This includes both the whole seconds and the fractional nanoseconds
    375        converted into milliseconds.
    376
    377        Example
    378        -------
    379        ```py
    380        duration = Duration(1, 500_000_000)
    381        assert duration.as_millis() == 1500.0
    382        ```
    383        """
    384        return self._secs * MILLIS_PER_SEC + (self._nanos / NANOS_PER_MILLI)

Returns the total duration in milliseconds as a floating-point number.

This includes both the whole seconds and the fractional nanoseconds converted into milliseconds.

###### Example

    duration = Duration(1, 500_000_000)
    assert duration.as_millis() == 1500.0

def as_micros(self) -> float: View Source

    386    def as_micros(self) -> float:
    387        """Returns the total duration in microseconds as a floating-point number.
    388
    389        This includes both the whole seconds and the fractional nanoseconds
    390        converted into microseconds.
    391
    392        Example
    393        -------
    394        ```py
    395        duration = Duration(1, 500_000)
    396        assert duration.as_micros() == 1_000_500.0
    397        ```
    398        """
    399        return self._secs * MICROS_PER_SEC + (self._nanos / NANOS_PER_MICRO)

Returns the total duration in microseconds as a floating-point number.

This includes both the whole seconds and the fractional nanoseconds converted into microseconds.

###### Example

    duration = Duration(1, 500_000)
    assert duration.as_micros() == 1_000_500.0

def as_nanos(self) -> int: View Source

    401    def as_nanos(self) -> int:
    402        """Returns the total duration in nanoseconds as an integer.
    403
    404        This includes both the whole seconds and the fractional nanoseconds.
    405
    406        Example
    407        -------
    408        ```py
    409        duration = Duration(1, 500_000_000)
    410        assert duration.as_nanos() == 1_500_000_000
    411        ```
    412        """
    413        return self._secs * NANOS_PER_SEC + self._nanos

Returns the total duration in nanoseconds as an integer.

This includes both the whole seconds and the fractional nanoseconds.

###### Example

    duration = Duration(1, 500_000_000)
    assert duration.as_nanos() == 1_500_000_000