Skip to main content

Rename timestamps to dates

ID
689a15f
date
2025-11-29 07:37:33+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
5f67edc
message
Rename `timestamps` to `dates`
changed files
6 files, 140 additions, 136 deletions

Changed files

CHANGELOG.md (454) → CHANGELOG.md (521)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0be58e0..d9ec7f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # CHANGELOG
 
+## v4 - 2025-11-29
+
+Rename `chives.timestamps` to `chives.dates`.
+
 ## v3 - 2025-11-29
 
 Add the `clean_youtube_url()` function and `urls` extra.

src/chives/__init__.py (390) → src/chives/__init__.py (390)

diff --git a/src/chives/__init__.py b/src/chives/__init__.py
index 5d290d7..6c780be 100644
--- a/src/chives/__init__.py
+++ b/src/chives/__init__.py
@@ -11,4 +11,4 @@ I share across multiple sites.
 
 """
 
-__version__ = "3"
+__version__ = "4"

src/chives/dates.py (0) → src/chives/dates.py (2122)

diff --git a/src/chives/dates.py b/src/chives/dates.py
new file mode 100644
index 0000000..272d651
--- /dev/null
+++ b/src/chives/dates.py
@@ -0,0 +1,73 @@
+"""
+Functions for interacting with timestamps and date strings.
+
+References:
+* https://alexwlchan.net/2025/messy-dates-in-json/
+
+"""
+
+from collections.abc import Iterable, Iterator
+from datetime import datetime, timezone
+from typing import Any
+
+
+def find_all_dates(json_value: Any) -> Iterator[tuple[dict[str, Any], str, str]]:
+    """
+    Find all the timestamps in a heavily nested JSON object.
+
+    This function looks for any JSON objects with a key-value pair
+    where the key starts with `date_` and the value is a string, and
+    emits a 3-tuple:
+
+    *   the JSON object
+    *   the key
+    *   the value
+
+    """
+    if isinstance(json_value, dict):
+        for key, value in json_value.items():
+            if (
+                isinstance(key, str)
+                and key.startswith("date_")
+                and isinstance(value, str)
+            ):
+                yield json_value, key, value
+            else:
+                yield from find_all_dates(value)
+    elif isinstance(json_value, list):
+        for value in json_value:
+            yield from find_all_dates(value)
+
+
+def date_matches_format(date_string: str, format: str) -> bool:
+    """
+    Returns True if `date_string` can be parsed as a datetime
+    using `format`, False otherwise.
+    """
+    try:
+        datetime.strptime(date_string, format)
+        return True
+    except ValueError:
+        return False
+
+
+def date_matches_any_format(date_string: str, formats: Iterable[str]) -> bool:
+    """
+    Returns True if `date_string` can be parsed as a datetime
+    with any of the `formats`, False otherwise.
+    """
+    return any(date_matches_format(date_string, fmt) for fmt in formats)
+
+
+def reformat_date(s: str, /, orig_fmt: str) -> str:
+    """
+    Reformat a date to one of my desired formats.
+    """
+    if "%Z" in orig_fmt:
+        d = datetime.strptime(s, orig_fmt)
+    else:
+        d = datetime.strptime(s.replace("Z", "+0000"), orig_fmt.replace("Z", "%z"))
+    d = d.replace(microsecond=0)
+    if d.tzinfo is None:
+        d = d.replace(tzinfo=timezone.utc)
+    return d.strftime("%Y-%m-%dT%H:%M:%S%z").replace("+0000", "Z")

src/chives/timestamps.py (2122) → src/chives/timestamps.py (0)

diff --git a/src/chives/timestamps.py b/src/chives/timestamps.py
deleted file mode 100644
index 272d651..0000000
--- a/src/chives/timestamps.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""
-Functions for interacting with timestamps and date strings.
-
-References:
-* https://alexwlchan.net/2025/messy-dates-in-json/
-
-"""
-
-from collections.abc import Iterable, Iterator
-from datetime import datetime, timezone
-from typing import Any
-
-
-def find_all_dates(json_value: Any) -> Iterator[tuple[dict[str, Any], str, str]]:
-    """
-    Find all the timestamps in a heavily nested JSON object.
-
-    This function looks for any JSON objects with a key-value pair
-    where the key starts with `date_` and the value is a string, and
-    emits a 3-tuple:
-
-    *   the JSON object
-    *   the key
-    *   the value
-
-    """
-    if isinstance(json_value, dict):
-        for key, value in json_value.items():
-            if (
-                isinstance(key, str)
-                and key.startswith("date_")
-                and isinstance(value, str)
-            ):
-                yield json_value, key, value
-            else:
-                yield from find_all_dates(value)
-    elif isinstance(json_value, list):
-        for value in json_value:
-            yield from find_all_dates(value)
-
-
-def date_matches_format(date_string: str, format: str) -> bool:
-    """
-    Returns True if `date_string` can be parsed as a datetime
-    using `format`, False otherwise.
-    """
-    try:
-        datetime.strptime(date_string, format)
-        return True
-    except ValueError:
-        return False
-
-
-def date_matches_any_format(date_string: str, formats: Iterable[str]) -> bool:
-    """
-    Returns True if `date_string` can be parsed as a datetime
-    with any of the `formats`, False otherwise.
-    """
-    return any(date_matches_format(date_string, fmt) for fmt in formats)
-
-
-def reformat_date(s: str, /, orig_fmt: str) -> str:
-    """
-    Reformat a date to one of my desired formats.
-    """
-    if "%Z" in orig_fmt:
-        d = datetime.strptime(s, orig_fmt)
-    else:
-        d = datetime.strptime(s.replace("Z", "+0000"), orig_fmt.replace("Z", "%z"))
-    d = d.replace(microsecond=0)
-    if d.tzinfo is None:
-        d = d.replace(tzinfo=timezone.utc)
-    return d.strftime("%Y-%m-%dT%H:%M:%S%z").replace("+0000", "Z")

