227 lines
9.7 KiB
Diff
227 lines
9.7 KiB
Diff
From bbda49b86f3841ac5100894da426edc541b6226c Mon Sep 17 00:00:00 2001
|
|
From: Viktor Ashirov <vashirov@redhat.com>
|
|
Date: Thu, 12 Feb 2026 09:15:18 +0100
|
|
Subject: [PATCH] Issue 7241 - Drop dateutil (#7242)
|
|
|
|
Bug Description:
|
|
python-dateutil is unmaintained upstream and is marked for deprecation.
|
|
|
|
Fix Description:
|
|
* Replace `dateutil.tz.tzoffset` with `datetime.timezone(datetime.timedelta())`.
|
|
* Replace `dateutil.parser.parse` with standard `datetime` calls.
|
|
* Import `datetime` as `dt` to avoid confusion between module and class.
|
|
* Fix month lookup bug ('Oct': 9 / 'Sep': 10).
|
|
|
|
Fixes: https://github.com/389ds/389-ds-base/issues/7241
|
|
|
|
Reviewed by: jchapma, droideck (Thanks!)
|
|
---
|
|
.../suites/password/pwdPolicy_warning_test.py | 5 +-
|
|
src/lib389/lib389/dirsrv_log.py | 48 ++++++++++++-------
|
|
src/lib389/lib389/tests/dirsrv_log_test.py | 13 +++--
|
|
src/lib389/pyproject.toml | 2 -
|
|
src/lib389/requirements.txt | 1 -
|
|
5 files changed, 38 insertions(+), 31 deletions(-)
|
|
|
|
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
|
|
index 154ec01f1..2341da6eb 100644
|
|
--- a/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
|
|
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
|
|
@@ -15,9 +15,8 @@ from lib389.topologies import topology_st
|
|
from lib389.idm.user import UserAccounts
|
|
from lib389.idm.organizationalunit import OrganizationalUnits
|
|
from lib389._constants import (DEFAULT_SUFFIX, DN_CONFIG, PASSWORD, DN_DM)
|
|
-from dateutil.parser import parse as dt_parse
|
|
from lib389.config import Config
|
|
-import datetime
|
|
+import datetime as dt
|
|
|
|
pytestmark = pytest.mark.tier1
|
|
|
|
@@ -351,7 +350,7 @@ def test_with_different_password_states(topology_st, global_policy, add_user):
|
|
old_ts = user.get_attr_val_utf8('passwordExpirationTime')
|
|
log.info("Old passwordExpirationTime: {}".format(old_ts))
|
|
|
|
- new_ts = (dt_parse(old_ts) - datetime.timedelta(31)).strftime('%Y%m%d%H%M%SZ')
|
|
+ new_ts = (dt.datetime.strptime(old_ts, '%Y%m%d%H%M%SZ') - dt.timedelta(31)).strftime('%Y%m%d%H%M%SZ')
|
|
log.info("New passwordExpirationTime: {}".format(new_ts))
|
|
user.replace('passwordExpirationTime', new_ts)
|
|
|
|
diff --git a/src/lib389/lib389/dirsrv_log.py b/src/lib389/lib389/dirsrv_log.py
|
|
index e40105ad3..3d923a8bf 100644
|
|
--- a/src/lib389/lib389/dirsrv_log.py
|
|
+++ b/src/lib389/lib389/dirsrv_log.py
|
|
@@ -10,11 +10,11 @@
|
|
"""
|
|
|
|
import copy
|
|
+import datetime as dt
|
|
import json
|
|
import glob
|
|
import re
|
|
import gzip
|
|
-from dateutil.parser import parse as dt_parse
|
|
from lib389.utils import ensure_bytes
|
|
from lib389._mapped_object_lint import DSLint
|
|
from lib389.lint import (
|
|
@@ -34,8 +34,8 @@ MONTH_LOOKUP = {
|
|
'Jun': 6,
|
|
'Jul': 7,
|
|
'Aug': 8,
|
|
- 'Oct': 9,
|
|
- 'Sep': 10,
|
|
+ 'Sep': 9,
|
|
+ 'Oct': 10,
|
|
'Nov': 11,
|
|
'Dec': 12,
|
|
}
|
|
@@ -50,9 +50,9 @@ class DirsrvLog(DSLint):
|
|
"""
|
|
self.dirsrv = dirsrv
|
|
self.log = self.dirsrv.log
|
|
- self.prog_timestamp = re.compile(r'\[(?P<day>\d*)\/(?P<month>\w*)\/(?P<year>\d*):(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d*)') # noqa
|
|
+ self.prog_timestamp = re.compile(r'\[(?P<day>\d*)\/(?P<month>\w*)\/(?P<year>\d*):(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d{4})') # noqa
|
|
# JSON timestamp uses strftime %FT%T --> 2025-02-12T17:00:47.663123181 -0500
|
|
- self.prog_json_timestamp = re.compile(r'(?P<year>\d*)-(?P<month>\w*)-(?P<day>\d*)T(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d*)') # noqa
|
|
+ self.prog_json_timestamp = re.compile(r'(?P<year>\d*)-(?P<month>\w*)-(?P<day>\d*)T(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d{4})') # noqa
|
|
self.prog_datetime = re.compile(r'^(?P<timestamp>\[.*\])')
|
|
self.jsonFormat = False
|
|
|
|
@@ -157,20 +157,32 @@ class DirsrvLog(DSLint):
|
|
else:
|
|
timedata = self.prog_timestamp.match(ts).groupdict()
|
|
|
|
- # Now, have to convert month to an int.
|
|
- dt_str = '{YEAR}-{MONTH}-{DAY} {HOUR}-{MINUTE}-{SECOND} {TZ}'.format(
|
|
- YEAR=timedata['year'],
|
|
- MONTH=timedata['month'],
|
|
- DAY=timedata['day'],
|
|
- HOUR=timedata['hour'],
|
|
- MINUTE=timedata['minute'],
|
|
- SECOND=timedata['second'],
|
|
- TZ=timedata['tz'],
|
|
- )
|
|
- dt = dt_parse(dt_str)
|
|
+ # Convert month to an int.
|
|
+ month = timedata['month']
|
|
+ if not month.isdigit():
|
|
+ month = MONTH_LOOKUP[month]
|
|
+ else:
|
|
+ month = int(month)
|
|
+
|
|
+ # Parse timezone offset string (e.g. "+1000" or "-0500") into a timezone
|
|
+ tz_str = timedata['tz']
|
|
+ tz_sign = 1 if tz_str[0] == '+' else -1
|
|
+ tz_hours = int(tz_str[1:3])
|
|
+ tz_minutes = int(tz_str[3:5])
|
|
+ tz = dt.timezone(dt.timedelta(hours=tz_sign * tz_hours, minutes=tz_sign * tz_minutes))
|
|
+
|
|
+ parsed_dt = dt.datetime(
|
|
+ int(timedata['year']),
|
|
+ month,
|
|
+ int(timedata['day']),
|
|
+ int(timedata['hour']),
|
|
+ int(timedata['minute']),
|
|
+ int(timedata['second']),
|
|
+ tzinfo=tz
|
|
+ )
|
|
if timedata['nanosecond']:
|
|
- dt = dt.replace(microsecond=int(int(timedata['nanosecond']) / 1000))
|
|
- return dt
|
|
+ parsed_dt = parsed_dt.replace(microsecond=int(timedata['nanosecond']) // 1000)
|
|
+ return parsed_dt
|
|
|
|
def get_time_in_secs(self, log_line):
|
|
"""Take the timestamp (not the date) from a DS access log and convert
|
|
diff --git a/src/lib389/lib389/tests/dirsrv_log_test.py b/src/lib389/lib389/tests/dirsrv_log_test.py
|
|
index 920e67a01..d0259ced9 100644
|
|
--- a/src/lib389/lib389/tests/dirsrv_log_test.py
|
|
+++ b/src/lib389/lib389/tests/dirsrv_log_test.py
|
|
@@ -12,8 +12,7 @@ from lib389 import DirSrv, Entry
|
|
import pytest
|
|
import time
|
|
import shutil
|
|
-import datetime
|
|
-from dateutil.tz import tzoffset
|
|
+import datetime as dt
|
|
|
|
INSTANCE_PORT = 54321
|
|
INSTANCE_SERVERID = 'standalone'
|
|
@@ -74,7 +73,7 @@ def test_access_log(topology):
|
|
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.726093186 +1000] conn=1 fd=64 slot=64 connection from ::1 to ::1') ==
|
|
{
|
|
'slot': '64', 'remote': '::1', 'action': 'CONNECT', 'timestamp': '[27/Apr/2016:12:49:49.726093186 +1000]', 'fd': '64', 'conn': '1', 'local': '::1',
|
|
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 726093, tzinfo=tzoffset(None, 36000))
|
|
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 726093, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
|
}
|
|
)
|
|
assert(
|
|
@@ -82,21 +81,21 @@ def test_access_log(topology):
|
|
{
|
|
'rem': 'base="cn=config" scope=0 filter="(objectClass=*)" attrs="nsslapd-instancedir nsslapd-errorlog nsslapd-accesslog nsslapd-auditlog nsslapd-certdir nsslapd-schemadir nsslapd-bakdir nsslapd-ldifdir"', # noqa
|
|
'action': 'SRCH', 'timestamp': '[27/Apr/2016:12:49:49.727235997 +1000]', 'conn': '1', 'op': '2',
|
|
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 727235, tzinfo=tzoffset(None, 36000))
|
|
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 727235, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
|
}
|
|
)
|
|
assert(
|
|
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.736297002 +1000] conn=1 op=4 fd=64 closed - U1') ==
|
|
{
|
|
'status': 'U1', 'fd': '64', 'action': 'DISCONNECT', 'timestamp': '[27/Apr/2016:12:49:49.736297002 +1000]', 'conn': '1', 'op': '4',
|
|
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 736297, tzinfo=tzoffset(None, 36000))
|
|
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 736297, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
|
}
|
|
)
|
|
assert(
|
|
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.736297002 -1000] conn=1 op=4 fd=64 closed - U1') ==
|
|
{
|
|
'status': 'U1', 'fd': '64', 'action': 'DISCONNECT', 'timestamp': '[27/Apr/2016:12:49:49.736297002 -1000]', 'conn': '1', 'op': '4',
|
|
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 736297, tzinfo=tzoffset(None, -36000))
|
|
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 736297, tzinfo=dt.timezone(dt.timedelta(seconds=-36000)))
|
|
}
|
|
)
|
|
|
|
@@ -113,7 +112,7 @@ def test_error_log(topology):
|
|
topology.standalone.ds_error_log.parse_line('[27/Apr/2016:13:46:35.775670167 +1000] slapd started. Listening on All Interfaces port 54321 for LDAP requests') == # noqa
|
|
{
|
|
'timestamp': '[27/Apr/2016:13:46:35.775670167 +1000]', 'message': 'slapd started. Listening on All Interfaces port 54321 for LDAP requests',
|
|
- 'datetime': datetime.datetime(2016, 4, 27, 13, 0, 0, 775670, tzinfo=tzoffset(None, 36000))
|
|
+ 'datetime': dt.datetime(2016, 4, 27, 13, 46, 35, 775670, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
|
|
}
|
|
)
|
|
|
|
diff --git a/src/lib389/pyproject.toml b/src/lib389/pyproject.toml
|
|
index 63c7c9710..e067d1590 100644
|
|
--- a/src/lib389/pyproject.toml
|
|
+++ b/src/lib389/pyproject.toml
|
|
@@ -5,7 +5,6 @@ requires = [
|
|
"argparse-manpage[setuptools]",
|
|
"pyasn1",
|
|
"pyasn1-modules",
|
|
- "python-dateutil",
|
|
"argcomplete",
|
|
"python-ldap",
|
|
"distro",
|
|
@@ -43,7 +42,6 @@ classifiers = [
|
|
dependencies = [
|
|
"pyasn1",
|
|
"pyasn1-modules",
|
|
- "python-dateutil",
|
|
"argcomplete",
|
|
"python-ldap",
|
|
"distro",
|
|
diff --git a/src/lib389/requirements.txt b/src/lib389/requirements.txt
|
|
index 5e1b3dad7..94b10e3c2 100644
|
|
--- a/src/lib389/requirements.txt
|
|
+++ b/src/lib389/requirements.txt
|
|
@@ -1,6 +1,5 @@
|
|
pyasn1
|
|
pyasn1-modules
|
|
-python-dateutil
|
|
argcomplete
|
|
argparse-manpage
|
|
python-ldap
|
|
--
|
|
2.52.0
|
|
|