sain.time

Temporal quantification

  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:
 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, /)
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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