CVS commit: pkgsrc/time/py-dateutil

"Thomas Klausner" <[email protected]>
Newsgroups gmane.os.netbsd.devel.pkgsrc.cvs
Message-ID <[email protected]>
Module Name:	pkgsrc
Committed By:	wiz
Date:		Fri Aug 21 12:17:33 UTC 2026

Modified Files:
	pkgsrc/time/py-dateutil: Makefile distinfo
Added Files:
	pkgsrc/time/py-dateutil/patches: patch-src_dateutil_parser___parser.py
	    patch-src_dateutil_tz_tz.py patch-src_dateutil_zoneinfo_rebuild.py
	    patch-tests_test__tz.py

Log Message:
py-dateutil: fix timezone handling on NetBSD.

From upstream pull request (from 2020!) by Paul Eggert.

Bump PKGREVISION.


To generate a diff of this commit:
cvs rdiff -u -r1.41 -r1.42 pkgsrc/time/py-dateutil/Makefile
cvs rdiff -u -r1.22 -r1.23 pkgsrc/time/py-dateutil/distinfo
cvs rdiff -u -r0 -r1.1 \
    pkgsrc/time/py-dateutil/patches/patch-src_dateutil_parser___parser.py \
    pkgsrc/time/py-dateutil/patches/patch-src_dateutil_tz_tz.py \
    pkgsrc/time/py-dateutil/patches/patch-src_dateutil_zoneinfo_rebuild.py \
    pkgsrc/time/py-dateutil/patches/patch-tests_test__tz.py

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
(unnamed) (text/x-diff, 16.7 KB)
Modified files:

