389-ds-base/0041-Issue-7241-Drop-dateutil-7242.patch
2026-07-21 07:46:19 -04:00

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