encommon 0.7.5__py3-none-any.whl → 0.8.0__py3-none-any.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.
- encommon/config/__init__.py +4 -0
- encommon/config/common.py +18 -14
- encommon/config/config.py +8 -5
- encommon/config/files.py +13 -7
- encommon/config/logger.py +94 -88
- encommon/config/params.py +1 -1
- encommon/config/paths.py +16 -8
- encommon/config/test/test_common.py +27 -4
- encommon/config/test/test_config.py +48 -82
- encommon/config/test/test_files.py +58 -43
- encommon/config/test/test_logger.py +129 -82
- encommon/config/test/test_paths.py +70 -30
- encommon/conftest.py +52 -12
- encommon/crypts/__init__.py +2 -0
- encommon/crypts/crypts.py +3 -1
- encommon/crypts/hashes.py +2 -2
- encommon/crypts/test/test_crypts.py +50 -28
- encommon/crypts/test/test_hashes.py +20 -18
- encommon/times/__init__.py +2 -0
- encommon/times/common.py +99 -15
- encommon/times/duration.py +50 -36
- encommon/times/parse.py +13 -25
- encommon/times/test/test_common.py +47 -16
- encommon/times/test/test_duration.py +104 -79
- encommon/times/test/test_parse.py +53 -63
- encommon/times/test/test_timers.py +90 -36
- encommon/times/test/test_times.py +21 -30
- encommon/times/test/test_window.py +73 -21
- encommon/times/timers.py +91 -58
- encommon/times/times.py +36 -34
- encommon/times/window.py +4 -4
- encommon/types/dicts.py +10 -4
- encommon/types/empty.py +7 -2
- encommon/types/strings.py +10 -0
- encommon/types/test/test_dicts.py +5 -5
- encommon/types/test/test_empty.py +4 -1
- encommon/types/test/test_strings.py +1 -1
- encommon/utils/__init__.py +4 -0
- encommon/utils/common.py +51 -6
- encommon/utils/match.py +1 -0
- encommon/utils/paths.py +42 -23
- encommon/utils/sample.py +30 -26
- encommon/utils/stdout.py +28 -17
- encommon/utils/test/test_common.py +35 -0
- encommon/utils/test/test_paths.py +3 -2
- encommon/utils/test/test_sample.py +28 -12
- encommon/version.txt +1 -1
- {encommon-0.7.5.dist-info → encommon-0.8.0.dist-info}/METADATA +1 -1
- encommon-0.8.0.dist-info/RECORD +63 -0
- encommon-0.7.5.dist-info/RECORD +0 -62
- {encommon-0.7.5.dist-info → encommon-0.8.0.dist-info}/LICENSE +0 -0
- {encommon-0.7.5.dist-info → encommon-0.8.0.dist-info}/WHEEL +0 -0
- {encommon-0.7.5.dist-info → encommon-0.8.0.dist-info}/top_level.txt +0 -0
@@ -9,12 +9,32 @@ is permitted, for more information consult the project license file.
|
|
9
9
|
|
10
10
|
from pathlib import Path
|
11
11
|
|
12
|
+
from pytest import fixture
|
13
|
+
|
12
14
|
from . import SAMPLES
|
13
15
|
from ..paths import ConfigPath
|
14
16
|
from ..paths import ConfigPaths
|
15
17
|
from ... import ENPYRWS
|
16
|
-
from ...
|
17
|
-
from ...utils
|
18
|
+
from ... import PROJECT
|
19
|
+
from ...utils import load_sample
|
20
|
+
from ...utils import prep_sample
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
@fixture
|
25
|
+
def paths(
|
26
|
+
config_path: Path,
|
27
|
+
) -> ConfigPaths:
|
28
|
+
"""
|
29
|
+
Construct the instance for use in the downstream tests.
|
30
|
+
|
31
|
+
:param config_path: Custom fixture for populating paths.
|
32
|
+
:returns: Newly constructed instance of related class.
|
33
|
+
"""
|
34
|
+
|
35
|
+
return ConfigPaths([
|
36
|
+
f'{SAMPLES}/stark',
|
37
|
+
f'{SAMPLES}/wayne'])
|
18
38
|
|
19
39
|
|
20
40
|
|
@@ -29,33 +49,38 @@ def test_ConfigPath(
|
|
29
49
|
|
30
50
|
path = ConfigPath(config_path)
|
31
51
|
|
52
|
+
|
32
53
|
attrs = list(path.__dict__)
|
33
54
|
|
34
|
-
assert attrs == [
|
55
|
+
assert attrs == [
|
56
|
+
'path',
|
57
|
+
'config']
|
58
|
+
|
59
|
+
|
60
|
+
assert repr(path)[:22] == (
|
61
|
+
'<encommon.config.paths')
|
62
|
+
|
63
|
+
assert hash(path) > 0
|
35
64
|
|
65
|
+
assert str(path)[:22] == (
|
66
|
+
'<encommon.config.paths')
|
36
67
|
|
37
|
-
assert repr(path).startswith(
|
38
|
-
'<encommon.config.paths.ConfigPath')
|
39
|
-
assert isinstance(hash(path), int)
|
40
|
-
assert str(path).startswith(
|
41
|
-
'<encommon.config.paths.ConfigPath')
|
42
68
|
|
69
|
+
assert 'test' in path.path.name
|
43
70
|
|
44
|
-
assert path.
|
45
|
-
assert len(path.config) == 2
|
71
|
+
assert len(path.config) == 1
|
46
72
|
|
47
73
|
|
48
74
|
|
49
75
|
def test_ConfigPaths(
|
50
|
-
|
76
|
+
paths: ConfigPaths,
|
51
77
|
) -> None:
|
52
78
|
"""
|
53
79
|
Perform various tests associated with relevant routines.
|
54
80
|
|
55
|
-
:param
|
81
|
+
:param paths: Custom fixture for the configuration paths.
|
56
82
|
"""
|
57
83
|
|
58
|
-
paths = ConfigPaths(config_path)
|
59
84
|
|
60
85
|
attrs = list(paths.__dict__)
|
61
86
|
|
@@ -64,35 +89,50 @@ def test_ConfigPaths(
|
|
64
89
|
'_ConfigPaths__merged']
|
65
90
|
|
66
91
|
|
67
|
-
assert repr(paths)
|
68
|
-
'<encommon.config.paths
|
69
|
-
|
70
|
-
assert
|
71
|
-
|
92
|
+
assert repr(paths)[:22] == (
|
93
|
+
'<encommon.config.paths')
|
94
|
+
|
95
|
+
assert hash(paths) > 0
|
96
|
+
|
97
|
+
assert str(paths)[:22] == (
|
98
|
+
'<encommon.config.paths')
|
72
99
|
|
73
100
|
|
74
|
-
assert len(paths.paths) ==
|
75
|
-
assert len(paths.config) == 1
|
101
|
+
assert len(paths.paths) == 2
|
76
102
|
|
103
|
+
assert len(paths.config) == 2
|
77
104
|
|
78
|
-
_merged1 = paths.merged
|
79
|
-
_merged2 = paths.merged
|
80
105
|
|
81
|
-
|
106
|
+
replaces = {
|
107
|
+
'PROJECT': PROJECT}
|
82
108
|
|
83
|
-
sample_path =
|
109
|
+
sample_path = (
|
84
110
|
f'{SAMPLES}/paths.json')
|
85
111
|
|
86
112
|
sample = load_sample(
|
87
113
|
path=sample_path,
|
88
114
|
update=ENPYRWS,
|
89
|
-
content=
|
90
|
-
replace=
|
91
|
-
'config_path': str(config_path)})
|
115
|
+
content=paths.merged,
|
116
|
+
replace=replaces)
|
92
117
|
|
93
118
|
expect = prep_sample(
|
94
|
-
content=
|
95
|
-
replace=
|
96
|
-
'config_path': str(config_path)})
|
119
|
+
content=paths.merged,
|
120
|
+
replace=replaces)
|
97
121
|
|
98
122
|
assert sample == expect
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
def test_ConfigPaths_cover(
|
127
|
+
paths: ConfigPaths,
|
128
|
+
) -> None:
|
129
|
+
"""
|
130
|
+
Perform various tests associated with relevant routines.
|
131
|
+
|
132
|
+
:param paths: Custom fixture for the configuration paths.
|
133
|
+
"""
|
134
|
+
|
135
|
+
merged1 = paths.merged
|
136
|
+
merged2 = paths.merged
|
137
|
+
|
138
|
+
assert merged1 is not merged2
|
encommon/conftest.py
CHANGED
@@ -11,6 +11,46 @@ from pathlib import Path
|
|
11
11
|
|
12
12
|
from pytest import fixture
|
13
13
|
|
14
|
+
from .config import Config
|
15
|
+
from .config.test import SAMPLES
|
16
|
+
from .utils import save_text
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
def config_factory(
|
21
|
+
tmp_path: Path,
|
22
|
+
) -> Config:
|
23
|
+
"""
|
24
|
+
Construct the instance for use in the downstream tests.
|
25
|
+
|
26
|
+
:param tmp_path: pytest object for temporal filesystem.
|
27
|
+
:returns: Newly constructed instance of related class.
|
28
|
+
"""
|
29
|
+
|
30
|
+
save_text(
|
31
|
+
f'{tmp_path}/config.yml',
|
32
|
+
content=(
|
33
|
+
'enconfig:\n'
|
34
|
+
' paths:\n'
|
35
|
+
f" - '{SAMPLES}/stark'\n"
|
36
|
+
f" - '{SAMPLES}/wayne'\n"
|
37
|
+
'enlogger:\n'
|
38
|
+
' stdo_level: info\n'
|
39
|
+
'encrypts:\n'
|
40
|
+
' phrases:\n'
|
41
|
+
' default: phrase\n'))
|
42
|
+
|
43
|
+
config_log = f'{tmp_path}/config.log'
|
44
|
+
|
45
|
+
cargs = {
|
46
|
+
'enlogger': {
|
47
|
+
'file_path': config_log,
|
48
|
+
'file_level': 'info'}}
|
49
|
+
|
50
|
+
return Config(
|
51
|
+
files=f'{tmp_path}/config.yml',
|
52
|
+
cargs=cargs)
|
53
|
+
|
14
54
|
|
15
55
|
|
16
56
|
@fixture
|
@@ -24,21 +64,21 @@ def config_path(
|
|
24
64
|
:returns: New resolved filesystem path object instance.
|
25
65
|
"""
|
26
66
|
|
67
|
+
config_factory(tmp_path)
|
27
68
|
|
28
|
-
|
29
|
-
Path(f'{tmp_path}/wayne'))
|
30
|
-
|
31
|
-
(Path(f'{tmp_path}/wayne')
|
32
|
-
.joinpath('bwayne.yml')
|
33
|
-
.write_text('name: Bruce Wayne'))
|
69
|
+
return tmp_path.resolve()
|
34
70
|
|
35
71
|
|
36
|
-
Path.mkdir(
|
37
|
-
Path(f'{tmp_path}/stark'))
|
38
72
|
|
39
|
-
|
40
|
-
|
41
|
-
|
73
|
+
@fixture
|
74
|
+
def config(
|
75
|
+
tmp_path: Path,
|
76
|
+
) -> Config:
|
77
|
+
"""
|
78
|
+
Construct the instance for use in the downstream tests.
|
42
79
|
|
80
|
+
:param tmp_path: pytest object for temporal filesystem.
|
81
|
+
:returns: Newly constructed instance of related class.
|
82
|
+
"""
|
43
83
|
|
44
|
-
return tmp_path
|
84
|
+
return config_factory(tmp_path)
|
encommon/crypts/__init__.py
CHANGED
encommon/crypts/crypts.py
CHANGED
@@ -15,6 +15,8 @@ from typing import TYPE_CHECKING
|
|
15
15
|
|
16
16
|
from cryptography.fernet import Fernet
|
17
17
|
|
18
|
+
from ..types.strings import SEMPTY
|
19
|
+
|
18
20
|
if TYPE_CHECKING:
|
19
21
|
from .params import CryptsParams
|
20
22
|
|
@@ -157,4 +159,4 @@ def crypt_clean(
|
|
157
159
|
:param value: String value that will returned decrypted.
|
158
160
|
"""
|
159
161
|
|
160
|
-
return re_sub(r'[\n\s]',
|
162
|
+
return re_sub(r'[\n\s]', SEMPTY, value)
|
encommon/crypts/hashes.py
CHANGED
@@ -90,7 +90,7 @@ class Hashes:
|
|
90
90
|
|
91
91
|
string = self.__string.encode()
|
92
92
|
|
93
|
-
return sha1(string).hexdigest()
|
93
|
+
return sha1(string).hexdigest()
|
94
94
|
|
95
95
|
|
96
96
|
@property
|
@@ -151,6 +151,6 @@ class Hashes:
|
|
151
151
|
|
152
152
|
string = self.__string.encode()
|
153
153
|
|
154
|
-
digest = sha1(string).digest()
|
154
|
+
digest = sha1(string).digest()
|
155
155
|
|
156
156
|
return b64encode(digest).decode()
|
@@ -12,33 +12,38 @@ from pytest import mark
|
|
12
12
|
from pytest import raises
|
13
13
|
|
14
14
|
from ..crypts import Crypts
|
15
|
+
from ..params import CryptsParams
|
15
16
|
|
16
17
|
|
17
18
|
|
18
19
|
@fixture
|
19
|
-
def
|
20
|
+
def crypts() -> Crypts:
|
20
21
|
"""
|
21
|
-
Construct
|
22
|
+
Construct the instance for use in the downstream tests.
|
22
23
|
|
23
|
-
:returns:
|
24
|
+
:returns: Newly constructed instance of related class.
|
24
25
|
"""
|
25
26
|
|
26
|
-
|
27
|
+
phrases = {
|
27
28
|
'default': Crypts.keygen(),
|
28
29
|
'secrets': Crypts.keygen()}
|
29
30
|
|
31
|
+
params = CryptsParams(
|
32
|
+
phrases=phrases)
|
33
|
+
|
34
|
+
return Crypts(params=params)
|
35
|
+
|
30
36
|
|
31
37
|
|
32
38
|
def test_Crypts(
|
33
|
-
|
39
|
+
crypts: Crypts,
|
34
40
|
) -> None:
|
35
41
|
"""
|
36
42
|
Perform various tests associated with relevant routines.
|
37
43
|
|
38
|
-
:param
|
44
|
+
:param crypts: Primary class instance for the encryption.
|
39
45
|
"""
|
40
46
|
|
41
|
-
crypts = Crypts(phrases)
|
42
47
|
|
43
48
|
attrs = list(crypts.__dict__)
|
44
49
|
|
@@ -46,14 +51,18 @@ def test_Crypts(
|
|
46
51
|
'_Crypts__phrases']
|
47
52
|
|
48
53
|
|
49
|
-
assert repr(crypts)
|
50
|
-
'<encommon.crypts.crypts
|
51
|
-
|
52
|
-
assert
|
53
|
-
'<encommon.crypts.crypts.Crypts')
|
54
|
+
assert repr(crypts)[:23] == (
|
55
|
+
'<encommon.crypts.crypts')
|
56
|
+
|
57
|
+
assert hash(crypts) > 0
|
54
58
|
|
59
|
+
assert str(crypts)[:23] == (
|
60
|
+
'<encommon.crypts.crypts')
|
55
61
|
|
56
|
-
|
62
|
+
|
63
|
+
assert len(crypts.phrases) == 2
|
64
|
+
|
65
|
+
assert len(crypts.keygen()) == 44
|
57
66
|
|
58
67
|
|
59
68
|
|
@@ -62,21 +71,20 @@ def test_Crypts(
|
|
62
71
|
[('foo', 'default'),
|
63
72
|
('foo', 'secrets')])
|
64
73
|
def test_Crypts_iterate(
|
74
|
+
crypts: Crypts,
|
65
75
|
value: str,
|
66
76
|
unique: str,
|
67
|
-
phrases: dict[str, str],
|
68
77
|
) -> None:
|
69
78
|
"""
|
70
79
|
Perform various tests associated with relevant routines.
|
71
80
|
|
81
|
+
:param crypts: Primary class instance for the encryption.
|
72
82
|
:param value: String value that will returned encrypted.
|
73
83
|
:param unique: Unique identifier of mapping passphrase.
|
74
|
-
:param phrases: Dictionary of randomly generated phrases.
|
75
84
|
"""
|
76
85
|
|
77
|
-
|
78
|
-
|
79
|
-
encrypt = crypts.encrypt(value, unique)
|
86
|
+
encrypt = (
|
87
|
+
crypts.encrypt(value, unique))
|
80
88
|
|
81
89
|
split = encrypt.split(';')
|
82
90
|
|
@@ -90,31 +98,45 @@ def test_Crypts_iterate(
|
|
90
98
|
|
91
99
|
|
92
100
|
def test_Crypts_raises(
|
93
|
-
|
101
|
+
crypts: Crypts,
|
94
102
|
) -> None:
|
95
103
|
"""
|
96
104
|
Perform various tests associated with relevant routines.
|
97
105
|
|
98
|
-
:param
|
106
|
+
:param crypts: Primary class instance for the encryption.
|
99
107
|
"""
|
100
108
|
|
101
109
|
|
102
|
-
|
110
|
+
_raises = raises(ValueError)
|
111
|
+
|
112
|
+
with _raises as reason:
|
103
113
|
Crypts({'foo': 'bar'})
|
104
114
|
|
105
|
-
|
115
|
+
_reason = str(reason.value)
|
116
|
+
|
117
|
+
assert _reason == 'default'
|
118
|
+
|
106
119
|
|
120
|
+
crypts = Crypts(
|
121
|
+
crypts.phrases)
|
107
122
|
|
108
|
-
crypts = Crypts(phrases)
|
109
123
|
|
124
|
+
_raises = raises(ValueError)
|
110
125
|
|
111
|
-
with
|
126
|
+
with _raises as reason:
|
112
127
|
crypts.decrypt('foo')
|
113
128
|
|
114
|
-
|
129
|
+
_reason = str(reason.value)
|
130
|
+
|
131
|
+
assert _reason == 'string'
|
132
|
+
|
133
|
+
|
134
|
+
_raises = raises(ValueError)
|
115
135
|
|
136
|
+
with _raises as reason:
|
137
|
+
string = '$ENCRYPT;1.1;f;oo'
|
138
|
+
crypts.decrypt(string)
|
116
139
|
|
117
|
-
|
118
|
-
crypts.decrypt('$ENCRYPT;1.1;f;oo')
|
140
|
+
_reason = str(reason.value)
|
119
141
|
|
120
|
-
assert
|
142
|
+
assert _reason == 'version'
|
@@ -18,36 +18,38 @@ def test_Hashes() -> None:
|
|
18
18
|
|
19
19
|
hashes = Hashes('string')
|
20
20
|
|
21
|
+
|
21
22
|
attrs = list(hashes.__dict__)
|
22
23
|
|
23
24
|
assert attrs == [
|
24
25
|
'_Hashes__string']
|
25
26
|
|
26
27
|
|
27
|
-
assert repr(hashes)
|
28
|
-
'<encommon.crypts.hashes
|
29
|
-
assert isinstance(hash(hashes), int)
|
30
|
-
assert str(hashes).startswith(
|
31
|
-
'<encommon.crypts.hashes.Hashes')
|
28
|
+
assert repr(hashes)[:23] == (
|
29
|
+
'<encommon.crypts.hashes')
|
32
30
|
|
31
|
+
assert hash(hashes) > 0
|
33
32
|
|
34
|
-
assert hashes
|
33
|
+
assert str(hashes)[:23] == (
|
34
|
+
'<encommon.crypts.hashes')
|
35
35
|
|
36
36
|
|
37
|
-
assert hashes.
|
38
|
-
|
37
|
+
assert hashes.string == 'string'
|
38
|
+
|
39
|
+
assert hashes.md5[:3] == 'b45'
|
40
|
+
assert hashes.md5[-2:] == '21'
|
39
41
|
|
40
|
-
assert hashes.sha1
|
41
|
-
assert hashes.sha1
|
42
|
+
assert hashes.sha1[:3] == 'ecb'
|
43
|
+
assert hashes.sha1[-2:] == '4d'
|
42
44
|
|
43
|
-
assert hashes.sha256
|
44
|
-
assert hashes.sha256
|
45
|
+
assert hashes.sha256[:3] == '473'
|
46
|
+
assert hashes.sha256[-2:] == 'a8'
|
45
47
|
|
46
|
-
assert hashes.sha512
|
47
|
-
assert hashes.sha512
|
48
|
+
assert hashes.sha512[:3] == '275'
|
49
|
+
assert hashes.sha512[-2:] == '87'
|
48
50
|
|
49
|
-
assert hashes.uuid
|
50
|
-
assert hashes.uuid
|
51
|
+
assert hashes.uuid[:3] == '38f'
|
52
|
+
assert hashes.uuid[-2:] == 'e4'
|
51
53
|
|
52
|
-
assert hashes.apache
|
53
|
-
assert hashes.apache
|
54
|
+
assert hashes.apache[:3] == '7LJ'
|
55
|
+
assert hashes.apache[-2:] == '0='
|
encommon/times/__init__.py
CHANGED
@@ -7,6 +7,7 @@ is permitted, for more information consult the project license file.
|
|
7
7
|
|
8
8
|
|
9
9
|
|
10
|
+
from .common import findtz
|
10
11
|
from .duration import Duration
|
11
12
|
from .parse import parse_time
|
12
13
|
from .parse import shift_time
|
@@ -20,6 +21,7 @@ from .window import Window
|
|
20
21
|
|
21
22
|
__all__ = [
|
22
23
|
'Duration',
|
24
|
+
'findtz',
|
23
25
|
'Timers',
|
24
26
|
'Times',
|
25
27
|
'Window',
|
encommon/times/common.py
CHANGED
@@ -10,30 +10,64 @@ is permitted, for more information consult the project license file.
|
|
10
10
|
from contextlib import suppress
|
11
11
|
from datetime import datetime
|
12
12
|
from datetime import timezone
|
13
|
+
from datetime import tzinfo
|
13
14
|
from re import compile
|
14
15
|
from typing import Any
|
16
|
+
from typing import Optional
|
15
17
|
from typing import TYPE_CHECKING
|
16
18
|
from typing import Union
|
19
|
+
|
20
|
+
from dateutil.tz import gettz
|
21
|
+
|
17
22
|
if TYPE_CHECKING:
|
18
23
|
from .times import Times
|
19
24
|
|
20
25
|
|
21
26
|
|
22
|
-
NUMERISH = compile(
|
23
|
-
|
24
|
-
|
27
|
+
NUMERISH = compile(
|
28
|
+
r'^\-?\d+(\.\d+)?$')
|
29
|
+
|
30
|
+
SNAPABLE = compile(
|
31
|
+
r'^(\-|\+)[\d\@a-z\-\+]+$')
|
32
|
+
|
33
|
+
STRINGNOW = {
|
34
|
+
'None', 'null', 'now'}
|
35
|
+
|
36
|
+
|
25
37
|
|
26
38
|
NUMERIC = Union[int, float]
|
27
|
-
PARSABLE = Union[str, NUMERIC, datetime, 'Times']
|
28
|
-
SCHEDULE = Union[str, dict[str, int]]
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
40
|
+
PARSABLE = Union[
|
41
|
+
str, NUMERIC,
|
42
|
+
datetime, 'Times']
|
43
|
+
|
44
|
+
SCHEDULE = Union[
|
45
|
+
str, dict[str, int]]
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
UNIXEPOCH = (
|
50
|
+
'1970-01-01T00:00:00+0000')
|
51
|
+
|
52
|
+
UNIXMPOCH = (
|
53
|
+
'1970-01-01T00:00:00.000000+0000')
|
33
54
|
|
34
|
-
|
35
|
-
|
36
|
-
|
55
|
+
UNIXSPOCH = (
|
56
|
+
'1970-01-01T00:00:00Z')
|
57
|
+
|
58
|
+
UNIXHPOCH = (
|
59
|
+
'01/01/1970 12:00AM UTC')
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
STAMP_SIMPLE = (
|
64
|
+
'%Y-%m-%dT%H:%M:%S%z')
|
65
|
+
|
66
|
+
STAMP_SUBSEC = (
|
67
|
+
'%Y-%m-%dT%H:%M:%S.%f%z')
|
68
|
+
|
69
|
+
STAMP_HUMAN = (
|
70
|
+
'%m/%d/%Y %I:%M%p %Z')
|
37
71
|
|
38
72
|
|
39
73
|
|
@@ -57,14 +91,16 @@ def utcdatetime(
|
|
57
91
|
:returns: Instance of datetime within the UTC timezone.
|
58
92
|
"""
|
59
93
|
|
94
|
+
tzinfo = timezone.utc
|
95
|
+
|
60
96
|
if not args and not kwargs:
|
61
|
-
return datetime.now(tz=
|
97
|
+
return datetime.now(tz=tzinfo)
|
62
98
|
|
63
99
|
if 'tzinfo' not in kwargs:
|
64
|
-
kwargs['tzinfo'] =
|
100
|
+
kwargs['tzinfo'] = tzinfo
|
65
101
|
|
66
102
|
return (
|
67
|
-
datetime(*args, **kwargs)
|
103
|
+
datetime(*args, **kwargs)
|
68
104
|
.astimezone(timezone.utc))
|
69
105
|
|
70
106
|
|
@@ -86,6 +122,8 @@ def strptime(
|
|
86
122
|
:returns: Python datetime object containing related time.
|
87
123
|
"""
|
88
124
|
|
125
|
+
tzinfo = timezone.utc
|
126
|
+
|
89
127
|
if isinstance(formats, str):
|
90
128
|
formats = [formats]
|
91
129
|
|
@@ -93,14 +131,60 @@ def strptime(
|
|
93
131
|
def _strptime(
|
94
132
|
format: str,
|
95
133
|
) -> datetime:
|
134
|
+
|
96
135
|
return (
|
97
136
|
datetime
|
98
137
|
.strptime(source, format)
|
99
|
-
.astimezone(
|
138
|
+
.astimezone(tzinfo))
|
100
139
|
|
101
140
|
|
102
141
|
for format in formats:
|
142
|
+
|
103
143
|
with suppress(ValueError):
|
104
144
|
return _strptime(format)
|
105
145
|
|
146
|
+
|
106
147
|
raise ValueError('invalid')
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
def strftime(
|
152
|
+
source: datetime,
|
153
|
+
format: str,
|
154
|
+
) -> str:
|
155
|
+
"""
|
156
|
+
Return the timestamp string for datetime object provided.
|
157
|
+
|
158
|
+
.. note::
|
159
|
+
This function is extremely pedantic and cosmetic.
|
160
|
+
|
161
|
+
:param source: Python datetime instance containing source.
|
162
|
+
:param format: Format for the timestamp string returned.
|
163
|
+
:returns: Timestamp string for datetime object provided.
|
164
|
+
"""
|
165
|
+
|
166
|
+
return (
|
167
|
+
datetime
|
168
|
+
.strftime(source, format))
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
def findtz(
|
173
|
+
tzname: Optional[str] = None,
|
174
|
+
) -> tzinfo:
|
175
|
+
"""
|
176
|
+
Return the located timezone object for the provided name.
|
177
|
+
|
178
|
+
:param tzname: Name of the timezone associated to source.
|
179
|
+
:returns: Located timezone object for the provided name.
|
180
|
+
"""
|
181
|
+
|
182
|
+
if tzname is None:
|
183
|
+
return timezone.utc
|
184
|
+
|
185
|
+
tzinfo = gettz(tzname)
|
186
|
+
|
187
|
+
if tzinfo is None:
|
188
|
+
raise ValueError('tzname')
|
189
|
+
|
190
|
+
return tzinfo
|