Skip to content

Commit 38755e0

Browse files
authored
fix datetime parsing when no fractional seconds present (#38)
* fix datetime parsing when no fractional seconds present * ci fix
1 parent ef8467b commit 38755e0

File tree

8 files changed

+213
-26
lines changed

8 files changed

+213
-26
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@ jobs:
6363
6464
- name: Pull dependencies
6565
run: |
66-
poetry env use system
66+
poetry env use 3.7
6767
poetry install --no-cache --no-root
6868
poetry self add poetry-plugin-export
6969
mkdir package/lib
7070
poetry export -f requirements.txt --output package/lib/requirements.txt
71-
poetry env use 3.7
72-
poetry install --no-cache --no-root --with dev,splunkslim
71+
poetry install --no-cache --no-root --with dev,test,splunkslim
72+
73+
- name: Tests
74+
run: poetry run pytest
7375

7476
- name: Build Splunk App
7577
run: |

globalConfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
"meta": {
2424
"name": "bitwarden_event_logs_beta",
2525
"restRoot": "bitwarden_event_logs",
26-
"version": "2.0.3",
26+
"version": "1.2.0",
2727
"displayName": "Bitwarden Event Logs (beta)",
2828
"schemaVersion": "0.0.3",
29-
"_uccVersion": "5.39.0"
29+
"_uccVersion": "5.41.0"
3030
}
3131
}

