Skip to content

Commit cb9939d

Browse files
committed
datetime versioning scheme
Signed-off-by: Kunz, Immanuel <[email protected]>
1 parent 9c15915 commit cb9939d

File tree

6 files changed

+89
-0
lines changed

6 files changed

+89
-0
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pyparsing==2.4.7
44
semantic-version==2.8.5
55
semver==2.13.0
66
isort==5.10.1
7+
python-dateutil==2.9.0.post0

src/univers/datetime.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Visit https://aboutcode.org and https://github.com/aboutcode-org/univers for support and download.
5+
6+
import re
7+
from dateutil.parser import isoparse
8+
9+
class DatetimeVersion:
10+
"""
11+
datetime version.
12+
13+
The timestamp must be RFC3339-compliant, i.e., a subset of ISO8601, where the date AND time are always specified. Therefore, we can use dateutil's ISO-parser but have to check for compliance with the RFC format first via a regex.
14+
"""
15+
16+
VERSION_PATTERN = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$')
17+
18+
def __init__(self, version):
19+
if not self.is_valid(version):
20+
raise InvalidVersionError(version)
21+
22+
version = str(version).strip()
23+
self.original = version
24+
self.parsed_stamp = isoparse(version)
25+
26+
def __eq__(self, other):
27+
return self.parsed_stamp == other.parsed_stamp
28+
29+
def __lt__(self, other):
30+
return self.parsed_stamp < other.parsed_stamp
31+
32+
def __le__(self, other):
33+
return self.parsed_stamp <= other.parsed_stamp
34+
35+
def __gt__(self, other):
36+
return self.parsed_stamp > other.parsed_stamp
37+
38+
def __ge__(self, other):
39+
return self.parsed_stamp >= other.parsed_stamp
40+
41+
@classmethod
42+
def is_valid(cls, string):
43+
return cls.VERSION_PATTERN.match(string)
44+
45+
class InvalidVersionError(ValueError):
46+
pass

src/univers/version_range.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,11 @@ class GolangVersionRange(VersionRange):
959959
}
960960

961961

962+
class DatetimeVersionRange(VersionRange):
963+
scheme = "datetime"
964+
version_class = versions.DatetimeVersion
965+
966+
962967
class GenericVersionRange(VersionRange):
963968
scheme = "generic"
964969
version_class = versions.SemverVersion
@@ -1419,6 +1424,7 @@ def build_range_from_snyk_advisory_string(scheme: str, string: Union[str, List])
14191424
"openssl": OpensslVersionRange,
14201425
"mattermost": MattermostVersionRange,
14211426
"conan": ConanVersionRange,
1427+
"datetime": DatetimeVersionRange,
14221428
}
14231429

14241430
PURL_TYPE_BY_GITLAB_SCHEME = {

src/univers/versions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from packaging import version as packaging_version
1010

1111
from univers import arch
12+
from univers import datetime
1213
from univers import debian
1314
from univers import gem
1415
from univers import gentoo
@@ -133,6 +134,16 @@ def __str__(self):
133134
return str(self.value)
134135

135136

137+
class DatetimeVersion(Version):
138+
@classmethod
139+
def is_valid(cls, string):
140+
return datetime.DatetimeVersion.is_valid(string)
141+
142+
@classmethod
143+
def build_value(self, string):
144+
return datetime.DatetimeVersion(string)
145+
146+
136147
class GenericVersion(Version):
137148
@classmethod
138149
def is_valid(cls, string):
@@ -702,4 +713,5 @@ def bump(self, index):
702713
OpensslVersion,
703714
LegacyOpensslVersion,
704715
AlpineLinuxVersion,
716+
DatetimeVersion,
705717
]

tests/test_version_range.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from univers.version_constraint import VersionConstraint
1313
from univers.version_range import RANGE_CLASS_BY_SCHEMES
1414
from univers.version_range import ConanVersionRange
15+
from univers.version_range import DatetimeVersionRange
1516
from univers.version_range import GemVersionRange
1617
from univers.version_range import InvalidVersionRange
1718
from univers.version_range import MattermostVersionRange
@@ -23,6 +24,7 @@
2324
from univers.version_range import build_range_from_snyk_advisory_string
2425
from univers.version_range import from_gitlab_native
2526
from univers.versions import InvalidVersion
27+
from univers.versions import DatetimeVersion
2628
from univers.versions import NugetVersion
2729
from univers.versions import OpensslVersion
2830
from univers.versions import PypiVersion
@@ -546,3 +548,15 @@ def test_version_range_normalize_case3():
546548
nvr = vr.normalize(known_versions=known_versions)
547549

548550
assert str(nvr) == "vers:pypi/>=1.0.0|<=1.3.0|3.0.0"
551+
552+
553+
def test_version_range_datetime():
554+
assert DatetimeVersion("2000-01-01T01:02:03.1234Z") in DatetimeVersionRange.from_string("vers:datetime/*")
555+
assert DatetimeVersion("2021-05-05T01:02:03Z") in DatetimeVersionRange.from_string("vers:datetime/>2021-01-01T01:02:03.1234Z|<2022-01-01T01:02:03.1234Z")
556+
datetime_constraints = DatetimeVersionRange(
557+
constraints=(
558+
VersionConstraint(comparator=">", version=DatetimeVersion(string="2000-01-01T01:02:03Z")),
559+
VersionConstraint(comparator="<", version=DatetimeVersion(string="2002-01-01T01:02:03Z")),
560+
)
561+
)
562+
assert DatetimeVersion("2001-01-01T01:02:03Z") in datetime_constraints

tests/test_versions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from univers.versions import AlpineLinuxVersion
99
from univers.versions import ArchLinuxVersion
1010
from univers.versions import ComposerVersion
11+
from univers.versions import DatetimeVersion
1112
from univers.versions import DebianVersion
1213
from univers.versions import EnhancedSemanticVersion
1314
from univers.versions import GentooVersion
@@ -218,3 +219,12 @@ def test_golang_version():
218219
assert GolangVersion("v0.1.1") >= GolangVersion("v0.1.1")
219220
assert GolangVersion("v0.1.1") <= GolangVersion("v0.1.1")
220221
assert GolangVersion("v0.1.1") <= GolangVersion("v0.1.2")
222+
223+
224+
def test_datetime_version():
225+
assert DatetimeVersion("2023-10-28T18:30:00Z") == DatetimeVersion("2023-10-28T18:30:00Z")
226+
assert DatetimeVersion("2023-01-11T10:10:10Z") > DatetimeVersion("2023-01-10T10:10:10Z")
227+
assert DatetimeVersion("2022-10-28T18:30:00Z") < DatetimeVersion("2023-10-28T18:30:00Z")
228+
assert DatetimeVersion("2022-10-28T18:30:00Z") <= DatetimeVersion("2023-10-28T18:30:00Z")
229+
assert DatetimeVersion("2024-10-28T18:30:00Z") > DatetimeVersion("2023-10-28T18:30:00Z")
230+
assert DatetimeVersion("2023-10-28T19:30:00+01:00") == DatetimeVersion("2023-10-28T18:30:00Z")

0 commit comments

Comments
 (0)