From: Christian Heller Date: Tue, 7 Jan 2025 04:53:11 +0000 (+0100) Subject: Switch from datetime.datetime to datetime.date where that's enough resolution. X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/%7B%7B%20web_path%20%7D%7D/static/test.html?a=commitdiff_plain;h=597d8340730b4b3c23e607dce045aa295392eeed;p=plomtask Switch from datetime.datetime to datetime.date where that's enough resolution. --- diff --git a/plomtask/dating.py b/plomtask/dating.py index 26b3ce3..5333751 100644 --- a/plomtask/dating.py +++ b/plomtask/dating.py @@ -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() diff --git a/plomtask/days.py b/plomtask/days.py index b576bb2..1fe90ee 100644 --- a/plomtask/days.py +++ b/plomtask/days.py @@ -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]: diff --git a/plomtask/todos.py b/plomtask/todos.py index 5a71400..f7d375d 100644 --- a/plomtask/todos.py +++ b/plomtask/todos.py @@ -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: diff --git a/tests/days.py b/tests/days.py index c195237..595ff20 100644 --- a/tests/days.py +++ b/tests/days.py @@ -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) diff --git a/tests/utils.py b/tests/utils.py index 66b2fbf..ad31f17 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -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)