Repository: coverahealth/dataspec

Issue:
Support ISO time strings
Currently, we support using `datetime.fromisoformat` and `date.fromisoformat` to check strings for a subset of ISO timestamps and dates. Python 3.7+ also has `time.fromisoformat`, which can validate a subset of ISO time strings.



Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9436e01..e88328b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 - Add an exact length validator to the string spec factory (#2)
 - Add conforming string formats (#3)
+- Add ISO time string format (#4)
 
 ## [0.1.0] - 2019-10-20
 ### Added
diff --git a/src/dataspec/impl.py b/src/dataspec/impl.py
index 769230e..a7ae7a8 100644
--- a/src/dataspec/impl.py
+++ b/src/dataspec/impl.py
@@ -1006,6 +1006,17 @@ if sys.version_info >= (3, 7):
                 value=s,
             )
 
+    @register_str_format("iso-time", conformer=time.fromisoformat)
+    def _str_is_iso_time(s: str) -> Iterator[ErrorDetails]:
+        try:
+            time.fromisoformat(s)
+        except ValueError:
+            yield ErrorDetails(
+                message=f"String does not contain ISO formatted time",
+                pred=_str_is_iso_time,
+                value=s,
+            )
+
 
 else:
 


Test patch:
diff --git a/tests/test_dataspec.py b/tests/test_dataspec.py
index 77ce686..a706606 100644
--- a/tests/test_dataspec.py
+++ b/tests/test_dataspec.py
@@ -1432,6 +1432,71 @@ class TestStringSpecValidation:
             assert not datetime_spec.is_valid(v)
             assert not conforming_datetime_spec.is_valid(v)
 
+    @pytest.mark.skipif(
+        sys.version_info <= (3, 7), reason="time.fromisoformat added in Python 3.7"
+    )
+    class TestISOTimeFormat:
+        @pytest.fixture
+        def conform(self):
+            return time.fromisoformat
+
+        @pytest.fixture
+        def conforming_time_spec(self) -> Spec:
+            return s.str(conform_format="iso-time")
+
+        @pytest.fixture
+        def time_spec(self) -> Spec:
+            return s.str(format_="iso-time")
+
+        @pytest.mark.parametrize(
+            "v",
+            [
+                "18",
+                "18-00:00",
+                "18.335",
+                "18.335-00:00",
+                "18:03",
+                "18:03-00:00",
+                "18:03.335",
+                "18:03.335-00:00",
+                "18:03:50",
+                "18:03:50-00:00",
+                "18:03:50.617",
+                "18:03:50.617-00:00",
+                "18:03:50.617332",
+                "18:03:50.617332-00:00",
+            ],
+        )
+        def test_is_time_str(
+            self, time_spec: Spec, conforming_time_spec: Spec, conform, v
+        ):
+            assert time_spec.is_valid(v)
+            assert conforming_time_spec.is_valid(v)
+            assert conform(v) == conforming_time_spec.conform(v)
+
+        @pytest.mark.parametrize(
+            "v",
+            [
+                None,
+                25,
+                3.14,
+                [],
+                set(),
+                "abcdef",
+                "abcdefg",
+                "100017",
+                "10017-383",
+                "1945-9-2",
+                "430-10-02",
+                "2019-10-12",
+                "1945-09-02",
+                "1066-10-14",
+            ],
+        )
+        def test_is_not_time_str(self, time_spec: Spec, conforming_time_spec: Spec, v):
+            assert not time_spec.is_valid(v)
+            assert not conforming_time_spec.is_valid(v)
+
     class TestUUIDFormat:
         @pytest.fixture
         def conforming_uuid_spec(self) -> Spec: