sysnet-pyutils 1.7.4__tar.gz → 1.7.5__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.
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/PKG-INFO +25 -1
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/README.md +24 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/pyproject.toml +1 -1
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/__init__.py +2 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/data_utils.py +44 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils.egg-info/PKG-INFO +25 -1
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils.egg-info/SOURCES.txt +1 -0
- sysnet_pyutils-1.7.5/tests/test_data_utils_datetime.py +156 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/LICENSE +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/setup.cfg +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/barcode.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/constants.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/cron_utils/__init__.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/cron_utils/validation.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/domino.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/geo.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/globalmodel/__init__.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/globalmodel/global_model.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/globalmodel/models.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/globalmodel/timeutils.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/ident.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/log.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/models/__init__.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/models/general.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/ses.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/syslog/__init__.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/syslog/factory.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/tools.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils/utils.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils.egg-info/dependency_links.txt +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils.egg-info/not-zip-safe +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils.egg-info/requires.txt +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/sysnet_pyutils.egg-info/top_level.txt +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_cron_utils.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_cron_validation_edge.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_data_utils.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_data_utils_edge.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_data_utils_missing_branches.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_domino.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_domino_edge.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_geo.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_global_model_recursive.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_global_model_timezone.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_ident_barcode.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_log_importerror_branch.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_log_mongo.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_models_contact.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_models_general.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_models_general_edge.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_package_init_version.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_recursive_update.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_ses.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_ses_importerror_branch.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_syslog_factory.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_tools.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_utils.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_utils_edge.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_utils_log_base64.py +0 -0
- {sysnet_pyutils-1.7.4 → sysnet_pyutils-1.7.5}/tests/test_utils_missing_branches.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysnet-pyutils
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.5
|
|
4
4
|
Summary: SYSNET Python Utilities - kompletní sada nástrojů pro Python aplikace
|
|
5
5
|
Author-email: Data Developer <info@sysnet.cz>
|
|
6
6
|
Maintainer-email: SYSNET Development Team <info@sysnet.cz>
|
|
@@ -958,6 +958,7 @@ from sysnet_pyutils import (
|
|
|
958
958
|
get_dict_value_email, get_dict_value_code, get_dict_value_uuid,
|
|
959
959
|
convert_iso_to_date, convert_cz_to_date,
|
|
960
960
|
paging_to_mongo, address_to_string,
|
|
961
|
+
normalize_datetime, localize_datetime,
|
|
961
962
|
)
|
|
962
963
|
|
|
963
964
|
data = {'name': 'John', 'age': '30', 'email': 'john@example.com'}
|
|
@@ -977,6 +978,21 @@ paging = paging_to_mongo(start=20, page_size=10)
|
|
|
977
978
|
# Adresa na string
|
|
978
979
|
address = {'street': 'Main St 123', 'city': 'Prague', 'zip': '110 00'}
|
|
979
980
|
addr_str = address_to_string(address)
|
|
981
|
+
|
|
982
|
+
# Normalizace datetime na UTC
|
|
983
|
+
# Naivní datetime dostane zónu dle env TZ (výchozí: Europe/Prague), pak se převede na UTC.
|
|
984
|
+
# Čistý date → půlnoc v lokální zóně → UTC.
|
|
985
|
+
from datetime import date, datetime
|
|
986
|
+
utc_dt = normalize_datetime(datetime(2024, 7, 1, 12, 0, 0)) # naive → Prague → UTC
|
|
987
|
+
utc_dt = normalize_datetime(date(2024, 7, 1)) # půlnoc Prague → UTC
|
|
988
|
+
utc_dt = normalize_datetime(None) # None → None
|
|
989
|
+
|
|
990
|
+
# Lokalizace datetime do lokální zóny (env TZ)
|
|
991
|
+
# Naivní datetime se považuje za UTC a převede do lokální zóny.
|
|
992
|
+
# Čistý date → půlnoc UTC → lokální zóna.
|
|
993
|
+
local_dt = localize_datetime(datetime(2024, 7, 1, 10, 0, 0)) # naive UTC → Prague
|
|
994
|
+
local_dt = localize_datetime(date(2024, 7, 1)) # půlnoc UTC → Prague
|
|
995
|
+
local_dt = localize_datetime(None) # None → None
|
|
980
996
|
```
|
|
981
997
|
|
|
982
998
|
---
|
|
@@ -1299,6 +1315,14 @@ pyutils/
|
|
|
1299
1315
|
|
|
1300
1316
|
## 📚 Changelog
|
|
1301
1317
|
|
|
1318
|
+
### Verze 1.7.5 (2026-04-07)
|
|
1319
|
+
|
|
1320
|
+
**Nové funkce:**
|
|
1321
|
+
- ✅ `data_utils.py` — přidána `normalize_datetime(date | datetime | None) -> datetime | None`: naivní vstup dostane zónu dle `TZ`, výstup vždy UTC
|
|
1322
|
+
- ✅ `data_utils.py` — přidána `localize_datetime(date | datetime | None) -> datetime | None`: naivní vstup se považuje za UTC, výstup v lokální zóně dle `TZ`
|
|
1323
|
+
- ✅ Obě funkce exportovány z `sysnet_pyutils/__init__.py`
|
|
1324
|
+
- ✅ 22 nových testů v `tests/test_data_utils_datetime.py` (včetně DST hraničních případů a roundtrip testů)
|
|
1325
|
+
|
|
1302
1326
|
### Verze 1.7.1 (2026-03-02)
|
|
1303
1327
|
|
|
1304
1328
|
**Opravy:**
|
|
@@ -235,6 +235,7 @@ from sysnet_pyutils import (
|
|
|
235
235
|
get_dict_value_email, get_dict_value_code, get_dict_value_uuid,
|
|
236
236
|
convert_iso_to_date, convert_cz_to_date,
|
|
237
237
|
paging_to_mongo, address_to_string,
|
|
238
|
+
normalize_datetime, localize_datetime,
|
|
238
239
|
)
|
|
239
240
|
|
|
240
241
|
data = {'name': 'John', 'age': '30', 'email': 'john@example.com'}
|
|
@@ -254,6 +255,21 @@ paging = paging_to_mongo(start=20, page_size=10)
|
|
|
254
255
|
# Adresa na string
|
|
255
256
|
address = {'street': 'Main St 123', 'city': 'Prague', 'zip': '110 00'}
|
|
256
257
|
addr_str = address_to_string(address)
|
|
258
|
+
|
|
259
|
+
# Normalizace datetime na UTC
|
|
260
|
+
# Naivní datetime dostane zónu dle env TZ (výchozí: Europe/Prague), pak se převede na UTC.
|
|
261
|
+
# Čistý date → půlnoc v lokální zóně → UTC.
|
|
262
|
+
from datetime import date, datetime
|
|
263
|
+
utc_dt = normalize_datetime(datetime(2024, 7, 1, 12, 0, 0)) # naive → Prague → UTC
|
|
264
|
+
utc_dt = normalize_datetime(date(2024, 7, 1)) # půlnoc Prague → UTC
|
|
265
|
+
utc_dt = normalize_datetime(None) # None → None
|
|
266
|
+
|
|
267
|
+
# Lokalizace datetime do lokální zóny (env TZ)
|
|
268
|
+
# Naivní datetime se považuje za UTC a převede do lokální zóny.
|
|
269
|
+
# Čistý date → půlnoc UTC → lokální zóna.
|
|
270
|
+
local_dt = localize_datetime(datetime(2024, 7, 1, 10, 0, 0)) # naive UTC → Prague
|
|
271
|
+
local_dt = localize_datetime(date(2024, 7, 1)) # půlnoc UTC → Prague
|
|
272
|
+
local_dt = localize_datetime(None) # None → None
|
|
257
273
|
```
|
|
258
274
|
|
|
259
275
|
---
|
|
@@ -576,6 +592,14 @@ pyutils/
|
|
|
576
592
|
|
|
577
593
|
## 📚 Changelog
|
|
578
594
|
|
|
595
|
+
### Verze 1.7.5 (2026-04-07)
|
|
596
|
+
|
|
597
|
+
**Nové funkce:**
|
|
598
|
+
- ✅ `data_utils.py` — přidána `normalize_datetime(date | datetime | None) -> datetime | None`: naivní vstup dostane zónu dle `TZ`, výstup vždy UTC
|
|
599
|
+
- ✅ `data_utils.py` — přidána `localize_datetime(date | datetime | None) -> datetime | None`: naivní vstup se považuje za UTC, výstup v lokální zóně dle `TZ`
|
|
600
|
+
- ✅ Obě funkce exportovány z `sysnet_pyutils/__init__.py`
|
|
601
|
+
- ✅ 22 nových testů v `tests/test_data_utils_datetime.py` (včetně DST hraničních případů a roundtrip testů)
|
|
602
|
+
|
|
579
603
|
### Verze 1.7.1 (2026-03-02)
|
|
580
604
|
|
|
581
605
|
**Opravy:**
|
|
@@ -333,6 +333,50 @@ def adjust_datetime_to_utc(date_value: date | datetime | None, for_store: bool =
|
|
|
333
333
|
return date_value
|
|
334
334
|
|
|
335
335
|
|
|
336
|
+
def normalize_datetime(value: date | datetime | None) -> datetime | None:
|
|
337
|
+
"""
|
|
338
|
+
Převede date nebo datetime na datetime v UTC.
|
|
339
|
+
|
|
340
|
+
Naivní datetime (bez tzinfo) dostane časovou zónu dle env proměnné TZ (výchozí: Europe/Prague).
|
|
341
|
+
Čistý date se převede na půlnoc v lokální zóně (TZ), poté na UTC.
|
|
342
|
+
Hodnota None je vrácena beze změny.
|
|
343
|
+
|
|
344
|
+
:param value: Vstupní datum nebo datetime
|
|
345
|
+
:return: datetime v UTC, nebo None
|
|
346
|
+
"""
|
|
347
|
+
if value is None:
|
|
348
|
+
return None
|
|
349
|
+
local_tz = pytz.timezone(TZ)
|
|
350
|
+
if isinstance(value, datetime):
|
|
351
|
+
if value.tzinfo is None:
|
|
352
|
+
value = local_tz.localize(value)
|
|
353
|
+
return value.astimezone(pytz.UTC)
|
|
354
|
+
# čistý date → půlnoc v lokální zóně → UTC
|
|
355
|
+
return local_tz.localize(datetime(value.year, value.month, value.day, 0, 0, 0)).astimezone(pytz.UTC)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def localize_datetime(value: date | datetime | None) -> datetime | None:
|
|
359
|
+
"""
|
|
360
|
+
Převede date nebo datetime na datetime v lokální časové zóně (dle env proměnné TZ).
|
|
361
|
+
|
|
362
|
+
Naivní datetime (bez tzinfo) je považován za UTC.
|
|
363
|
+
Čistý date se převede na půlnoc v UTC, poté do lokální zóny.
|
|
364
|
+
Hodnota None je vrácena beze změny.
|
|
365
|
+
|
|
366
|
+
:param value: Vstupní datum nebo datetime
|
|
367
|
+
:return: datetime v lokální zóně (TZ), nebo None
|
|
368
|
+
"""
|
|
369
|
+
if value is None:
|
|
370
|
+
return None
|
|
371
|
+
local_tz = pytz.timezone(TZ)
|
|
372
|
+
if isinstance(value, datetime):
|
|
373
|
+
if value.tzinfo is None:
|
|
374
|
+
value = pytz.UTC.localize(value)
|
|
375
|
+
return value.astimezone(local_tz)
|
|
376
|
+
# čistý date → půlnoc v UTC → lokální zóna
|
|
377
|
+
return pytz.UTC.localize(datetime(value.year, value.month, value.day, 0, 0, 0)).astimezone(local_tz)
|
|
378
|
+
|
|
379
|
+
|
|
336
380
|
def dict_convert_date_to_str(data: dict | list) -> None:
|
|
337
381
|
"""
|
|
338
382
|
Rekurzivně převede všechny časové položky ve slovníku na ISO text
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysnet-pyutils
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.5
|
|
4
4
|
Summary: SYSNET Python Utilities - kompletní sada nástrojů pro Python aplikace
|
|
5
5
|
Author-email: Data Developer <info@sysnet.cz>
|
|
6
6
|
Maintainer-email: SYSNET Development Team <info@sysnet.cz>
|
|
@@ -958,6 +958,7 @@ from sysnet_pyutils import (
|
|
|
958
958
|
get_dict_value_email, get_dict_value_code, get_dict_value_uuid,
|
|
959
959
|
convert_iso_to_date, convert_cz_to_date,
|
|
960
960
|
paging_to_mongo, address_to_string,
|
|
961
|
+
normalize_datetime, localize_datetime,
|
|
961
962
|
)
|
|
962
963
|
|
|
963
964
|
data = {'name': 'John', 'age': '30', 'email': 'john@example.com'}
|
|
@@ -977,6 +978,21 @@ paging = paging_to_mongo(start=20, page_size=10)
|
|
|
977
978
|
# Adresa na string
|
|
978
979
|
address = {'street': 'Main St 123', 'city': 'Prague', 'zip': '110 00'}
|
|
979
980
|
addr_str = address_to_string(address)
|
|
981
|
+
|
|
982
|
+
# Normalizace datetime na UTC
|
|
983
|
+
# Naivní datetime dostane zónu dle env TZ (výchozí: Europe/Prague), pak se převede na UTC.
|
|
984
|
+
# Čistý date → půlnoc v lokální zóně → UTC.
|
|
985
|
+
from datetime import date, datetime
|
|
986
|
+
utc_dt = normalize_datetime(datetime(2024, 7, 1, 12, 0, 0)) # naive → Prague → UTC
|
|
987
|
+
utc_dt = normalize_datetime(date(2024, 7, 1)) # půlnoc Prague → UTC
|
|
988
|
+
utc_dt = normalize_datetime(None) # None → None
|
|
989
|
+
|
|
990
|
+
# Lokalizace datetime do lokální zóny (env TZ)
|
|
991
|
+
# Naivní datetime se považuje za UTC a převede do lokální zóny.
|
|
992
|
+
# Čistý date → půlnoc UTC → lokální zóna.
|
|
993
|
+
local_dt = localize_datetime(datetime(2024, 7, 1, 10, 0, 0)) # naive UTC → Prague
|
|
994
|
+
local_dt = localize_datetime(date(2024, 7, 1)) # půlnoc UTC → Prague
|
|
995
|
+
local_dt = localize_datetime(None) # None → None
|
|
980
996
|
```
|
|
981
997
|
|
|
982
998
|
---
|
|
@@ -1299,6 +1315,14 @@ pyutils/
|
|
|
1299
1315
|
|
|
1300
1316
|
## 📚 Changelog
|
|
1301
1317
|
|
|
1318
|
+
### Verze 1.7.5 (2026-04-07)
|
|
1319
|
+
|
|
1320
|
+
**Nové funkce:**
|
|
1321
|
+
- ✅ `data_utils.py` — přidána `normalize_datetime(date | datetime | None) -> datetime | None`: naivní vstup dostane zónu dle `TZ`, výstup vždy UTC
|
|
1322
|
+
- ✅ `data_utils.py` — přidána `localize_datetime(date | datetime | None) -> datetime | None`: naivní vstup se považuje za UTC, výstup v lokální zóně dle `TZ`
|
|
1323
|
+
- ✅ Obě funkce exportovány z `sysnet_pyutils/__init__.py`
|
|
1324
|
+
- ✅ 22 nových testů v `tests/test_data_utils_datetime.py` (včetně DST hraničních případů a roundtrip testů)
|
|
1325
|
+
|
|
1302
1326
|
### Verze 1.7.1 (2026-03-02)
|
|
1303
1327
|
|
|
1304
1328
|
**Opravy:**
|
|
@@ -31,6 +31,7 @@ sysnet_pyutils/syslog/factory.py
|
|
|
31
31
|
tests/test_cron_utils.py
|
|
32
32
|
tests/test_cron_validation_edge.py
|
|
33
33
|
tests/test_data_utils.py
|
|
34
|
+
tests/test_data_utils_datetime.py
|
|
34
35
|
tests/test_data_utils_edge.py
|
|
35
36
|
tests/test_data_utils_missing_branches.py
|
|
36
37
|
tests/test_domino.py
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Testy pro normalize_datetime a localize_datetime v sysnet_pyutils.data_utils
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import date, datetime
|
|
6
|
+
|
|
7
|
+
import pytz
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from sysnet_pyutils.data_utils import localize_datetime, normalize_datetime
|
|
11
|
+
|
|
12
|
+
# Referenční zóna shodná s výchozí TZ env proměnnou (Europe/Prague)
|
|
13
|
+
PRAGUE = pytz.timezone("Europe/Prague")
|
|
14
|
+
UTC = pytz.UTC
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# normalize_datetime
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
class TestNormalizeDatetime:
|
|
22
|
+
"""normalize_datetime → výstup vždy datetime v UTC (nebo None)"""
|
|
23
|
+
|
|
24
|
+
def test_none_returns_none(self):
|
|
25
|
+
assert normalize_datetime(None) is None
|
|
26
|
+
|
|
27
|
+
def test_naive_datetime_treated_as_local(self):
|
|
28
|
+
# naivní 2024-03-10 12:00:00 → Prague je UTC+1 (zimní čas)
|
|
29
|
+
naive = datetime(2024, 3, 10, 12, 0, 0)
|
|
30
|
+
result = normalize_datetime(naive)
|
|
31
|
+
expected = PRAGUE.localize(naive).astimezone(UTC)
|
|
32
|
+
assert result == expected
|
|
33
|
+
|
|
34
|
+
def test_naive_datetime_summer_time(self):
|
|
35
|
+
# letní čas: Prague je UTC+2
|
|
36
|
+
naive = datetime(2024, 7, 1, 12, 0, 0)
|
|
37
|
+
result = normalize_datetime(naive)
|
|
38
|
+
expected = PRAGUE.localize(naive).astimezone(UTC)
|
|
39
|
+
assert result == expected
|
|
40
|
+
|
|
41
|
+
def test_aware_utc_unchanged(self):
|
|
42
|
+
aware = datetime(2024, 6, 15, 10, 0, 0, tzinfo=UTC)
|
|
43
|
+
result = normalize_datetime(aware)
|
|
44
|
+
assert result == aware
|
|
45
|
+
assert result.tzinfo == UTC
|
|
46
|
+
|
|
47
|
+
def test_aware_prague_converted_to_utc(self):
|
|
48
|
+
aware = PRAGUE.localize(datetime(2024, 6, 15, 12, 0, 0))
|
|
49
|
+
result = normalize_datetime(aware)
|
|
50
|
+
assert result.tzinfo == UTC
|
|
51
|
+
assert result == aware.astimezone(UTC)
|
|
52
|
+
|
|
53
|
+
def test_date_midnight_local_to_utc(self):
|
|
54
|
+
# date 2024-01-15 → půlnoc v Prague (UTC+1 zimní čas) → 23:00 předchozího dne UTC
|
|
55
|
+
d = date(2024, 1, 15)
|
|
56
|
+
result = normalize_datetime(d)
|
|
57
|
+
expected = PRAGUE.localize(datetime(2024, 1, 15, 0, 0, 0)).astimezone(UTC)
|
|
58
|
+
assert result == expected
|
|
59
|
+
assert result.tzinfo == UTC
|
|
60
|
+
|
|
61
|
+
def test_date_summer_midnight_local_to_utc(self):
|
|
62
|
+
# date 2024-07-01 → půlnoc v Prague (UTC+2) → 22:00 předchozího dne UTC
|
|
63
|
+
d = date(2024, 7, 1)
|
|
64
|
+
result = normalize_datetime(d)
|
|
65
|
+
expected = PRAGUE.localize(datetime(2024, 7, 1, 0, 0, 0)).astimezone(UTC)
|
|
66
|
+
assert result == expected
|
|
67
|
+
|
|
68
|
+
def test_result_is_always_datetime(self):
|
|
69
|
+
assert isinstance(normalize_datetime(date(2024, 1, 1)), datetime)
|
|
70
|
+
assert isinstance(normalize_datetime(datetime(2024, 1, 1)), datetime)
|
|
71
|
+
|
|
72
|
+
def test_result_tzinfo_is_utc(self):
|
|
73
|
+
result = normalize_datetime(datetime(2024, 5, 1, 8, 0, 0))
|
|
74
|
+
assert result.utcoffset().total_seconds() == 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# localize_datetime
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
class TestLocalizeDatetime:
|
|
82
|
+
"""localize_datetime → výstup vždy datetime v lokální zóně (TZ) nebo None"""
|
|
83
|
+
|
|
84
|
+
def test_none_returns_none(self):
|
|
85
|
+
assert localize_datetime(None) is None
|
|
86
|
+
|
|
87
|
+
def test_naive_datetime_treated_as_utc(self):
|
|
88
|
+
# naivní → považuje se za UTC → převede do Prague
|
|
89
|
+
naive = datetime(2024, 3, 10, 11, 0, 0)
|
|
90
|
+
result = localize_datetime(naive)
|
|
91
|
+
expected = UTC.localize(naive).astimezone(PRAGUE)
|
|
92
|
+
assert result == expected
|
|
93
|
+
|
|
94
|
+
def test_aware_utc_converted_to_local(self):
|
|
95
|
+
aware = datetime(2024, 1, 15, 23, 0, 0, tzinfo=UTC)
|
|
96
|
+
result = localize_datetime(aware)
|
|
97
|
+
# UTC+1 zimní čas → 00:00 následujícího dne v Prague
|
|
98
|
+
assert result.tzinfo.zone == "Europe/Prague"
|
|
99
|
+
assert result == aware.astimezone(PRAGUE)
|
|
100
|
+
|
|
101
|
+
def test_aware_prague_unchanged_zone(self):
|
|
102
|
+
aware = PRAGUE.localize(datetime(2024, 6, 15, 12, 0, 0))
|
|
103
|
+
result = localize_datetime(aware)
|
|
104
|
+
assert result.tzinfo.zone == "Europe/Prague"
|
|
105
|
+
|
|
106
|
+
def test_date_midnight_utc_to_local(self):
|
|
107
|
+
# date 2024-07-01 → půlnoc UTC → 02:00 Prague (UTC+2 letní čas)
|
|
108
|
+
d = date(2024, 7, 1)
|
|
109
|
+
result = localize_datetime(d)
|
|
110
|
+
expected = UTC.localize(datetime(2024, 7, 1, 0, 0, 0)).astimezone(PRAGUE)
|
|
111
|
+
assert result == expected
|
|
112
|
+
assert result.tzinfo.zone == "Europe/Prague"
|
|
113
|
+
|
|
114
|
+
def test_date_winter_midnight_utc_to_local(self):
|
|
115
|
+
# date 2024-01-15 → půlnoc UTC → 01:00 Prague (UTC+1 zimní čas)
|
|
116
|
+
d = date(2024, 1, 15)
|
|
117
|
+
result = localize_datetime(d)
|
|
118
|
+
expected = UTC.localize(datetime(2024, 1, 15, 0, 0, 0)).astimezone(PRAGUE)
|
|
119
|
+
assert result == expected
|
|
120
|
+
|
|
121
|
+
def test_result_is_always_datetime(self):
|
|
122
|
+
assert isinstance(localize_datetime(date(2024, 1, 1)), datetime)
|
|
123
|
+
assert isinstance(localize_datetime(datetime(2024, 1, 1)), datetime)
|
|
124
|
+
|
|
125
|
+
def test_result_tzinfo_is_local(self):
|
|
126
|
+
result = localize_datetime(datetime(2024, 5, 1, 8, 0, 0))
|
|
127
|
+
assert result.tzinfo.zone == "Europe/Prague"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# Symetrie: roundtrip normalize → localize
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
class TestRoundtrip:
|
|
135
|
+
"""normalize_datetime a localize_datetime jsou inverzní operace."""
|
|
136
|
+
|
|
137
|
+
@pytest.mark.parametrize("naive", [
|
|
138
|
+
datetime(2024, 1, 15, 10, 30, 0),
|
|
139
|
+
datetime(2024, 7, 1, 0, 0, 0),
|
|
140
|
+
datetime(2024, 3, 31, 1, 30, 0), # těsně před přechodem na letní čas
|
|
141
|
+
])
|
|
142
|
+
def test_normalize_then_localize_roundtrip(self, naive):
|
|
143
|
+
utc = normalize_datetime(naive)
|
|
144
|
+
back = localize_datetime(utc)
|
|
145
|
+
# Po roundtripu musí být hodnota čas. shodná
|
|
146
|
+
assert back.astimezone(UTC) == utc
|
|
147
|
+
|
|
148
|
+
@pytest.mark.parametrize("d", [
|
|
149
|
+
date(2024, 1, 15),
|
|
150
|
+
date(2024, 7, 1),
|
|
151
|
+
])
|
|
152
|
+
def test_date_normalize_then_localize(self, d):
|
|
153
|
+
utc = normalize_datetime(d)
|
|
154
|
+
local = localize_datetime(utc)
|
|
155
|
+
# Půlnoc v lokální zóně → UTC → zpět do lokální → stále půlnoc
|
|
156
|
+
assert local.hour == 0 and local.minute == 0 and local.second == 0
|
|
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
|
|
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
|