stdl 0.6.7__tar.gz → 0.6.8__tar.gz
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.
- {stdl-0.6.7 → stdl-0.6.8}/PKG-INFO +5 -2
- {stdl-0.6.7 → stdl-0.6.8}/mkdocs.yml +0 -1
- {stdl-0.6.7 → stdl-0.6.8}/pyproject.toml +12 -13
- {stdl-0.6.7 → stdl-0.6.8}/stdl/dt.py +35 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/fs.py +3 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/log.py +9 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/net.py +8 -0
- stdl-0.6.8/tests/test_dt.py +231 -0
- stdl-0.6.7/tests/test_dt.py +0 -109
- {stdl-0.6.7 → stdl-0.6.8}/.editorconfig +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.github/workflows/lint.yml +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.github/workflows/publish.yml +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.github/workflows/test.yml +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.gitignore +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.pre-commit-config.yaml +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.python-version +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/.readthedocs.yml +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/CONTRIBUTING.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/Justfile +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/LICENSE +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/README.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/index.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/color.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/decorators.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/dt.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/fs.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/import_lazy.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/log.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/net.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/reference/st.md +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/docs/requirements.txt +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/__init__.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/color.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/decorators.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/import_lazy.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/lst.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/stdl/st.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/tests/test_color.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/tests/test_decorators.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/tests/test_fs.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/tests/test_lst.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/tests/test_pydantic_integration.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/tests/test_st.py +0 -0
- {stdl-0.6.7 → stdl-0.6.8}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stdl
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: Extended Python Standard Library
|
|
5
5
|
Project-URL: Repository, https://github.com/zigai/stdl
|
|
6
6
|
Project-URL: Issues, https://github.com/zigai/stdl/issues
|
|
@@ -28,7 +28,7 @@ License: MIT License
|
|
|
28
28
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
29
|
SOFTWARE.
|
|
30
30
|
License-File: LICENSE
|
|
31
|
-
Keywords:
|
|
31
|
+
Keywords: ansi-colors,color-formats,filesystem,lazy-import,list-utils,python-utilities,standard-library,stdl,terminal-links,text-processing
|
|
32
32
|
Classifier: Intended Audience :: Developers
|
|
33
33
|
Classifier: License :: OSI Approved :: MIT License
|
|
34
34
|
Classifier: Operating System :: OS Independent
|
|
@@ -41,6 +41,9 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
41
41
|
Classifier: Programming Language :: Python :: 3.13
|
|
42
42
|
Classifier: Programming Language :: Python :: 3.14
|
|
43
43
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
+
Classifier: Topic :: System :: Filesystems
|
|
45
|
+
Classifier: Topic :: Text Processing :: General
|
|
46
|
+
Classifier: Topic :: Utilities
|
|
44
47
|
Classifier: Typing :: Typed
|
|
45
48
|
Requires-Python: >=3.10
|
|
46
49
|
Requires-Dist: python-dateutil>=2.8.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stdl"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.8"
|
|
4
4
|
description = "Extended Python Standard Library"
|
|
5
5
|
authors = [{ name = "Žiga Ivanšek", email = "ziga.ivansek@gmail.com" }]
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -19,6 +19,9 @@ classifiers = [
|
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
"Intended Audience :: Developers",
|
|
21
21
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Topic :: Utilities",
|
|
23
|
+
"Topic :: System :: Filesystems",
|
|
24
|
+
"Topic :: Text Processing :: General",
|
|
22
25
|
"Programming Language :: Python",
|
|
23
26
|
"Programming Language :: Python :: 3",
|
|
24
27
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -31,21 +34,17 @@ classifiers = [
|
|
|
31
34
|
|
|
32
35
|
keywords = [
|
|
33
36
|
"stdl",
|
|
34
|
-
"standard
|
|
35
|
-
"python
|
|
37
|
+
"standard-library",
|
|
38
|
+
"python-utilities",
|
|
36
39
|
"filesystem",
|
|
37
|
-
"list
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"terminal links",
|
|
44
|
-
"lazy import",
|
|
45
|
-
"python lazy import",
|
|
40
|
+
"list-utils",
|
|
41
|
+
"ansi-colors",
|
|
42
|
+
"color-formats",
|
|
43
|
+
"terminal-links",
|
|
44
|
+
"lazy-import",
|
|
45
|
+
"text-processing",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
|
-
|
|
49
48
|
[project.optional-dependencies]
|
|
50
49
|
full = ["tqdm>=4.67.1"]
|
|
51
50
|
dev = [
|
|
@@ -233,6 +233,40 @@ def date_range(start: date, end: date) -> Generator[date, None, None]:
|
|
|
233
233
|
yield start + timedelta(n)
|
|
234
234
|
|
|
235
235
|
|
|
236
|
+
def datetime_range(
|
|
237
|
+
start: datetime,
|
|
238
|
+
end: datetime,
|
|
239
|
+
step: timedelta,
|
|
240
|
+
) -> Generator[datetime, None, None]:
|
|
241
|
+
"""
|
|
242
|
+
Returns a generator for datetimes between ``start`` and ``end`` with a given ``step``.
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
```python
|
|
246
|
+
>>> for dt in datetime_range(
|
|
247
|
+
... datetime(2026, 1, 27, 0, 0),
|
|
248
|
+
... datetime(2026, 1, 27, 3, 0),
|
|
249
|
+
... timedelta(hours=1)
|
|
250
|
+
... ):
|
|
251
|
+
... print(dt)
|
|
252
|
+
2026-01-27 00:00:00
|
|
253
|
+
2026-01-27 01:00:00
|
|
254
|
+
2026-01-27 02:00:00
|
|
255
|
+
```
|
|
256
|
+
"""
|
|
257
|
+
current = start
|
|
258
|
+
if step.total_seconds() == 0:
|
|
259
|
+
raise ValueError("step cannot be zero")
|
|
260
|
+
if step.total_seconds() > 0:
|
|
261
|
+
while current < end:
|
|
262
|
+
yield current
|
|
263
|
+
current += step
|
|
264
|
+
else:
|
|
265
|
+
while current > end:
|
|
266
|
+
yield current
|
|
267
|
+
current += step
|
|
268
|
+
|
|
269
|
+
|
|
236
270
|
def sleep(lo: float, hi: float | None = None) -> float:
|
|
237
271
|
"""
|
|
238
272
|
Sleeps for a random duration within a specified range.
|
|
@@ -346,6 +380,7 @@ __all__ = [
|
|
|
346
380
|
"time_fmt",
|
|
347
381
|
"date_fmt",
|
|
348
382
|
"date_range",
|
|
383
|
+
"datetime_range",
|
|
349
384
|
"sleep",
|
|
350
385
|
"local_timezone",
|
|
351
386
|
"timezone",
|
|
@@ -1059,6 +1059,7 @@ class File(PathBase):
|
|
|
1059
1059
|
|
|
1060
1060
|
@property
|
|
1061
1061
|
def exists(self) -> bool:
|
|
1062
|
+
"""Whether the file exists."""
|
|
1062
1063
|
return os.path.isfile(self.path)
|
|
1063
1064
|
|
|
1064
1065
|
@property
|
|
@@ -1409,6 +1410,7 @@ class Directory(PathBase):
|
|
|
1409
1410
|
|
|
1410
1411
|
@property
|
|
1411
1412
|
def size(self) -> int:
|
|
1413
|
+
"""Total size of the directory and all its contents in bytes."""
|
|
1412
1414
|
return get_dir_size(self.path, readable=False) # type:ignore
|
|
1413
1415
|
|
|
1414
1416
|
@property
|
|
@@ -1423,6 +1425,7 @@ class Directory(PathBase):
|
|
|
1423
1425
|
|
|
1424
1426
|
@property
|
|
1425
1427
|
def parent(self) -> Directory:
|
|
1428
|
+
"""The parent directory."""
|
|
1426
1429
|
return Directory(os.path.dirname(self.path))
|
|
1427
1430
|
|
|
1428
1431
|
@classmethod
|
|
@@ -130,6 +130,15 @@ def br(
|
|
|
130
130
|
*,
|
|
131
131
|
newline: bool = False,
|
|
132
132
|
) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Print a horizontal line break across the terminal.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
c (str): Character to repeat. Defaults to "_".
|
|
138
|
+
length (int | None): Line length. Defaults to terminal width.
|
|
139
|
+
handler (Callable[[str], None]): Function to output the line. Defaults to print.
|
|
140
|
+
newline (bool): Whether to append a newline. Defaults to False.
|
|
141
|
+
"""
|
|
133
142
|
if IGNORE_BREAKS:
|
|
134
143
|
return
|
|
135
144
|
length = length or get_terminal_size().columns
|
|
@@ -22,6 +22,14 @@ else:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class DownloadSizeExceededError(Exception):
|
|
25
|
+
"""
|
|
26
|
+
Exception raised when a file download exceeds the maximum allowed size.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
filesize (int): The actual file size in bytes.
|
|
30
|
+
maxsize (int): The maximum allowed size in bytes.
|
|
31
|
+
"""
|
|
32
|
+
|
|
25
33
|
def __init__(self, filesize: int, maxsize: int):
|
|
26
34
|
self.filesize = filesize
|
|
27
35
|
self.maxsize = maxsize
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
from datetime import date, datetime, timedelta
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from stdl.dt import date_fmt, datetime_fmt, datetime_range, hms_to_seconds, seconds_to_hms, time_fmt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.parametrize(
|
|
10
|
+
"time_input, ms, expected_output",
|
|
11
|
+
[
|
|
12
|
+
(123.456, False, "00:02:03"),
|
|
13
|
+
(123.456, True, "00:02:03.456"),
|
|
14
|
+
(123, False, "00:02:03"),
|
|
15
|
+
(123.0, True, "00:02:03.000"),
|
|
16
|
+
(-123.456, False, "-00:02:03"),
|
|
17
|
+
(-123.456, True, "-00:02:03.456"),
|
|
18
|
+
(-0.5, True, "-00:00:00.500"),
|
|
19
|
+
(0, False, "00:00:00"),
|
|
20
|
+
(0.0, True, "00:00:00.000"),
|
|
21
|
+
(3600, False, "01:00:00"),
|
|
22
|
+
(3661.5, True, "01:01:01.500"),
|
|
23
|
+
(90321.789, False, "25:05:21"),
|
|
24
|
+
(90321.789, True, "25:05:21.789"),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_seconds_to_hms(time_input: float, ms: bool, expected_output: Type[Exception]):
|
|
28
|
+
assert seconds_to_hms(time_input, ms) == expected_output
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"time_input, ms, expected_output, expected_exception",
|
|
33
|
+
[
|
|
34
|
+
("00:02:03", False, 123.0, None),
|
|
35
|
+
("1:02:03", False, 3723.0, None),
|
|
36
|
+
("2:3", False, 123.0, None),
|
|
37
|
+
("0:0:0", False, 0.0, None),
|
|
38
|
+
("123", False, 123.0, None),
|
|
39
|
+
("-1:02:03", False, -3723.0, None), # Negative time
|
|
40
|
+
("00:02:03.456", True, 123.456, None),
|
|
41
|
+
("00:02:03.4", True, 123.4, None),
|
|
42
|
+
("00:02:03.45", True, 123.45, None),
|
|
43
|
+
("00:02:03.045", True, 123.045, None),
|
|
44
|
+
("23:59:59.999", True, 23 * 3600 + 59 * 60 + 59.999, None),
|
|
45
|
+
("12:34.567", True, 12 * 60 + 34.567, None), # Valid m:s.ms format
|
|
46
|
+
("00:00:00", False, 0.0, None),
|
|
47
|
+
("0:0", False, 0.0, None),
|
|
48
|
+
("1:60", False, None, ValueError), # Invalid seconds
|
|
49
|
+
("1:02:60", False, None, ValueError), # Invalid seconds
|
|
50
|
+
("invalid_time", False, None, ValueError),
|
|
51
|
+
("1:2:3:4", False, None, ValueError), # Too many parts
|
|
52
|
+
("12:34:56.789x", True, None, ValueError), # Invalid characters in ms
|
|
53
|
+
],
|
|
54
|
+
)
|
|
55
|
+
def test_hms_to_seconds(
|
|
56
|
+
time_input: str,
|
|
57
|
+
ms: bool,
|
|
58
|
+
expected_output: str,
|
|
59
|
+
expected_exception: Type[Exception],
|
|
60
|
+
):
|
|
61
|
+
if expected_exception:
|
|
62
|
+
with pytest.raises(expected_exception):
|
|
63
|
+
x = hms_to_seconds(time_input, ms)
|
|
64
|
+
print(time_input, ms, x)
|
|
65
|
+
else:
|
|
66
|
+
result = hms_to_seconds(time_input, ms)
|
|
67
|
+
assert result == pytest.approx(expected_output, abs=1e-9)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_documentation_examples():
|
|
71
|
+
assert seconds_to_hms(123.456) == "00:02:03"
|
|
72
|
+
assert seconds_to_hms(123.456, ms=True) == "00:02:03.456"
|
|
73
|
+
assert hms_to_seconds("00:02:03.456", ms=True) == pytest.approx(123.456)
|
|
74
|
+
assert hms_to_seconds("2:3", ms=True) == pytest.approx(123.0)
|
|
75
|
+
assert hms_to_seconds("-00:02:03", ms=False) == -123.0
|
|
76
|
+
assert hms_to_seconds("00:02:03", ms=False) == 123.0
|
|
77
|
+
assert hms_to_seconds("-1:02:03", ms=False) == pytest.approx(-3723.0)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_millisecond_padding():
|
|
81
|
+
assert hms_to_seconds("00:00:00.1", ms=True) == pytest.approx(0.1)
|
|
82
|
+
assert hms_to_seconds("00:00:00.01", ms=True) == pytest.approx(0.01)
|
|
83
|
+
assert hms_to_seconds("00:00:00.001", ms=True) == pytest.approx(0.001)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_negative_time_roundtrip():
|
|
87
|
+
original_seconds = -123.456
|
|
88
|
+
time_str = seconds_to_hms(original_seconds, ms=True)
|
|
89
|
+
parsed_seconds = hms_to_seconds(time_str, ms=True)
|
|
90
|
+
assert parsed_seconds == original_seconds
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_time_fmt():
|
|
94
|
+
assert time_fmt(0) == "00:00:00"
|
|
95
|
+
assert time_fmt(0, ms=True) == "00:00:00.000"
|
|
96
|
+
assert time_fmt(1, ms=True) == "00:00:01.000"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_datetime_fmt():
|
|
100
|
+
assert datetime_fmt(0.0, utc=True) == "1970-01-01 00:00:00"
|
|
101
|
+
assert datetime_fmt(0) == "1970-01-01 00:00:00"
|
|
102
|
+
assert datetime_fmt(0, fmt="dmY") == "01-01-1970 00:00:00"
|
|
103
|
+
assert datetime_fmt(0, fmt="dmY", dsep="/") == "01/01/1970 00:00:00"
|
|
104
|
+
assert datetime_fmt(0, fmt="dmY", dsep="/", tsep=".") == "01/01/1970 00.00.00"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_date_fmt():
|
|
108
|
+
assert date_fmt(date(1970, 1, 1)) == "1970-01-01"
|
|
109
|
+
assert date_fmt(0) == "1970-01-01"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestDatetimeRange:
|
|
113
|
+
def test_basic_positive_step(self):
|
|
114
|
+
"""Test datetime_range with positive step."""
|
|
115
|
+
start = datetime(2026, 1, 27, 0, 0)
|
|
116
|
+
end = datetime(2026, 1, 27, 3, 0)
|
|
117
|
+
step = timedelta(hours=1)
|
|
118
|
+
result = list(datetime_range(start, end, step))
|
|
119
|
+
expected = [
|
|
120
|
+
datetime(2026, 1, 27, 0, 0),
|
|
121
|
+
datetime(2026, 1, 27, 1, 0),
|
|
122
|
+
datetime(2026, 1, 27, 2, 0),
|
|
123
|
+
]
|
|
124
|
+
assert result == expected
|
|
125
|
+
|
|
126
|
+
def test_basic_negative_step(self):
|
|
127
|
+
"""Test datetime_range with negative step (going backwards)."""
|
|
128
|
+
start = datetime(2026, 1, 27, 3, 0)
|
|
129
|
+
end = datetime(2026, 1, 27, 0, 0)
|
|
130
|
+
step = timedelta(hours=-1)
|
|
131
|
+
result = list(datetime_range(start, end, step))
|
|
132
|
+
expected = [
|
|
133
|
+
datetime(2026, 1, 27, 3, 0),
|
|
134
|
+
datetime(2026, 1, 27, 2, 0),
|
|
135
|
+
datetime(2026, 1, 27, 1, 0),
|
|
136
|
+
]
|
|
137
|
+
assert result == expected
|
|
138
|
+
|
|
139
|
+
def test_zero_step_raises(self):
|
|
140
|
+
"""Test that zero step raises ValueError."""
|
|
141
|
+
start = datetime(2026, 1, 27, 0, 0)
|
|
142
|
+
end = datetime(2026, 1, 27, 3, 0)
|
|
143
|
+
step = timedelta(0)
|
|
144
|
+
with pytest.raises(ValueError, match="step cannot be zero"):
|
|
145
|
+
list(datetime_range(start, end, step))
|
|
146
|
+
|
|
147
|
+
def test_empty_range_positive_step(self):
|
|
148
|
+
"""Test when start >= end with positive step."""
|
|
149
|
+
start = datetime(2026, 1, 27, 3, 0)
|
|
150
|
+
end = datetime(2026, 1, 27, 0, 0)
|
|
151
|
+
step = timedelta(hours=1)
|
|
152
|
+
result = list(datetime_range(start, end, step))
|
|
153
|
+
assert result == []
|
|
154
|
+
|
|
155
|
+
def test_empty_range_negative_step(self):
|
|
156
|
+
"""Test when start <= end with negative step."""
|
|
157
|
+
start = datetime(2026, 1, 27, 0, 0)
|
|
158
|
+
end = datetime(2026, 1, 27, 3, 0)
|
|
159
|
+
step = timedelta(hours=-1)
|
|
160
|
+
result = list(datetime_range(start, end, step))
|
|
161
|
+
assert result == []
|
|
162
|
+
|
|
163
|
+
def test_minute_step(self):
|
|
164
|
+
"""Test with minute-level precision."""
|
|
165
|
+
start = datetime(2026, 1, 27, 0, 0)
|
|
166
|
+
end = datetime(2026, 1, 27, 0, 5)
|
|
167
|
+
step = timedelta(minutes=1)
|
|
168
|
+
result = list(datetime_range(start, end, step))
|
|
169
|
+
expected = [
|
|
170
|
+
datetime(2026, 1, 27, 0, 0),
|
|
171
|
+
datetime(2026, 1, 27, 0, 1),
|
|
172
|
+
datetime(2026, 1, 27, 0, 2),
|
|
173
|
+
datetime(2026, 1, 27, 0, 3),
|
|
174
|
+
datetime(2026, 1, 27, 0, 4),
|
|
175
|
+
]
|
|
176
|
+
assert result == expected
|
|
177
|
+
|
|
178
|
+
def test_second_step(self):
|
|
179
|
+
"""Test with second-level precision."""
|
|
180
|
+
start = datetime(2026, 1, 27, 0, 0, 0)
|
|
181
|
+
end = datetime(2026, 1, 27, 0, 0, 3)
|
|
182
|
+
step = timedelta(seconds=1)
|
|
183
|
+
result = list(datetime_range(start, end, step))
|
|
184
|
+
expected = [
|
|
185
|
+
datetime(2026, 1, 27, 0, 0, 0),
|
|
186
|
+
datetime(2026, 1, 27, 0, 0, 1),
|
|
187
|
+
datetime(2026, 1, 27, 0, 0, 2),
|
|
188
|
+
]
|
|
189
|
+
assert result == expected
|
|
190
|
+
|
|
191
|
+
def test_day_step(self):
|
|
192
|
+
"""Test with day-level step across multiple days."""
|
|
193
|
+
start = datetime(2026, 1, 27)
|
|
194
|
+
end = datetime(2026, 1, 30)
|
|
195
|
+
step = timedelta(days=1)
|
|
196
|
+
result = list(datetime_range(start, end, step))
|
|
197
|
+
expected = [
|
|
198
|
+
datetime(2026, 1, 27),
|
|
199
|
+
datetime(2026, 1, 28),
|
|
200
|
+
datetime(2026, 1, 29),
|
|
201
|
+
]
|
|
202
|
+
assert result == expected
|
|
203
|
+
|
|
204
|
+
def test_partial_step(self):
|
|
205
|
+
"""Test when end is not exactly reachable by step."""
|
|
206
|
+
start = datetime(2026, 1, 27, 0, 0)
|
|
207
|
+
end = datetime(2026, 1, 27, 2, 30)
|
|
208
|
+
step = timedelta(hours=1)
|
|
209
|
+
result = list(datetime_range(start, end, step))
|
|
210
|
+
expected = [
|
|
211
|
+
datetime(2026, 1, 27, 0, 0),
|
|
212
|
+
datetime(2026, 1, 27, 1, 0),
|
|
213
|
+
datetime(2026, 1, 27, 2, 0),
|
|
214
|
+
]
|
|
215
|
+
assert result == expected
|
|
216
|
+
|
|
217
|
+
def test_documentation_example(self):
|
|
218
|
+
"""Test the exact example from the docstring."""
|
|
219
|
+
result = list(
|
|
220
|
+
datetime_range(
|
|
221
|
+
datetime(2026, 1, 27, 0, 0),
|
|
222
|
+
datetime(2026, 1, 27, 3, 0),
|
|
223
|
+
timedelta(hours=1),
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
expected = [
|
|
227
|
+
datetime(2026, 1, 27, 0, 0),
|
|
228
|
+
datetime(2026, 1, 27, 1, 0),
|
|
229
|
+
datetime(2026, 1, 27, 2, 0),
|
|
230
|
+
]
|
|
231
|
+
assert result == expected
|
stdl-0.6.7/tests/test_dt.py
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
from datetime import date
|
|
2
|
-
from typing import Type
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
from stdl.dt import date_fmt, datetime_fmt, hms_to_seconds, seconds_to_hms, time_fmt
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@pytest.mark.parametrize(
|
|
10
|
-
"time_input, ms, expected_output",
|
|
11
|
-
[
|
|
12
|
-
(123.456, False, "00:02:03"),
|
|
13
|
-
(123.456, True, "00:02:03.456"),
|
|
14
|
-
(123, False, "00:02:03"),
|
|
15
|
-
(123.0, True, "00:02:03.000"),
|
|
16
|
-
(-123.456, False, "-00:02:03"),
|
|
17
|
-
(-123.456, True, "-00:02:03.456"),
|
|
18
|
-
(-0.5, True, "-00:00:00.500"),
|
|
19
|
-
(0, False, "00:00:00"),
|
|
20
|
-
(0.0, True, "00:00:00.000"),
|
|
21
|
-
(3600, False, "01:00:00"),
|
|
22
|
-
(3661.5, True, "01:01:01.500"),
|
|
23
|
-
(90321.789, False, "25:05:21"),
|
|
24
|
-
(90321.789, True, "25:05:21.789"),
|
|
25
|
-
],
|
|
26
|
-
)
|
|
27
|
-
def test_seconds_to_hms(time_input: float, ms: bool, expected_output: Type[Exception]):
|
|
28
|
-
assert seconds_to_hms(time_input, ms) == expected_output
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@pytest.mark.parametrize(
|
|
32
|
-
"time_input, ms, expected_output, expected_exception",
|
|
33
|
-
[
|
|
34
|
-
("00:02:03", False, 123.0, None),
|
|
35
|
-
("1:02:03", False, 3723.0, None),
|
|
36
|
-
("2:3", False, 123.0, None),
|
|
37
|
-
("0:0:0", False, 0.0, None),
|
|
38
|
-
("123", False, 123.0, None),
|
|
39
|
-
("-1:02:03", False, -3723.0, None), # Negative time
|
|
40
|
-
("00:02:03.456", True, 123.456, None),
|
|
41
|
-
("00:02:03.4", True, 123.4, None),
|
|
42
|
-
("00:02:03.45", True, 123.45, None),
|
|
43
|
-
("00:02:03.045", True, 123.045, None),
|
|
44
|
-
("23:59:59.999", True, 23 * 3600 + 59 * 60 + 59.999, None),
|
|
45
|
-
("12:34.567", True, 12 * 60 + 34.567, None), # Valid m:s.ms format
|
|
46
|
-
("00:00:00", False, 0.0, None),
|
|
47
|
-
("0:0", False, 0.0, None),
|
|
48
|
-
("1:60", False, None, ValueError), # Invalid seconds
|
|
49
|
-
("1:02:60", False, None, ValueError), # Invalid seconds
|
|
50
|
-
("invalid_time", False, None, ValueError),
|
|
51
|
-
("1:2:3:4", False, None, ValueError), # Too many parts
|
|
52
|
-
("12:34:56.789x", True, None, ValueError), # Invalid characters in ms
|
|
53
|
-
],
|
|
54
|
-
)
|
|
55
|
-
def test_hms_to_seconds(
|
|
56
|
-
time_input: str,
|
|
57
|
-
ms: bool,
|
|
58
|
-
expected_output: str,
|
|
59
|
-
expected_exception: Type[Exception],
|
|
60
|
-
):
|
|
61
|
-
if expected_exception:
|
|
62
|
-
with pytest.raises(expected_exception):
|
|
63
|
-
x = hms_to_seconds(time_input, ms)
|
|
64
|
-
print(time_input, ms, x)
|
|
65
|
-
else:
|
|
66
|
-
result = hms_to_seconds(time_input, ms)
|
|
67
|
-
assert result == pytest.approx(expected_output, abs=1e-9)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def test_documentation_examples():
|
|
71
|
-
assert seconds_to_hms(123.456) == "00:02:03"
|
|
72
|
-
assert seconds_to_hms(123.456, ms=True) == "00:02:03.456"
|
|
73
|
-
assert hms_to_seconds("00:02:03.456", ms=True) == pytest.approx(123.456)
|
|
74
|
-
assert hms_to_seconds("2:3", ms=True) == pytest.approx(123.0)
|
|
75
|
-
assert hms_to_seconds("-00:02:03", ms=False) == -123.0
|
|
76
|
-
assert hms_to_seconds("00:02:03", ms=False) == 123.0
|
|
77
|
-
assert hms_to_seconds("-1:02:03", ms=False) == pytest.approx(-3723.0)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_millisecond_padding():
|
|
81
|
-
assert hms_to_seconds("00:00:00.1", ms=True) == pytest.approx(0.1)
|
|
82
|
-
assert hms_to_seconds("00:00:00.01", ms=True) == pytest.approx(0.01)
|
|
83
|
-
assert hms_to_seconds("00:00:00.001", ms=True) == pytest.approx(0.001)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def test_negative_time_roundtrip():
|
|
87
|
-
original_seconds = -123.456
|
|
88
|
-
time_str = seconds_to_hms(original_seconds, ms=True)
|
|
89
|
-
parsed_seconds = hms_to_seconds(time_str, ms=True)
|
|
90
|
-
assert parsed_seconds == original_seconds
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def test_time_fmt():
|
|
94
|
-
assert time_fmt(0) == "00:00:00"
|
|
95
|
-
assert time_fmt(0, ms=True) == "00:00:00.000"
|
|
96
|
-
assert time_fmt(1, ms=True) == "00:00:01.000"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def test_datetime_fmt():
|
|
100
|
-
assert datetime_fmt(0.0, utc=True) == "1970-01-01 00:00:00"
|
|
101
|
-
assert datetime_fmt(0) == "1970-01-01 00:00:00"
|
|
102
|
-
assert datetime_fmt(0, fmt="dmY") == "01-01-1970 00:00:00"
|
|
103
|
-
assert datetime_fmt(0, fmt="dmY", dsep="/") == "01/01/1970 00:00:00"
|
|
104
|
-
assert datetime_fmt(0, fmt="dmY", dsep="/", tsep=".") == "01/01/1970 00.00.00"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_date_fmt():
|
|
108
|
-
assert date_fmt(date(1970, 1, 1)) == "1970-01-01"
|
|
109
|
-
assert date_fmt(0) == "1970-01-01"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|