package.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ rm -rf package/lib/*
1212
poetry export -f requirements.txt --output package/lib/requirements.txt
1313
cp -R src/* package/bin/
1414

15-
ucc-gen build --ta-version ${VERSION}
15+
poetry run ucc-gen build --ta-version ${VERSION}
1616
## cleanup python files
1717
rm -rf output/$APP_NAME/{bin,lib}/__pycache__
1818
rm -rf output/$APP_NAME/bin/{bitwarden_event_logs_rh_settings.py,import_declare_test.py}
@@ -21,7 +21,7 @@ rm -rf output/$APP_NAME/appserver/static/{css,js,openapi.json}
2121
rm -rf output/$APP_NAME/appserver/templates/base.html
2222
rm -rf output/$APP_NAME/default/{restmap.conf,web.conf,bitwarden_event_logs_settings.conf}
2323
rm -rf output/$APP_NAME/README/bitwarden_event_logs_settings.conf.spec
24-
ucc-gen package --path output/$APP_NAME/ -o output/
24+
poetry run ucc-gen package --path output/$APP_NAME/ -o output/
2525

2626
mv output/${APP_NAME}-${VERSION}.tar.gz output/bitwarden_event_logs.tar.gz
2727

poetry.lock

Lines changed: 80 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ readme = "README.md"
1212
keywords = ["splunk", "bitwarden"]
1313

1414
[tool.poetry.dependencies]
15+
python = "~3.7"
1516
requests = { version = "2.31.0", python = "~3.7" }
1617
splunk-sdk = { version = "2.0.1", python = "~3.7" }
1718
splunktaucclib = { version = "6.0.8", python = "~3.7" }
@@ -21,6 +22,7 @@ python-dateutil = { version = "2.9.0.post0", python = "~3.7" }
2122
optional = true
2223

2324
[tool.poetry.group.dev.dependencies]
25+
python = "^3.7"
2426
python-dotenv = { version = "0.21.1", python = "^3.7" }
2527
types-requests = { version = "2.31.0.6", python = "^3.7" }
2628
splunk-add-on-ucc-framework = { version = "5.41.0", python = "^3.7" }
@@ -30,8 +32,19 @@ splunk-appinspect = { version = "3.5.0", python = "^3.7" }
3032
optional = true
3133

3234
[tool.poetry.group.splunkslim.dependencies]
35+
python = "^3.7"
3336
splunk-packaging-toolkit = { version = "1.0.1", python = "^3.7" }
3437

38+
[tool.poetry.group.test]
39+
optional = true
40+
41+
[tool.poetry.group.test.dependencies]
42+
python = "^3.7"
43+
pytest = { version = "7.4.4", python = "^3.7" }
44+
45+
[tool.pytest.ini_options]
46+
pythonpath = "src"
47+
3548
[build-system]
3649
requires = ["poetry-core"]
3750
build-backend = "poetry.core.masonry.api"

src/mappers.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import re
21
from datetime import datetime
32
from typing import Dict, Any, Optional
43

@@ -74,22 +73,13 @@ def datetime_from_str(date_str: Optional[str]) -> Optional[datetime]:
7473

7574
# no time
7675
if ":" not in date_str:
77-
date_str = date_str + "T00:00:00"
76+
return dateutil.parser.isoparse(date_str + "T00:00:00Z")
7877

79-
# no fractional seconds
80-
if "." not in date_str:
81-
date_str = date_str + ".0"
78+
# no timezone
79+
if "Z" not in date_str and "+" not in date_str:
80+
return dateutil.parser.isoparse(date_str + "Z")
8281

83-
date_str_split = date_str.split(".")
84-
85-
fractional_seconds_str = date_str_split[1]
86-
fractional_seconds_str = re.sub(r"\D", "", fractional_seconds_str)
87-
if len(fractional_seconds_str) > 6:
88-
fractional_seconds_str = fractional_seconds_str[:6]
89-
90-
new_date_str = f"{date_str_split[0]}.{fractional_seconds_str}Z"
91-
92-
return dateutil.parser.isoparse(new_date_str)
82+
return dateutil.parser.isoparse(date_str)
9383

9484

9585
def datetime_to_str(date: datetime) -> str:

tests/__init__.py

Whitespace-only changes.

tests/test_mappers.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from datetime import datetime, date, time, timezone
2+
3+
from mappers import datetime_from_str, datetime_to_str
4+
5+
DATETIME_NO_TIME = datetime.combine(date(2024, 4, 6), time(tzinfo=timezone.utc), tzinfo=timezone.utc)
6+
DATETIME_TIME_NO_FRACTIONAL = DATETIME_NO_TIME.replace(hour=13, minute=30, second=45)
7+
DATETIME_TIME_WITH_FRACTIONAL_LONG = DATETIME_TIME_NO_FRACTIONAL.replace(microsecond=123456)
8+
9+
DATETIME_TIME_WITH_FRACTIONAL_SHORT = DATETIME_TIME_NO_FRACTIONAL.replace(microsecond=123000)
10+
11+
12+
def test_datetime_from_str_none_expect_none():
13+
assert datetime_from_str(None) is None
14+
15+
16+
def test_datetime_from_str_empty_string_expect_none():
17+
assert datetime_from_str('') is None
18+
19+
20+
def test_datetime_from_str_empty_string_not_trimmed_expect_none():
21+
assert datetime_from_str(' \t\n') is None
22+
23+
24+
def test_datetime_from_str_date_without_time():
25+
assert datetime_from_str('2024-04-06') == DATETIME_NO_TIME
26+
27+
28+
def test_datetime_from_str_datetime_no_fractional_seconds_no_timezone():
29+
assert datetime_from_str('2024-04-06T13:30:45') == DATETIME_TIME_NO_FRACTIONAL
30+
31+
32+
def test_datetime_from_str_datetime_no_fractional_seconds_utc_z_timezone():
33+
assert datetime_from_str('2024-04-06T13:30:45Z') == DATETIME_TIME_NO_FRACTIONAL
34+
35+
36+
def test_datetime_from_str_datetime_no_fractional_seconds_utc_offset_timezone():
37+
assert datetime_from_str('2024-04-06T13:30:45+00:00') == DATETIME_TIME_NO_FRACTIONAL
38+
39+
40+
def test_datetime_from_str_datetime_0_digits_fractional_seconds_no_timezone():
41+
assert datetime_from_str('2024-04-06T13:30:45.0') == DATETIME_TIME_NO_FRACTIONAL
42+
43+
44+
def test_datetime_from_str_datetime_0_digits_fractional_utc_z_timezone():
45+
assert datetime_from_str('2024-04-06T13:30:45.0Z') == DATETIME_TIME_NO_FRACTIONAL
46+
47+
48+
def test_datetime_from_str_datetime_0_digits_fractional_utc_offset_timezone():
49+
assert datetime_from_str('2024-04-06T13:30:45.0+00:00') == DATETIME_TIME_NO_FRACTIONAL
50+
51+
52+
def test_datetime_from_str_datetime_no_timezone():
53+
assert datetime_from_str('2024-04-06T13:30:45.123456') == DATETIME_TIME_WITH_FRACTIONAL_LONG
54+
55+
56+
def test_datetime_from_str_datetime_utc_z_timezone():
57+
assert datetime_from_str('2024-04-06T13:30:45.123456Z') == DATETIME_TIME_WITH_FRACTIONAL_LONG
58+
59+
60+
def test_datetime_from_str_datetime_utc_offset_timezone():
61+
assert datetime_from_str('2024-04-06T13:30:45.123456+00:00') == DATETIME_TIME_WITH_FRACTIONAL_LONG
62+
63+
64+
def test_datetime_from_str_datetime_short_fractional_seconds_no_timezone():
65+
assert datetime_from_str('2024-04-06T13:30:45.123') == DATETIME_TIME_WITH_FRACTIONAL_SHORT
66+
67+
68+
def test_datetime_from_str_datetime_short_fractional_seconds_utc_z_timezone():
69+
assert datetime_from_str('2024-04-06T13:30:45.123Z') == DATETIME_TIME_WITH_FRACTIONAL_SHORT
70+
71+
72+
def test_datetime_from_str_datetime_short_fractional_seconds_utc_offset_timezone():
73+
assert datetime_from_str('2024-04-06T13:30:45.123+00:00') == DATETIME_TIME_WITH_FRACTIONAL_SHORT
74+
75+
76+
def test_datetime_from_str_datetime_7_digits_fractional_seconds_no_timezone():
77+
assert datetime_from_str('2024-04-06T13:30:45.1234567') == DATETIME_TIME_WITH_FRACTIONAL_LONG
78+
79+
80+
def test_datetime_from_str_datetime_7_digits_fractional_seconds_utc_z_timezone():
81+
assert datetime_from_str('2024-04-06T13:30:45.1234567Z') == DATETIME_TIME_WITH_FRACTIONAL_LONG
82+
83+
84+
def test_datetime_from_str_datetime_7_digits_fractional_seconds_utc_offset_timezone():
85+
assert datetime_from_str('2024-04-06T13:30:45.1234567+00:00') == DATETIME_TIME_WITH_FRACTIONAL_LONG
86+
87+
88+
def test_datetime_to_str_no_time_no_time():
89+
assert datetime_to_str(DATETIME_NO_TIME) == '2024-04-06T00:00:00.000000Z'
90+
91+
92+
def test_datetime_to_str_no_fractional_seconds():
93+
assert datetime_to_str(DATETIME_TIME_NO_FRACTIONAL) == '2024-04-06T13:30:45.000000Z'
94+
95+
96+
def test_datetime_to_str_short_fractional_seconds():
97+
assert datetime_to_str(DATETIME_TIME_WITH_FRACTIONAL_SHORT) == '2024-04-06T13:30:45.123000Z'
98+
99+
100+
def test_datetime_to_str_long_fractional_seconds():
101+
assert datetime_to_str(DATETIME_TIME_WITH_FRACTIONAL_LONG) == '2024-04-06T13:30:45.123456Z'
102+
103+
104+
def test_datetime_to_str_no_timezone():
105+
datetime_no_tz = DATETIME_TIME_WITH_FRACTIONAL_LONG.replace(tzinfo=None)
106+
assert datetime_to_str(datetime_no_tz) == '2024-04-06T13:30:45.123456Z'

0 commit comments

Comments
 (0)