tests/test_dates.py (0) → tests/test_dates.py (2021)

diff --git a/tests/test_dates.py b/tests/test_dates.py
new file mode 100644
index 0000000..74c9128
--- /dev/null
+++ b/tests/test_dates.py
@@ -0,0 +1,62 @@
+"""Tests for `chives.dates`."""
+
+import json
+
+import pytest
+
+from chives.dates import date_matches_any_format, find_all_dates, reformat_date
+
+
+def test_find_all_dates() -> None:
+    """find_all_dates finds all the nested dates in a JSON object."""
+    json_value = json.loads("""{
+      "doc1": {"id": "1", "date_created": "2025-10-14T05:34:07+0000"},
+      "shapes": [
+      	{"color": "blue", "date_saved": "2015-03-01 23:34:39 +00:00"},
+      	{"color": "yellow", "date_saved": "2013-9-21 13:43:00Z", "is_square": true},
+      	{"color": "green", "date_saved": null}
+      ],
+      "date_verified": "2024-08-30"
+    }""")
+
+    assert list(find_all_dates(json_value)) == [
+        (
+            {"id": "1", "date_created": "2025-10-14T05:34:07+0000"},
+            "date_created",
+            "2025-10-14T05:34:07+0000",
+        ),
+        (
+            {"color": "blue", "date_saved": "2015-03-01 23:34:39 +00:00"},
+            "date_saved",
+            "2015-03-01 23:34:39 +00:00",
+        ),
+        (
+            {"color": "yellow", "date_saved": "2013-9-21 13:43:00Z", "is_square": True},
+            "date_saved",
+            "2013-9-21 13:43:00Z",
+        ),
+        (json_value, "date_verified", "2024-08-30"),
+    ]
+
+
+def test_date_matches_any_format() -> None:
+    """
+    Tests for `date_matches_any_format`.
+    """
+    assert date_matches_any_format(
+        "2001-01-01", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S%z"]
+    )
+    assert not date_matches_any_format("2001-01-01", formats=["%Y-%m-%dT%H:%M:%S%z"])
+
+
+@pytest.mark.parametrize(
+    "s, orig_fmt, formatted_date",
+    [
+        ("2025-11-12T15:34:39.570Z", "%Y-%m-%dT%H:%M:%S.%fZ", "2025-11-12T15:34:39Z"),
+        ("2025-03-12 09:57:03", "%Y-%m-%d %H:%M:%S", "2025-03-12T09:57:03Z"),
+        ("2016-02-25 05:28:35 GMT", "%Y-%m-%d %H:%M:%S %Z", "2016-02-25T05:28:35Z"),
+    ],
+)
+def test_reformat_date(s: str, orig_fmt: str, formatted_date: str) -> None:
+    """Tests for `reformat_date`."""
+    assert reformat_date(s, orig_fmt) == formatted_date

