workclock 0.1.0__cp39-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- workclock/__init__.py +23 -0
- workclock/_arrays.py +84 -0
- workclock/_errors.py +20 -0
- workclock/_schedule.py +620 -0
- workclock/_workclock.pyd +0 -0
- workclock/_workclock.pyi +34 -0
- workclock/_zones.py +26 -0
- workclock/compat.py +494 -0
- workclock/py.typed +0 -0
- workclock-0.1.0.dist-info/METADATA +239 -0
- workclock-0.1.0.dist-info/RECORD +15 -0
- workclock-0.1.0.dist-info/WHEEL +4 -0
- workclock-0.1.0.dist-info/licenses/LICENSE +21 -0
- workclock-0.1.0.dist-info/licenses/NOTICE +28 -0
- workclock-0.1.0.dist-info/sboms/workclock-py.cyclonedx.json +1139 -0
workclock/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Fast, correct business-time arithmetic: working hours, SLAs and shifts."""
|
|
2
|
+
|
|
3
|
+
from workclock._errors import (
|
|
4
|
+
NoWorkingTimeError,
|
|
5
|
+
OutOfRangeError,
|
|
6
|
+
UnknownZoneError,
|
|
7
|
+
WorkclockError,
|
|
8
|
+
)
|
|
9
|
+
from workclock._schedule import Break, Holidays, Rule, Schedule, WorkDay
|
|
10
|
+
from workclock._workclock import __version__
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Break",
|
|
14
|
+
"Holidays",
|
|
15
|
+
"NoWorkingTimeError",
|
|
16
|
+
"OutOfRangeError",
|
|
17
|
+
"Rule",
|
|
18
|
+
"Schedule",
|
|
19
|
+
"UnknownZoneError",
|
|
20
|
+
"WorkDay",
|
|
21
|
+
"WorkclockError",
|
|
22
|
+
"__version__",
|
|
23
|
+
]
|
workclock/_arrays.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""NumPy support for the batch API, without depending on NumPy.
|
|
2
|
+
|
|
3
|
+
NumPy is imported only when an array is passed in. ``datetime64`` values are
|
|
4
|
+
naive, so they are read and returned as UTC.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import numpy as np
|
|
13
|
+
from numpy.typing import NDArray
|
|
14
|
+
from typing_extensions import TypeGuard
|
|
15
|
+
|
|
16
|
+
_FINER_UNITS = ("ns", "ps", "fs", "as")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_array(value: object) -> TypeGuard[NDArray[Any]]:
|
|
20
|
+
"""Return whether ``value`` is a NumPy array."""
|
|
21
|
+
kind = type(value)
|
|
22
|
+
return kind.__module__ == "numpy" and kind.__name__ == "ndarray"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def broadcast_shape(a: object, b: object) -> tuple[int, ...]:
|
|
26
|
+
"""Return the shape that ``a`` and ``b`` broadcast to under NumPy's rules.
|
|
27
|
+
|
|
28
|
+
Raises ``ValueError`` naming both shapes if they do not broadcast.
|
|
29
|
+
"""
|
|
30
|
+
import numpy as np
|
|
31
|
+
|
|
32
|
+
shapes = np.asarray(a).shape, np.asarray(b).shape
|
|
33
|
+
try:
|
|
34
|
+
return np.broadcast_shapes(*shapes)
|
|
35
|
+
except ValueError:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"batch inputs of shapes {shapes[0]} and {shapes[1]} do not "
|
|
38
|
+
"broadcast together"
|
|
39
|
+
) from None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def to_micros(values: object, unit: str, shape: tuple[int, ...]) -> list[int]:
|
|
43
|
+
"""Return ``values`` as integer microseconds, broadcast to ``shape``.
|
|
44
|
+
|
|
45
|
+
Raises ``TypeError`` for numbers, which carry no unit, ``ValueError`` for
|
|
46
|
+
NaT, and for values with digits below a microsecond, which would otherwise
|
|
47
|
+
be rounded.
|
|
48
|
+
"""
|
|
49
|
+
import numpy as np
|
|
50
|
+
|
|
51
|
+
given = np.asarray(values)
|
|
52
|
+
if given.dtype.kind in "biufc":
|
|
53
|
+
raise TypeError(f"expected {unit} values, got {given.dtype} numbers")
|
|
54
|
+
converted: NDArray[Any] = np.asarray(values, dtype=f"{unit}[us]")
|
|
55
|
+
array = np.broadcast_to(converted, shape)
|
|
56
|
+
nat = np.flatnonzero(np.isnat(array))
|
|
57
|
+
if nat.size:
|
|
58
|
+
raise ValueError(f"item {nat[0]}: batch inputs must not contain NaT")
|
|
59
|
+
finer = (
|
|
60
|
+
given.dtype.kind in "mM" and np.datetime_data(given.dtype)[0] in _FINER_UNITS
|
|
61
|
+
)
|
|
62
|
+
if finer:
|
|
63
|
+
lossy = np.broadcast_to(converted.astype(given.dtype) != given, shape)
|
|
64
|
+
if lossy.any():
|
|
65
|
+
i = int(np.flatnonzero(lossy)[0])
|
|
66
|
+
value = np.broadcast_to(given, shape).flat[i]
|
|
67
|
+
raise ValueError(f"item {i}: {value!r} has digits below a microsecond")
|
|
68
|
+
result: list[int] = array.astype(np.int64).ravel().tolist()
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def item(values: object, shape: tuple[int, ...], i: int) -> object:
|
|
73
|
+
"""Return item ``i`` of ``values`` broadcast to ``shape`` and flattened."""
|
|
74
|
+
import numpy as np
|
|
75
|
+
|
|
76
|
+
return np.broadcast_to(np.asarray(values), shape).flat[i]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def from_micros(micros: list[int], unit: str, shape: tuple[int, ...]) -> NDArray[Any]:
|
|
80
|
+
"""Return integer microseconds as an array of ``unit`` with ``shape``."""
|
|
81
|
+
import numpy as np
|
|
82
|
+
|
|
83
|
+
array: NDArray[np.int64] = np.array(micros, dtype=np.int64).reshape(shape)
|
|
84
|
+
return array.astype(f"{unit}[us]")
|
workclock/_errors.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Exceptions raised by workclock."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WorkclockError(Exception):
|
|
5
|
+
"""Base class for workclock errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NoWorkingTimeError(WorkclockError, ValueError):
|
|
9
|
+
"""The schedule has no working time, or a search found none within its bound.
|
|
10
|
+
|
|
11
|
+
The bound is 366 days, plus seven days per holiday date.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OutOfRangeError(WorkclockError, OverflowError):
|
|
16
|
+
"""An instant, amount or result is outside the supported range."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnknownZoneError(WorkclockError, ValueError):
|
|
20
|
+
"""A time zone name is not in the IANA time zone database."""
|