home · contact · privacy
Use same date ranging code for Day and Todo filtering.
[plomtask] / plomtask / days.py
index 071b0b1b27d76b676d02ee4d4530563a5ba547b5..d7083b4310bf76c168f6f33d29cf55c8ada7a29f 100644 (file)
@@ -1,23 +1,89 @@
 """Collecting Day and date-related items."""
-from datetime import datetime
+from __future__ import annotations
+from datetime import datetime, timedelta
+from plomtask.db import DatabaseConnection, BaseModel
+from plomtask.todos import Todo
+from plomtask.dating import (DATE_FORMAT, valid_date)
 
-DATE_FORMAT = '%Y-%m-%d'
 
-
-class Day:
+class Day(BaseModel[str]):
     """Individual days defined by their dates."""
+    table_name = 'days'
+    to_save = ['comment']
+
+    def __init__(self, date: str, comment: str = '') -> None:
+        id_ = valid_date(date)
+        super().__init__(id_)
+        self.datetime = datetime.strptime(self.date, DATE_FORMAT)
+        self.comment = comment
+        self.calendarized_todos: list[Todo] = []
+
+    def __lt__(self, other: Day) -> bool:
+        return self.date < other.date
 
-    def __init__(self, date: str):
-        self.date = date
-        self.datetime = datetime.strptime(date, DATE_FORMAT)
+    @classmethod
+    def all(cls, db_conn: DatabaseConnection,
+            date_range: tuple[str, str] = ('', ''),
+            fill_gaps: bool = False) -> list[Day]:
+        """Return list of Days in database within (open) date_range interval.
+
+        On fill_gaps=True, will instantiate (without saving) Days of all dates
+        within the date range that don't exist yet.
+        """
+        ret = cls.by_date_range_with_limits(db_conn, date_range, 'id')
+        days, start_date, end_date = ret
+        days.sort()
+        if fill_gaps:
+            if start_date not in [d.date for d in days]:
+                days = [Day(start_date)] + days
+            if end_date not in [d.date for d in days]:
+                days += [Day(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:
+                            day = Day(day.next_date)
+                            gapless_days += [day]
+                days = gapless_days
+        return days
+
+    @property
+    def date(self) -> str:
+        """Return self.id_ under the assumption it's a date string."""
+        assert isinstance(self.id_, str)
+        return self.id_
 
     @property
-    def weekday(self):
+    def first_of_month(self) -> bool:
+        """Return what month self.date is part of."""
+        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')
+
+    @property
+    def weekday(self) -> str:
         """Return what weekday matches self.date."""
         return self.datetime.strftime('%A')
 
-    def __eq__(self, other: object):
-        return isinstance(other, self.__class__) and self.date == other.date
+    @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)
 
-    def __lt__(self, other):
-        return self.date < other.date
+    @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)
+
+    def collect_calendarized_todos(self, db_conn: DatabaseConnection) -> None:
+        """Fill self.calendarized_todos."""
+        self.calendarized_todos = [t for t in Todo.by_date(db_conn, self.date)
+                                   if t.calendarize]