diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d6052a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/pyRFC3339-1.0.tar.gz +/pyRFC3339-1.1.tar.gz +/pyrfc3339-2.0.1.tar.gz diff --git a/dead.package b/dead.package deleted file mode 100644 index a72aec0..0000000 --- a/dead.package +++ /dev/null @@ -1 +0,0 @@ -epel8-playground decommissioned : https://pagure.io/epel/issue/136 diff --git a/pyrfc3339-use-pytest-in-tests.patch b/pyrfc3339-use-pytest-in-tests.patch new file mode 100644 index 0000000..c216f53 --- /dev/null +++ b/pyrfc3339-use-pytest-in-tests.patch @@ -0,0 +1,195 @@ +commit a863d1a3c90944b90685680af4dad5d8c7dbc0da +Author: Matthew Davis +Date: Fri Jan 28 16:42:42 2022 -0500 + + Converted tests from python-nose to pytest + +diff --git a/tests.py b/tests.py +index 831daab..d4c7622 100644 +--- a/tests.py ++++ b/tests.py +@@ -8,12 +8,11 @@ from copy import deepcopy + + from pyrfc3339 import generate, parse + from pyrfc3339.utils import timezone ++import unittest ++import pytest + import pytz + +-from nose.tools import eq_, raises +- +- +-class TestCore(): ++class TestCore(unittest.TestCase): + ''' + This test suite contains tests to address cases not tested in the doctests, + as well as additional tests for end-to-end verification. +@@ -24,8 +23,11 @@ class TestCore(): + Test rounding of timezone values to the nearest second. + + ''' +- eq_(timezone(5429), '+01:30') +- eq_(timezone(5431), '+01:31') ++ if not timezone(5429) == '+01:30': ++ raise AssertionError("%r != %r" % (timezone(5429), '+01:30')) ++ ++ if not timezone(5431) == '+01:31': ++ raise AssertionError("%r != %r" % (timezone(5431), '+01:31')) + + def test_zero_offset(self): + ''' +@@ -34,11 +36,13 @@ class TestCore(): + ''' + timestamp = '2009-01-01T10:02:03+00:00' + dt = parse(timestamp) +- eq_(dt.tzinfo, pytz.utc) ++ if not dt.tzinfo == pytz.utc: ++ raise AssertionError("%r != %r" % (dt.tzinfo, pytz.utc)) + + timestamp = '2009-01-01T10:02:03-00:00' + dt = parse(timestamp) +- eq_(dt.tzinfo, pytz.utc) ++ if not dt.tzinfo == pytz.utc: ++ raise AssertionError("%r != %r" % (dt.tzinfo, pytz.utc)) + + def test_deepcopy(self): + ''' +@@ -56,7 +60,8 @@ class TestCore(): + ''' + timestamp = '2009-01-01T10:02:03.25Z' + dt = parse(timestamp) +- eq_(dt.microsecond, 250000) ++ if not dt.microsecond == 250000: ++ raise AssertionError("%r != %r" % (dt.microsecond, 250000)) + + def test_generate_microseconds(self): + ''' +@@ -65,7 +70,8 @@ class TestCore(): + ''' + dt = datetime(2009, 1, 1, 10, 2, 3, 500000, pytz.utc) + timestamp = generate(dt, microseconds=True) +- eq_(timestamp, '2009-01-01T10:02:03.500000Z') ++ if not timestamp == '2009-01-01T10:02:03.500000Z': ++ raise AssertionError("%r != %r" % (timestamp, '2009-01-01T10:02:03.500000Z')) + + def test_mixed_case(self): + ''' +@@ -76,7 +82,8 @@ class TestCore(): + dt1 = parse('2009-01-01t10:01:02z') + dt2 = datetime(2009, 1, 1, 10, 1, 2, tzinfo=pytz.utc) + +- eq_(dt1, dt2) ++ if not dt1 == dt2: ++ raise AssertionError("%r != %r" % (dt1, dt2)) + + def test_parse_naive_utc(self): + ''' +@@ -84,15 +91,17 @@ class TestCore(): + + ''' + dt1 = parse('2009-01-01T10:01:02Z', produce_naive=True) +- eq_(dt1.tzinfo, None) ++ if not dt1.tzinfo == None: ++ raise AssertionError("%r != %r" % (dt1.tzinfo, None)) + +- @raises(ValueError) + def test_parse_naive_local(self): + ''' + Test that parsing a local timestamp to a naive datetime fails. + + ''' +- parse('2009-01-01T10:01:02-04:00', produce_naive=True) ++ with self.assertRaises(ValueError) as context: ++ parse('2009-01-01T10:01:02-04:00', produce_naive=True) ++ + + def test_generate_utc_parse_utc(self): + ''' +@@ -103,7 +112,8 @@ class TestCore(): + dt1 = dt1.replace(tzinfo=pytz.utc) + + dt2 = parse(generate(dt1, microseconds=True)) +- eq_(dt1, dt2) ++ if not dt1 == dt2: ++ raise AssertionError("%r != %r" % (dt1, dt2)) + + def test_generate_local_parse_local(self): + ''' +@@ -113,7 +123,8 @@ class TestCore(): + eastern = pytz.timezone('US/Eastern') + dt1 = eastern.localize(datetime.utcnow()) + dt2 = parse(generate(dt1, utc=False, microseconds=True), utc=False) +- eq_(dt1, dt2) ++ if not dt1 == dt2: ++ raise AssertionError("%r != %r" % (dt1, dt2)) + + def test_generate_local_parse_utc(self): + ''' +@@ -123,10 +134,12 @@ class TestCore(): + eastern = pytz.timezone('US/Eastern') + dt1 = eastern.localize(datetime.utcnow()) + dt2 = parse(generate(dt1, utc=False, microseconds=True)) +- eq_(dt1, dt2) ++ if not dt1 == dt2: ++ raise AssertionError("%r != %r" % (dt1, dt2)) + + +-class TestExhaustiveRoundtrip(): ++@pytest.mark.parametrize('tz_name', pytz.all_timezones) ++class TestExhaustiveRoundtrip: + ''' + This test suite exhaustively tests parsing and generation by generating + a local RFC 3339 timestamp for every timezone supported by pytz, +@@ -135,36 +148,32 @@ class TestExhaustiveRoundtrip(): + + slow = True + +- def test_local_roundtrip(self): +- for tz_name in pytz.all_timezones: +- yield self.local_roundtrip, tz_name +- +- def local_roundtrip(self, tz_name): ++ def test_local_roundtrip(self, tz_name): + ''' + Generates a local datetime using the given timezone, + produces a local timestamp from the datetime, parses the timestamp + to a local datetime, and verifies that the two datetimes are equal. + + ''' +- tzinfo = pytz.timezone(tz_name) +- dt1 = tzinfo.localize(datetime.utcnow()) +- timestamp = generate(dt1, utc=False, microseconds=True) +- dt2 = parse(timestamp, utc=False) +- eq_(dt1, dt2) +- +- def test_utc_roundtrip(self): +- for tz_name in pytz.all_timezones: +- yield self.utc_roundtrip, tz_name ++ if not tz_name == 'leapseconds': ++ tzinfo = pytz.timezone(tz_name) ++ dt1 = tzinfo.localize(datetime.utcnow()) ++ timestamp = generate(dt1, utc=False, microseconds=True) ++ dt2 = parse(timestamp, utc=False) ++ if not dt1 == dt2: ++ raise AssertionError("%r != %r" % (dt1, dt2)) + +- def utc_roundtrip(self, tz_name): ++ def test_utc_roundtrip(self, tz_name): + ''' + Generates a local datetime using the given timezone, + produces a local timestamp from the datetime, parses the timestamp + to a UTC datetime, and verifies that the two datetimes are equal. + + ''' +- tzinfo = pytz.timezone(tz_name) +- dt1 = tzinfo.localize(datetime.utcnow()) +- timestamp = generate(dt1, utc=False, microseconds=True) +- dt2 = parse(timestamp) +- eq_(dt1, dt2) ++ if not tz_name == 'leapseconds': ++ tzinfo = pytz.timezone(tz_name) ++ dt1 = tzinfo.localize(datetime.utcnow()) ++ timestamp = generate(dt1, utc=False, microseconds=True) ++ dt2 = parse(timestamp) ++ if not dt1 == dt2: ++ raise AssertionError("%r != %r" % (dt1, dt2)) diff --git a/python-pyrfc3339.spec b/python-pyrfc3339.spec new file mode 100644 index 0000000..8727d46 --- /dev/null +++ b/python-pyrfc3339.spec @@ -0,0 +1,192 @@ +%global srcname pyrfc3339 + +Name: python-pyrfc3339 +Version: 2.0.1 +Release: 6%{?dist} +Summary: Generate and parse RFC 3339 timestamps + +License: MIT +URL: https://pypi.python.org/pypi/pyRFC3339 +Source0: %{pypi_source} +# release tarballs do not contain unit tests (pyrfc3339/tests/tests.py) +# https://github.com/kurtraschke/pyRFC3339/blob/master/pyrfc3339/tests/test_all.py +# v2.0.1: git commit 53c2d1587d3a +Source1: https://raw.githubusercontent.com/kurtraschke/pyRFC3339/53c2d1587d3aac1734ddd4d4006a815df2d80f36/pyrfc3339/tests/test_all.py + +BuildArch: noarch + +BuildRequires: python3-devel +# --- unit tests --- +# Specified manually because upstream release tarballs do not contain unit tests +BuildRequires: python3-pytest + +%description +This package contains a python library to parse and generate +RFC 3339-compliant timestamps using Python datetime.datetime objects. + +%package -n python3-pyrfc3339 +Summary: Generate and parse RFC 3339 timestamps +%{?python_provide:%python_provide python3-pyrfc3339} + +%description -n python3-pyrfc3339 +This package contains a Python 3 library to parse and generate +RFC 3339-compliant timestamps using Python datetime.datetime objects. + +%generate_buildrequires +%pyproject_buildrequires + + +%prep +%autosetup -n %{srcname}-%{version} -N +cp -a %{SOURCE1} . + +%build +%pyproject_wheel + +%install +%pyproject_install +%pyproject_save_files pyrfc3339 + +%check +%pytest -v test_all.py + +%files -n python3-pyrfc3339 -f %{pyproject_files} +%doc README.rst + +%changelog +* Fri Sep 19 2025 Python Maint - 2.0.1-6 +- Rebuilt for Python 3.14.0rc3 bytecode + +* Fri Aug 15 2025 Python Maint - 2.0.1-5 +- Rebuilt for Python 3.14.0rc2 bytecode + +* Fri Jul 25 2025 Fedora Release Engineering - 2.0.1-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild + +* Mon Jun 02 2025 Python Maint - 2.0.1-3 +- Rebuilt for Python 3.14 + +* Sat Jan 18 2025 Fedora Release Engineering - 2.0.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild + +* Fri Jan 03 2025 Felix Schwarz - 2.0.1-1 +- update to 2.0.1 + +* Fri Jul 19 2024 Fedora Release Engineering - 1.1-20 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild + +* Fri Jun 07 2024 Python Maint - 1.1-19 +- Rebuilt for Python 3.13 + +* Fri Jan 26 2024 Fedora Release Engineering - 1.1-18 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Mon Jan 22 2024 Fedora Release Engineering - 1.1-17 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jul 21 2023 Fedora Release Engineering - 1.1-16 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Tue Jun 13 2023 Python Maint - 1.1-15 +- Rebuilt for Python 3.12 + +* Fri Jan 20 2023 Fedora Release Engineering - 1.1-14 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Fri Jul 22 2022 Fedora Release Engineering - 1.1-13 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Mon Jun 13 2022 Python Maint - 1.1-12 +- Rebuilt for Python 3.11 + +* Thu Apr 07 2022 Matthew Davis - 1.1-11 +- Converted tests from python-nose to pytest +- Removed Python 2 support +- Converted to pyproject macros + +* Fri Jan 21 2022 Fedora Release Engineering - 1.1-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Fri Jul 23 2021 Fedora Release Engineering - 1.1-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Fri Jun 04 2021 Python Maint - 1.1-8 +- Rebuilt for Python 3.10 + +* Wed Jan 27 2021 Fedora Release Engineering - 1.1-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Wed Jul 29 2020 Fedora Release Engineering - 1.1-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Thu Jun 25 2020 Felix Schwarz - 1.1-5 +- add python-setuptools to BuildRequires + +* Tue May 26 2020 Miro Hrončok - 1.1-4 +- Rebuilt for Python 3.9 + +* Tue Apr 14 2020 Felix Schwarz - 1.1-3 +- also package+run unit tests +- build Python 3 subpackage also in EPEL 7 + +* Thu Jan 30 2020 Fedora Release Engineering - 1.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Tue Oct 08 2019 Eli Young - 1.1-1 +- Update to 1.1 (#1697425) + +* Thu Oct 03 2019 Miro Hrončok - 1.0-16 +- Rebuilt for Python 3.8.0rc1 (#1748018) + +* Mon Aug 19 2019 Miro Hrončok - 1.0-15 +- Rebuilt for Python 3.8 + +* Fri Jul 26 2019 Fedora Release Engineering - 1.0-14 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Sat Feb 02 2019 Fedora Release Engineering - 1.0-13 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Wed Jan 09 2019 Miro Hrončok - 1.0-12 +- Subpackage python2-pyrfc3339 has been removed + See https://fedoraproject.org/wiki/Changes/Mass_Python_2_Package_Removal + +* Sat Jul 14 2018 Fedora Release Engineering - 1.0-11 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Tue Jun 19 2018 Miro Hrončok - 1.0-10 +- Rebuilt for Python 3.7 + +* Fri Feb 09 2018 Fedora Release Engineering - 1.0-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Sat Jan 27 2018 Iryna Shcherbina - 1.0-8 +- Update Python 2 dependency declarations to new packaging standards + (See https://fedoraproject.org/wiki/FinalizingFedoraSwitchtoPython3) + +* Thu Jul 27 2017 Fedora Release Engineering - 1.0-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Sat Feb 11 2017 Fedora Release Engineering - 1.0-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Mon Dec 19 2016 Miro Hrončok - 1.0-5 +- Rebuild for Python 3.6 + +* Tue Jul 19 2016 Fedora Release Engineering - 1.0-4 +- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages + +* Thu Feb 04 2016 Fedora Release Engineering - 1.0-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Thu Dec 03 2015 Robert Buchholz - 1.0-2 +- epel7: Only build python2 package + +* Tue Nov 10 2015 James Hogarth - 1.0-1 +- Add installed tests back as per review +- Update to new 1.0 PyPi release +- Add external license file +* Sun Nov 08 2015 James Hogarth - 0.2-2 +- Update to follow the python guidelines +* Wed Oct 28 2015 Felix Schwarz - 0.2-1 +- initial packaging diff --git a/sources b/sources new file mode 100644 index 0000000..bb55905 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +SHA512 (pyrfc3339-2.0.1.tar.gz) = b1c6768842a8fc8917280264e4fa3e2e1223399184bd7639fbb12d3bd4755ea2cadfb291efe2bf1f3b7e89e50fcb1f89a46a9e81a94cca3a5ca0aa8c0b698c69 diff --git a/test_all.py b/test_all.py new file mode 100644 index 0000000..ed47f20 --- /dev/null +++ b/test_all.py @@ -0,0 +1,155 @@ +""" +Test suite for pyRFC3339. + +""" + +import unittest +import zoneinfo +from datetime import datetime, timezone +from zoneinfo import ZoneInfo + +from pyrfc3339 import generate, parse + + +class TestCore(unittest.TestCase): + """ + This test case contains tests to address cases not tested in the doctests, + as well as additional tests for end-to-end verification. + + """ + + def test_zero_offset(self) -> None: + """ + Both +00:00 and -00:00 are equivalent to the offset 'Z' (UTC). + + """ + timestamp = "2009-01-01T10:02:03+00:00" + dt = parse(timestamp) + self.assertEqual(dt.tzinfo, timezone.utc) + + timestamp = "2009-01-01T10:02:03-00:00" + dt = parse(timestamp) + self.assertEqual(dt.tzinfo, timezone.utc) + + def test_parse_microseconds(self) -> None: + """ + Test parsing timestamps with microseconds. + + """ + timestamp = "2009-01-01T10:02:03.25Z" + dt = parse(timestamp) + self.assertEqual(dt.microsecond, 250000) + + def test_generate_microseconds(self) -> None: + """ + Test generating timestamps with microseconds. + + """ + dt = datetime(2009, 1, 1, 10, 2, 3, 500000, tzinfo=timezone.utc) + timestamp = generate(dt, microseconds=True) + self.assertEqual(timestamp, "2009-01-01T10:02:03.500000Z") + + def test_mixed_case(self) -> None: + """ + Timestamps may use either 'T' or 't' and either 'Z' or 'z' + according to :RFC:`3339`. + + """ + dt1 = parse("2009-01-01t10:01:02z") + dt2 = datetime(2009, 1, 1, 10, 1, 2, tzinfo=timezone.utc) + + self.assertEqual(dt1, dt2) + + def test_parse_naive_utc(self) -> None: + """ + Test parsing a UTC timestamp to a naive datetime. + + """ + dt1 = parse("2009-01-01T10:01:02Z", produce_naive=True) + self.assertEqual(dt1.tzinfo, None) + + def test_parse_naive_local(self) -> None: + """ + Test that parsing a local timestamp to a naive datetime fails. + + """ + with self.assertRaises(ValueError): + parse("2009-01-01T10:01:02-04:00", produce_naive=True) + + def test_generate_utc_parse_utc(self) -> None: + """ + Generate a UTC timestamp and parse it into a UTC datetime. + + """ + dt1 = datetime.now(timezone.utc) + + dt2 = parse(generate(dt1, microseconds=True)) + self.assertEqual(dt1, dt2) + + def test_generate_local_parse_local(self) -> None: + """ + Generate a local timestamp and parse it into a local datetime. + + """ + eastern = ZoneInfo("US/Eastern") + dt1 = datetime.now(eastern) + dt2 = parse(generate(dt1, utc=False, microseconds=True), utc=False) + self.assertEqual(dt1, dt2) + + def test_generate_local_parse_utc(self) -> None: + """ + Generate a local timestamp and parse it into a UTC datetime. + + """ + eastern = ZoneInfo("US/Eastern") + dt1 = datetime.now(eastern) + dt2 = parse(generate(dt1, utc=False, microseconds=True)) + self.assertEqual(dt1, dt2) + + @unittest.skip("fails due to python/cpython#120713") + def test_three_digit_year(self) -> None: + dt = datetime(999, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + self.assertEqual(generate(dt), "0999-01-01T00:00:00Z") + + +class TestExhaustiveRoundtrip(unittest.TestCase): + """ + This test case exhaustively tests parsing and generation by generating + a local RFC 3339 timestamp for every timezone supported by `zoneinfo`, + parsing that timestamp into a local datetime and a UTC datetime + and asserting that those represent the same instant. + + """ + + def setUp(self) -> None: + self.available_timezones = zoneinfo.available_timezones() + + def test_local_roundtrip(self) -> None: + """ + Generates a local datetime using the given timezone, + produces a local timestamp from the datetime, parses the timestamp + to a local datetime, and verifies that the two datetimes are equal. + + """ + for tz_name in self.available_timezones: + with self.subTest(tz=tz_name): + tzinfo = ZoneInfo(tz_name) + dt1 = datetime.now(tzinfo) + timestamp = generate(dt1, utc=False, microseconds=True) + dt2 = parse(timestamp, utc=False) + self.assertEqual(dt1, dt2) + + def test_utc_roundtrip(self) -> None: + """ + Generates a local datetime using the given timezone, + produces a local timestamp from the datetime, parses the timestamp + to a UTC datetime, and verifies that the two datetimes are equal. + + """ + for tz_name in self.available_timezones: + with self.subTest(tz=tz_name): + tzinfo = ZoneInfo(tz_name) + dt1 = datetime.now(tzinfo) + timestamp = generate(dt1, utc=False, microseconds=True) + dt2 = parse(timestamp) + self.assertEqual(dt1, dt2)