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)
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)
The duration of one second.
Example
from sain.time import Duration
assert Duration.SECOND, Duration.from_secs(1)
The duration of one millisecond.
Example
from sain.time import Duration
assert Duration.MILLISECOND == Duration.from_millis(1)
The duration of one microsecond.
Example
from sain.time import Duration
assert Duration.MICROSECOND == Duration.from_micros(1)
The duration of one nanosecond.
Example
from sain.time import Duration
assert Duration.NANOSECOND == Duration.from_nanos(1)
The maximum possible duration.
Example
from sain.time import Duration
assert Duration.MAX.as_secs() == sys.maxsize
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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