home · contact · privacy
Switch from datetime.datetime to datetime.date where that's enough resolution.
authorChristian Heller <c.heller@plomlompom.de>
Tue, 7 Jan 2025 04:53:11 +0000 (05:53 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 7 Jan 2025 04:53:11 +0000 (05:53 +0100)
plomtask/dating.py
plomtask/days.py
plomtask/todos.py
tests/days.py
tests/utils.py

index 26b3ce3f4c2dae84fb44c8366d4a96882ced76ef..533375184c4d6ed80132ffd4160faa24b15585fb 100644 (file)
@@ -1,15 +1,10 @@
 """Various utilities for handling dates."""
-from datetime import datetime, timedelta
+from datetime import date as datetime_date, timedelta
 from plomtask.exceptions import BadFormatException
 
-DATE_FORMAT = '%Y-%m-%d'
-
 
 def valid_date(date_str: str) -> str:
-    """Validate date against DATE_FORMAT or 'today'/'yesterday'/'tomorrow.
-
-    In any case, returns in DATE_FORMAT.
-    """
+    """Validate against ISO format/relative terms; return in ISO format."""
     if date_str == 'today':
         date_str = date_in_n_days(0)
     elif date_str == 'yesterday':
@@ -17,14 +12,14 @@ def valid_date(date_str: str) -> str:
     elif date_str == 'tomorrow':
         date_str = date_in_n_days(1)
     try:
-        dt = datetime.strptime(date_str, DATE_FORMAT)
+        date = datetime_date.fromisoformat(date_str)
     except (ValueError, TypeError) as e:
         msg = f'Given date of wrong format: {date_str}'
         raise BadFormatException(msg) from e
-    return dt.strftime(DATE_FORMAT)
+    return date.isoformat()
 
 
 def date_in_n_days(n: int) -> str:
-    """Return in DATE_FORMAT date from today + n days."""
-    date = datetime.now() + timedelta(days=n)
-    return date.strftime(DATE_FORMAT)
+    """Return in ISO format date from today + n days."""
+    date = datetime_date.today() + timedelta(days=n)
+    return date.isoformat()
index b576bb2c16262d76a8d0caa71d358364570a3969..1fe90ee94186819fd356592a69ff82b41e0d801b 100644 (file)
@@ -2,10 +2,10 @@
 from __future__ import annotations
 from typing import Any, Self
 from sqlite3 import Row
-from datetime import datetime, timedelta
+from datetime import date as datetime_date, timedelta
 from plomtask.db import DatabaseConnection, BaseModel, BaseModelId
 from plomtask.todos import Todo
-from plomtask.dating import (DATE_FORMAT, valid_date)
+from plomtask.dating import valid_date
 
 
 class Day(BaseModel):
@@ -18,12 +18,12 @@ class Day(BaseModel):
     def __init__(self, date: str, comment: str = '') -> None:
         id_ = valid_date(date)
         super().__init__(id_)
-        self.datetime = datetime.strptime(self.date, DATE_FORMAT)
+        self.date = datetime_date.fromisoformat(self.date_str)
         self.comment = comment
         self.todos: list[Todo] = []
 
     def __lt__(self, other: Self) -> bool:
-        return self.date < other.date
+        return self.date_str < other.date_str
 
     @classmethod
     def from_table_row(cls, db_conn: DatabaseConnection, row: Row | list[Any]
@@ -59,56 +59,57 @@ class Day(BaseModel):
         start_date, end_date = valid_date(start_date), valid_date(end_date)
         if start_date > end_date:
             return []
-        days = [d for d in days if d.date >= start_date and d.date <= end_date]
+        days = [d for d in days
+                if d.date_str >= start_date and d.date_str <= end_date]
         days.sort()
-        if start_date not in [d.date for d in days]:
+        if start_date not in [d.date_str for d in days]:
             days[:] = [cls(start_date)] + days
-        if end_date not in [d.date for d in days]:
+        if end_date not in [d.date_str for d in days]:
             days += [cls(end_date)]
         if len(days) > 1:
             gapless_days = []
             for i, day in enumerate(days):
                 gapless_days += [day]
                 if i < len(days) - 1:
-                    while day.next_date != days[i+1].date:
+                    while day.next_date != days[i+1].date_str:
                         day = cls(day.next_date)
                         gapless_days += [day]
             days[:] = gapless_days
         return days
 
     @property
-    def date(self) -> str:
+    def date_str(self) -> str:
         """Return self.id_ under the assumption it's a date string."""
         assert isinstance(self.id_, str)
         return self.id_
 
     @property
     def first_of_month(self) -> bool:
-        """Return what month self.date is part of."""
+        """Return if self is first day of a month."""
         assert isinstance(self.id_, str)
         return self.id_[-2:] == '01'
 
     @property
     def month_name(self) -> str:
-        """Return what month self.date is part of."""
-        return self.datetime.strftime('%B')
+        """Return what month self is part of."""
+        return self.date.strftime('%B')
 
     @property
     def weekday(self) -> str:
-        """Return what weekday matches self.date."""
-        return self.datetime.strftime('%A')
+        """Return what weekday matches self."""
+        return self.date.strftime('%A')
 
     @property
     def prev_date(self) -> str:
-        """Return date preceding date of this Day."""
-        prev_datetime = self.datetime - timedelta(days=1)
-        return prev_datetime.strftime(DATE_FORMAT)
+        """Return date preceding date of self."""
+        prev_date = self.date - timedelta(days=1)
+        return prev_date.isoformat()
 
     @property
     def next_date(self) -> str:
         """Return date succeeding date of this Day."""
-        next_datetime = self.datetime + timedelta(days=1)
-        return next_datetime.strftime(DATE_FORMAT)
+        next_date = self.date + timedelta(days=1)
+        return next_date.isoformat()
 
     @property
     def calendarized_todos(self) -> list[Todo]:
index 5a71400406578bb8720bf372076ded2a876ee36b..f7d375d9bac10d99ee9422c90e3e5a1b06aba5df 100644 (file)
@@ -58,9 +58,11 @@ class Todo(BaseModel, ConditionsRelations):
     def __init__(self, id_: int | None,
                  process: Process,
                  is_done: bool,
-                 date: str, comment: str = '',
+                 date: str,
+                 comment: str = '',
                  effort: None | float = None,
-                 calendarize: bool = False) -> None:
+                 calendarize: bool = False
+                 ) -> None:
         super().__init__(id_)
         ConditionsRelations.__init__(self)
         if process.id_ is None:
index c1952373aa55518b2e02856bb3397bc1e255bb47..595ff2048cf939183bdcbefbc5ff86c26131fc3b 100644 (file)
@@ -1,24 +1,24 @@
 """Test Days module."""
-from datetime import datetime, timedelta
+from datetime import date as datetime_date, datetime, timedelta
 from typing import Any
 from tests.utils import (TestCaseSansDB, TestCaseWithDB, TestCaseWithServer,
                          Expected)
 from plomtask.dating import date_in_n_days as tested_date_in_n_days
 from plomtask.days import Day
 
-# so far the same as plomtask.dating.DATE_FORMAT, but for testing purposes we
-# want to explicitly state our expectations here indepedently from that
+# Simply the ISO format for dates as used in plomtask.dating, but for testing
+# purposes we state our expectations here independently from that
 TESTING_DATE_FORMAT = '%Y-%m-%d'
 
 
 def _testing_date_in_n_days(n: int) -> str:
-    """Return in TEST_DATE_FORMAT date from today + n days.
+    """Return in ISO format / TEST_DATE_FORMAT date from today + n days.
 
     As with TESTING_DATE_FORMAT, we assume this equal the original's code
     at plomtask.dating.date_in_n_days, but want to state our expectations
     explicitly to rule out importing issues from the original.
     """
-    date = datetime.now() + timedelta(days=n)
+    date = datetime_date.today() + timedelta(days=n)
     return date.strftime(TESTING_DATE_FORMAT)
 
 
@@ -35,9 +35,9 @@ class TestsSansDB(TestCaseSansDB):
             self.assertEqual(tested_date_in_n_days(n),
                              date.strftime(TESTING_DATE_FORMAT))
 
-    def test_Day_datetime_weekday_neighbor_dates(self) -> None:
+    def test_Day_date_weekday_neighbor_dates(self) -> None:
         """Test Day's date parsing and neighbourhood resolution."""
-        self.assertEqual(datetime(2024, 5, 1), Day('2024-05-01').datetime)
+        self.assertEqual(datetime_date(2024, 5, 1), Day('2024-05-01').date)
         self.assertEqual('Sunday', Day('2024-03-17').weekday)
         self.assertEqual('March', Day('2024-03-17').month_name)
         self.assertEqual('2023-12-31', Day('2024-01-01').prev_date)
index 66b2fbfa3c27cce0c68fefb29b2fda9f7f251fe2..ad31f170e4623d551996b1fe0ff9c2771d3b1674 100644 (file)
@@ -5,7 +5,7 @@ from unittest import TestCase
 from typing import Mapping, Any, Callable
 from threading import Thread
 from http.client import HTTPConnection
-from datetime import datetime, timedelta
+from datetime import date as datetime_date, datetime, timedelta
 from time import sleep
 from json import loads as json_loads, dumps as json_dumps
 from urllib.parse import urlencode
@@ -17,7 +17,6 @@ from plomtask.http import TaskHandler, TaskServer
 from plomtask.processes import Process, ProcessStep
 from plomtask.conditions import Condition
 from plomtask.days import Day
-from plomtask.dating import DATE_FORMAT
 from plomtask.todos import Todo
 from plomtask.versioned_attributes import VersionedAttribute, TIMESTAMP_FMT
 from plomtask.exceptions import NotFoundException, HandledException
@@ -245,10 +244,10 @@ class TestCaseWithDB(TestCaseAugmented):
         # check empty, translation of 'yesterday' and 'tomorrow'
         items, start, end = f(self.db_conn, legal_range, date_col)
         self.assertEqual(items, [])
-        yesterday = datetime.now() + timedelta(days=-1)
-        tomorrow = datetime.now() + timedelta(days=+1)
-        self.assertEqual(start, yesterday.strftime(DATE_FORMAT))
-        self.assertEqual(end, tomorrow.strftime(DATE_FORMAT))
+        yesterday = datetime_date.today() + timedelta(days=-1)
+        tomorrow = datetime_date.today() + timedelta(days=+1)
+        self.assertEqual(start, yesterday.isoformat())
+        self.assertEqual(end, tomorrow.isoformat())
         # prepare dated items for non-empty results
         kwargs_with_date = self.default_init_kwargs.copy()
         if set_id_field:
@@ -277,7 +276,7 @@ class TestCaseWithDB(TestCaseAugmented):
         date_range = [dates[-1], dates[0]]
         self.assertEqual(f(self.db_conn, date_range, date_col)[0], [])
         # check that "today" is interpreted, and single-element interval
-        today_date = datetime.now().strftime(DATE_FORMAT)
+        today_date = datetime_date.today().isoformat()
         kwargs_with_date['date'] = today_date
         obj_today = self.checked_class(**kwargs_with_date)
         obj_today.save(self.db_conn)