tests/test_timestamps.py (2031) → tests/test_timestamps.py (0)

diff --git a/tests/test_timestamps.py b/tests/test_timestamps.py
deleted file mode 100644
index 6b64184..0000000
--- a/tests/test_timestamps.py
+++ /dev/null
@@ -1,62 +0,0 @@
-"""Tests for `chives.timestamps`."""
-
-import json
-
-import pytest
-
-from chives.timestamps import date_matches_any_format, find_all_dates, reformat_date
-
-
-def test_find_all_dates() -> None:
-    """find_all_dates finds all the nested dates in a JSON object."""
-    json_value = json.loads("""{
-      "doc1": {"id": "1", "date_created": "2025-10-14T05:34:07+0000"},
-      "shapes": [
-      	{"color": "blue", "date_saved": "2015-03-01 23:34:39 +00:00"},
-      	{"color": "yellow", "date_saved": "2013-9-21 13:43:00Z", "is_square": true},
-      	{"color": "green", "date_saved": null}
-      ],
-      "date_verified": "2024-08-30"
-    }""")
-
-    assert list(find_all_dates(json_value)) == [
-        (
-            {"id": "1", "date_created": "2025-10-14T05:34:07+0000"},
-            "date_created",
-            "2025-10-14T05:34:07+0000",
-        ),
-        (
-            {"color": "blue", "date_saved": "2015-03-01 23:34:39 +00:00"},
-            "date_saved",
-            "2015-03-01 23:34:39 +00:00",
-        ),
-        (
-            {"color": "yellow", "date_saved": "2013-9-21 13:43:00Z", "is_square": True},
-            "date_saved",
-            "2013-9-21 13:43:00Z",
-        ),
-        (json_value, "date_verified", "2024-08-30"),
-    ]
-
-
-def test_date_matches_any_format() -> None:
-    """
-    Tests for `date_matches_any_format`.
-    """
-    assert date_matches_any_format(
-        "2001-01-01", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S%z"]
-    )
-    assert not date_matches_any_format("2001-01-01", formats=["%Y-%m-%dT%H:%M:%S%z"])
-
-
-@pytest.mark.parametrize(
-    "s, orig_fmt, formatted_date",
-    [
-        ("2025-11-12T15:34:39.570Z", "%Y-%m-%dT%H:%M:%S.%fZ", "2025-11-12T15:34:39Z"),
-        ("2025-03-12 09:57:03", "%Y-%m-%d %H:%M:%S", "2025-03-12T09:57:03Z"),
-        ("2016-02-25 05:28:35 GMT", "%Y-%m-%d %H:%M:%S %Z", "2016-02-25T05:28:35Z"),
-    ],
-)
-def test_reformat_date(s: str, orig_fmt: str, formatted_date: str) -> None:
-    """Tests for `reformat_date`."""
-    assert reformat_date(s, orig_fmt) == formatted_date