"""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':
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()
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):
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]
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]:
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:
"""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)
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)
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
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
# 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:
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)