Index: pkgsrc/time/py-dateutil/Makefile
diff -u pkgsrc/time/py-dateutil/Makefile:1.41 pkgsrc/time/py-dateutil/Makefile:1.42
--- pkgsrc/time/py-dateutil/Makefile:1.41	Fri Aug 21 11:21:36 2026
+++ pkgsrc/time/py-dateutil/Makefile	Fri Aug 21 12:17:32 2026
@@ -1,8 +1,8 @@
-# $NetBSD: Makefile,v 1.41 2026/08/21 11:21:36 wiz Exp $
+# $NetBSD: Makefile,v 1.42 2026/08/21 12:17:32 wiz Exp $
 
 DISTNAME=	python-dateutil-2.9.0.post0
 PKGNAME=	${PYPKGPREFIX}-${DISTNAME:S/python-//:S/post/0.0./}
-PKGREVISION=	2
+PKGREVISION=	3
 CATEGORIES=	time python
 MASTER_SITES=	${MASTER_SITE_PYPI:=p/python-dateutil/}
 

Index: pkgsrc/time/py-dateutil/distinfo
diff -u pkgsrc/time/py-dateutil/distinfo:1.22 pkgsrc/time/py-dateutil/distinfo:1.23
--- pkgsrc/time/py-dateutil/distinfo:1.22	Fri Aug 21 11:21:36 2026
+++ pkgsrc/time/py-dateutil/distinfo	Fri Aug 21 12:17:32 2026
@@ -1,6 +1,10 @@
-$NetBSD: distinfo,v 1.22 2026/08/21 11:21:36 wiz Exp $
+$NetBSD: distinfo,v 1.23 2026/08/21 12:17:32 wiz Exp $
 
 BLAKE2s (python-dateutil-2.9.0.post0.tar.gz) = 853dce8ffa2bb628e0d82444793adb394e9962ddca529ce1dea814e30384b416
 SHA512 (python-dateutil-2.9.0.post0.tar.gz) = f76522de0ff21547327eaf6966e80a15c57f8f92588d520eabd354a732e5c4b51d9c3ac5effd9eaa6dd451d1bce329a54a3f4c6bf4f1bd08ff06b0305c994e5a
 Size (python-dateutil-2.9.0.post0.tar.gz) = 342432 bytes
+SHA1 (patch-src_dateutil_parser___parser.py) = fe00859ca998812df85ba5e800f5c673cf38987b
+SHA1 (patch-src_dateutil_tz_tz.py) = c14680fe7347d5b5e3d507f064728bdd4df55a62
+SHA1 (patch-src_dateutil_zoneinfo_rebuild.py) = d01e7abce705dffb1bd7624099cfe5e3f4340fb7
 SHA1 (patch-tests_test__isoparser.py) = d4cee0a18d24001746ad65ce5bad1f72ad253e7b
+SHA1 (patch-tests_test__tz.py) = 896c50c2739a725acf6c380592f8ec9b0b5a22a2

Added files:

Index: pkgsrc/time/py-dateutil/patches/patch-src_dateutil_parser___parser.py
diff -u /dev/null pkgsrc/time/py-dateutil/patches/patch-src_dateutil_parser___parser.py:1.1
--- /dev/null	Fri Aug 21 12:17:33 2026
+++ pkgsrc/time/py-dateutil/patches/patch-src_dateutil_parser___parser.py	Fri Aug 21 12:17:32 2026
@@ -0,0 +1,79 @@
+$NetBSD: patch-src_dateutil_parser___parser.py,v 1.1 2026/08/21 12:17:32 wiz Exp $
+
+Support Internet RFC 8536 TZif files
+https://github.com/dateutil/dateutil/pull/1091
+
+--- src/dateutil/parser/_parser.py.orig	2024-03-01 18:35:19.000000000 +0000
++++ src/dateutil/parser/_parser.py
+@@ -1389,7 +1389,8 @@ class _tzparser(object):
+ 
+     def parse(self, tzstr):
+         res = self._result()
+-        l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x]
++        token_re = r'([,:./+-]|[a-zA-Z]+|[0-9]+|<[a-zA-Z0-9+-]+>)'
++        l = [x for x in re.split(token_re, tzstr) if x]
+         used_idxs = list()
+         try:
+ 
+@@ -1398,20 +1399,25 @@ class _tzparser(object):
+             i = 0
+             while i < len_l:
+                 # BRST+3[BRDT[+2]]
++                abbr = ""
+                 j = i
+-                while j < len_l and not [x for x in l[j]
+-                                         if x in "0123456789:,-+"]:
++                while j < len_l and (l[j][0] == '<'
++                                     or not [x for x in l[j]
++                                             if x in "0123456789:,-+"]):
++                    if l[j][0] == '<':
++                        abbr += l[j][1:-1]
++                    else:
++                        abbr += l[j]
++                    used_idxs.append(j)
+                     j += 1
+                 if j != i:
+                     if not res.stdabbr:
+                         offattr = "stdoffset"
+-                        res.stdabbr = "".join(l[i:j])
++                        res.stdabbr = abbr
+                     else:
+                         offattr = "dstoffset"
+-                        res.dstabbr = "".join(l[i:j])
++                        res.dstabbr = abbr
+ 
+-                    for ii in range(j):
+-                        used_idxs.append(ii)
+                     i = j
+                     if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
+                                        "0123456789")):
+@@ -1505,7 +1511,7 @@ class _tzparser(object):
+                      'TZ variable format.', tz.DeprecatedTzFormatWarning)
+             elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
+                   not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
+-                                                     '.', '-', ':')
++                                                     '.', '-', '+', ':')
+                        for y in x if y not in "0123456789"]):
+                 for x in (res.start, res.end):
+                     if l[i] == 'J':
+@@ -1542,6 +1548,12 @@ class _tzparser(object):
+                     if i < len_l and l[i] == '/':
+                         used_idxs.append(i)
+                         i += 1
++                        if l[i] in ('+', '-'):
++                            signal = (-1, 1)[l[i] == '+']
++                            used_idxs.append(i)
++                            i += 1
++                        else:
++                            signal = 1
+                         # start time
+                         len_li = len(l[i])
+                         if len_li == 4:
+@@ -1562,6 +1574,7 @@ class _tzparser(object):
+                             x.time = (int(l[i][:2]) * 3600)
+                         else:
+                             return None
++                        x.time *= signal
+                         used_idxs.append(i)
+                         i += 1
+ 
Index: pkgsrc/time/py-dateutil/patches/patch-src_dateutil_tz_tz.py
diff -u /dev/null pkgsrc/time/py-dateutil/patches/patch-src_dateutil_tz_tz.py:1.1
--- /dev/null	Fri Aug 21 12:17:33 2026
+++ pkgsrc/time/py-dateutil/patches/patch-src_dateutil_tz_tz.py	Fri Aug 21 12:17:32 2026
@@ -0,0 +1,228 @@
+$NetBSD: patch-src_dateutil_tz_tz.py,v 1.1 2026/08/21 12:17:32 wiz Exp $
+
+Support Internet RFC 8536 TZif files
+https://github.com/dateutil/dateutil/pull/1091
+
+--- src/dateutil/tz/tz.py.orig	2024-03-01 18:35:19.000000000 +0000
++++ src/dateutil/tz/tz.py
+@@ -376,7 +376,8 @@ class _tzfile(object):
+     information read from binary tzfiles.
+     """
+     attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
+-             'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']
++             'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first',
++             'tz_string_info']
+ 
+     def __init__(self, **kwargs):
+         for attr in self.attrs:
+@@ -486,8 +487,6 @@ class tzfile(_tzinfo):
+             setattr(self, '_' + attr, getattr(tzobj, attr))
+ 
+     def _read_tzfile(self, fileobj):
+-        out = _tzfile()
+-
+         # From tzfile(5):
+         #
+         # The time zone information files used by tzset(3)
+@@ -500,8 +499,22 @@ class tzfile(_tzinfo):
+         if fileobj.read(4).decode() != "TZif":
+             raise ValueError("magic not found")
+ 
+-        fileobj.read(16)
++        version = fileobj.read(1).decode()
++        fileobj.read(15)
+ 
++        out = self._read_data_block(fileobj, 4, 'l')
++        if version < '2':
++            return out
++        fileobj.read(20)
++        out = self._read_data_block(fileobj, 8, 'q')
++        fileobj.read(1)
++        tz_string = fileobj.readline()[:-1].decode()
++        out.tz_string_info = tzstr(tz_string, posix_offset=True)
++        return out
++
++    def _read_data_block(self, fileobj, time_size, format_char):
++        out = _tzfile()
++
+         (
+             # The number of UTC/local indicators stored in the file.
+             ttisgmtcnt,
+@@ -527,16 +540,18 @@ class tzfile(_tzinfo):
+ 
+         ) = struct.unpack(">6l", fileobj.read(24))
+ 
+-        # The above header is followed by tzh_timecnt four-byte
+-        # values  of  type long,  sorted  in ascending order.
++        # The above header is followed by tzh_timecnt TIME_SIZE-byte
++        # signed integer values, sorted in ascending order.
+         # These values are written in ``standard'' byte order.
+         # Each is used as a transition time (as  returned  by
+         # time(2)) at which the rules for computing local time
+         # change.
+ 
+         if timecnt:
+-            out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
+-                                                    fileobj.read(timecnt*4)))
++            format = ">%d%s" % (timecnt, format_char)
++            size = timecnt * time_size
++            out.trans_list_utc = list(struct.unpack(format,
++                                                    fileobj.read(size)))
+         else:
+             out.trans_list_utc = []
+ 
+@@ -571,18 +586,18 @@ class tzfile(_tzinfo):
+ 
+         abbr = fileobj.read(charcnt).decode()
+ 
+-        # Then there are tzh_leapcnt pairs of four-byte
++        # Then there are tzh_leapcnt pairs of
+         # values, written in  standard byte  order;  the
+-        # first  value  of  each pair gives the time (as
++        # first (TIME_SIZE-byte) value of each pair gives the time (as
+         # returned by time(2)) at which a leap second
+-        # occurs;  the  second  gives the  total  number of
++        # occurs; the second (4-byte) value gives the total number of
+         # leap seconds to be applied after the given time.
+         # The pairs of values are sorted in ascending order
+         # by time.
+ 
+         # Not used, for now (but seek for correct file position)
+         if leapcnt:
+-            fileobj.seek(leapcnt * 8, os.SEEK_CUR)
++            fileobj.seek(leapcnt * (time_size + 4), os.SEEK_CUR)
+ 
+         # Then there are tzh_ttisstdcnt standard/wall
+         # indicators, each stored as a one-byte value;
+@@ -712,6 +727,8 @@ class tzfile(_tzinfo):
+     def _find_last_transition(self, dt, in_utc=False):
+         # If there's no list, there are no transitions to find
+         if not self._trans_list:
++            if self._tz_string_info:
++                return -2
+             return None
+ 
+         timestamp = _datetime_to_timestamp(dt)
+@@ -721,6 +738,8 @@ class tzfile(_tzinfo):
+         trans_list = self._trans_list_utc if in_utc else self._trans_list
+         idx = bisect.bisect_right(trans_list, timestamp)
+ 
++        if idx == len(trans_list) and self._tz_string_info:
++            return -2
+         # We want to know when the previous transition was, so subtract off 1
+         return idx - 1
+ 
+@@ -735,11 +754,6 @@ class tzfile(_tzinfo):
+ 
+         return self._trans_idx[idx]
+ 
+-    def _find_ttinfo(self, dt):
+-        idx = self._resolve_ambiguous_time(dt)
+-
+-        return self._get_ttinfo(idx)
+-
+     def fromutc(self, dt):
+         """
+         The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
+@@ -768,12 +782,18 @@ class tzfile(_tzinfo):
+ 
+         # First treat UTC as wall time and get the transition we're in.
+         idx = self._find_last_transition(dt, in_utc=True)
+-        tti = self._get_ttinfo(idx)
+ 
+-        dt_out = dt + datetime.timedelta(seconds=tti.offset)
++        # For a timestamp in indefinite future, use the TZ string if available.
++        if idx == -2:
++            tzinfo = self._tz_string_info
++            dt_out = tzinfo.fromutc(dt.replace(tzinfo=tzinfo))
++            fold = tzinfo._fold(dt_out)
++            dt_out = dt_out.replace(tzinfo=self)
++        else:
++            tti = self._get_ttinfo(idx)
++            dt_out = dt + datetime.timedelta(seconds=tti.offset)
++            fold = self.is_ambiguous(dt_out, idx=idx)
+ 
+-        fold = self.is_ambiguous(dt_out, idx=idx)
+-
+         return enfold(dt_out, fold=int(fold))
+ 
+     def is_ambiguous(self, dt, idx=None):
+@@ -793,6 +813,10 @@ class tzfile(_tzinfo):
+         if idx is None:
+             idx = self._find_last_transition(dt)
+ 
++        # For a timestamp in indefinite future, use the TZ string if available.
++        if idx == -2:
++            return self._tz_string_info.is_ambiguous(dt)
++
+         # Calculate the difference in offsets from current to previous
+         timestamp = _datetime_to_timestamp(dt)
+         tti = self._get_ttinfo(idx)
+@@ -809,11 +833,11 @@ class tzfile(_tzinfo):
+         idx = self._find_last_transition(dt)
+ 
+         # If we have no transitions, return the index
+-        _fold = self._fold(dt)
+-        if idx is None or idx == 0:
++        if idx is None or idx <= 0:
+             return idx
+ 
+         # If it's ambiguous and we're in a fold, shift to a different index.
++        _fold = self._fold(dt)
+         idx_offset = int(not _fold and self.is_ambiguous(dt, idx))
+ 
+         return idx - idx_offset
+@@ -822,19 +846,27 @@ class tzfile(_tzinfo):
+         if dt is None:
+             return None
+ 
++        idx = self._resolve_ambiguous_time(dt)
++        if idx == -2:
++            return self._tz_string_info.utcoffset(dt)
++
+         if not self._ttinfo_std:
+             return ZERO
+ 
+-        return self._find_ttinfo(dt).delta
++        return self._get_ttinfo(idx).delta
+ 
+     def dst(self, dt):
+         if dt is None:
+             return None
+ 
++        idx = self._resolve_ambiguous_time(dt)
++        if idx == -2:
++            return self._tz_string_info.dst(dt)
++
+         if not self._ttinfo_dst:
+             return ZERO
+ 
+-        tti = self._find_ttinfo(dt)
++        tti = self._get_ttinfo(idx)
+ 
+         if not tti.isdst:
+             return ZERO
+@@ -845,15 +877,21 @@ class tzfile(_tzinfo):
+ 
+     @tzname_in_python2
+     def tzname(self, dt):
+-        if not self._ttinfo_std or dt is None:
++        if dt is None:
+             return None
+-        return self._find_ttinfo(dt).abbr
++        idx = self._resolve_ambiguous_time(dt)
++        if idx == -2:
++            return self._tz_string_info.tzname(dt)
++        if not self._ttinfo_std:
++            return None
++        return self._get_ttinfo(idx).abbr
+ 
+     def __eq__(self, other):
+         if not isinstance(other, tzfile):
+             return NotImplemented
+         return (self._trans_list == other._trans_list and
+                 self._trans_idx == other._trans_idx and
++                self._tz_string_info == other._tz_string_info and
+                 self._ttinfo_list == other._ttinfo_list)
+ 
+     __hash__ = None
Index: pkgsrc/time/py-dateutil/patches/patch-src_dateutil_zoneinfo_rebuild.py
diff -u /dev/null pkgsrc/time/py-dateutil/patches/patch-src_dateutil_zoneinfo_rebuild.py:1.1
--- /dev/null	Fri Aug 21 12:17:33 2026
+++ pkgsrc/time/py-dateutil/patches/patch-src_dateutil_zoneinfo_rebuild.py	Fri Aug 21 12:17:32 2026
@@ -0,0 +1,46 @@
+$NetBSD: patch-src_dateutil_zoneinfo_rebuild.py,v 1.1 2026/08/21 12:17:32 wiz Exp $
+
+Support Internet RFC 8536 TZif files
+https://github.com/dateutil/dateutil/pull/1091
+
+--- src/dateutil/zoneinfo/rebuild.py.orig	2024-03-01 18:35:19.000000000 +0000
++++ src/dateutil/zoneinfo/rebuild.py
+@@ -3,7 +3,7 @@ import json
+ import tempfile
+ import shutil
+ import json
+-from subprocess import check_call, check_output
++from subprocess import check_call
+ from tarfile import TarFile
+ 
+ from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
+@@ -39,27 +39,14 @@ def _run_zic(zonedir, filepaths):
+ 
+ 
+ def _run_zic(zonedir, filepaths):
+-    """Calls the ``zic`` compiler in a compatible way to get a "fat" binary.
+-
+-    Recent versions of ``zic`` default to ``-b slim``, while older versions
+-    don't even have the ``-b`` option (but default to "fat" binaries). The
+-    current version of dateutil does not support Version 2+ TZif files, which
+-    causes problems when used in conjunction with "slim" binaries, so this
+-    function is used to ensure that we always get a "fat" binary.
++    """Calls the ``zic`` compiler.
+     """
+ 
+     try:
+-        help_text = check_output(["zic", "--help"])
++        check_call(["zic", "-d", zonedir] + filepaths)
+     except OSError as e:
+         _print_on_nosuchfile(e)
+         raise
+-
+-    if b"-b " in help_text:
+-        bloat_args = ["-b", "fat"]
+-    else:
+-        bloat_args = []
+-
+-    check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths)
+ 
+ 
+ def _print_on_nosuchfile(e):
Index: pkgsrc/time/py-dateutil/patches/patch-tests_test__tz.py
diff -u /dev/null pkgsrc/time/py-dateutil/patches/patch-tests_test__tz.py:1.1
--- /dev/null	Fri Aug 21 12:17:33 2026
+++ pkgsrc/time/py-dateutil/patches/patch-tests_test__tz.py	Fri Aug 21 12:17:32 2026
@@ -0,0 +1,16 @@
+$NetBSD: patch-tests_test__tz.py,v 1.1 2026/08/21 12:17:32 wiz Exp $
+
+Support Internet RFC 8536 TZif files
+https://github.com/dateutil/dateutil/pull/1091
+
+--- tests/test_tz.py.orig	2024-03-01 18:35:19.000000000 +0000
++++ tests/test_tz.py
+@@ -1642,7 +1642,7 @@ def test_valid_dateutil_format(tz_str, expected):
+     'hdfiughdfuig,dfughdfuigpu87ñ::',
+     ',dfughdfuigpu87ñ::',
+     '-1:WART4WARST,J1,J365/25',
+-    'WART4WARST,J1,J365/-25',
++    'WART4WARST,J1,J365/--25',
+     'IST-2IDT,M3.4.-1/26,M10.5.0',
+     'IST-2IDT,M3,2000,1/26,M10,5,0'
+ ])
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.