Use full datetimes events
This commit is contained in:
parent
05c870402d
commit
78a3b3b767
|
@ -5,7 +5,7 @@ Sub-module for importing time-based data into Obsidian.
|
|||
|
||||
import dataclasses
|
||||
import datetime
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Iterator, Iterable
|
||||
from logging import getLogger
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
@ -119,7 +119,6 @@ class EventContent:
|
|||
subject: str
|
||||
comment: str
|
||||
|
||||
|
||||
def import_activity_sample_csv(
|
||||
vault: ObsidianVault,
|
||||
rows: Rows,
|
||||
|
@ -141,16 +140,9 @@ def import_activity_sample_csv(
|
|||
|
||||
def map_to_event(sample: RealizedActivitySample) -> Event:
|
||||
content = content_mapper(sample)
|
||||
expected_tz = datetime.timezone(
|
||||
datetime.timedelta(hours=2),
|
||||
) # TODO: Determine this in a more intelligent manner
|
||||
return Event(
|
||||
sample.start_at.astimezone(expected_tz)
|
||||
.replace(second=0, microsecond=0)
|
||||
.time(),
|
||||
sample.end_at.astimezone(expected_tz)
|
||||
.replace(second=0, microsecond=0)
|
||||
.time(),
|
||||
sample.start_at,
|
||||
sample.end_at,
|
||||
verb=content.verb,
|
||||
subject=escape_for_obsidian_link(content.subject),
|
||||
comment=content.comment,
|
||||
|
|
|
@ -6,6 +6,8 @@ from decimal import Decimal
|
|||
from logging import getLogger
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from zoneinfo import ZoneInfo
|
||||
import enforce_typing
|
||||
|
||||
import frontmatter
|
||||
import marko
|
||||
|
@ -16,10 +18,11 @@ logger = getLogger(__name__)
|
|||
StatisticKey = str
|
||||
|
||||
|
||||
@enforce_typing.enforce_types
|
||||
@dataclasses.dataclass(frozen=True, order=True)
|
||||
class Event:
|
||||
start_time: datetime.time | None
|
||||
end_time: datetime.time | None
|
||||
start_time: datetime.datetime | None
|
||||
end_time: datetime.datetime | None
|
||||
verb: str | None
|
||||
subject: str | None
|
||||
comment: str
|
||||
|
@ -29,13 +32,13 @@ class Event:
|
|||
assert ':' not in self.subject
|
||||
assert '/' not in self.subject
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class FileContents:
|
||||
frontmatter: dict[str, Any]
|
||||
blocks_pre_events: list
|
||||
events: frozenset[Event]
|
||||
blocks_post_events: list
|
||||
timezone: ZoneInfo
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=False)
|
||||
|
@ -54,9 +57,6 @@ FILE_FORMAT = """
|
|||
{blocks_post_events}
|
||||
"""
|
||||
|
||||
MIDNIGHT = datetime.time(0, 0, 0)
|
||||
|
||||
|
||||
class ObsidianVault:
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -136,6 +136,8 @@ class ObsidianVault:
|
|||
return contents.events
|
||||
|
||||
def _load_date_contents(self, date: datetime.date) -> FileContents | None:
|
||||
timezone = ZoneInfo('Europe/Copenhagen') # TODO: Parameterize in an intelligent manner
|
||||
|
||||
file_path = self._date_file_path(date)
|
||||
text = self._load_file_text(file_path) or self._load_file_text(
|
||||
self._daily_template_path(),
|
||||
|
@ -147,9 +149,9 @@ class ObsidianVault:
|
|||
ast = MARKDOWN_PARSER.parse(str(file_frontmatter))
|
||||
(pre_events, list_block_items, post_events) = find_events_list_block(ast)
|
||||
events = frozenset(
|
||||
parse_event_string(list_item) for list_item in list_block_items
|
||||
parse_event_string(list_item, date, timezone) for list_item in list_block_items
|
||||
)
|
||||
return FileContents(file_frontmatter.metadata, pre_events, events, post_events)
|
||||
return FileContents(file_frontmatter.metadata, pre_events, events, post_events, timezone)
|
||||
|
||||
def _save_date_contents(self, date: datetime.date, contents: FileContents) -> None:
|
||||
blocks_pre_events = ''.join(
|
||||
|
@ -162,8 +164,9 @@ class ObsidianVault:
|
|||
events = list(contents.events)
|
||||
events.sort(key=lambda x: x.subject or '')
|
||||
events.sort(key=lambda x: x.verb or '')
|
||||
events.sort(key=lambda x: x.start_time or x.end_time or MIDNIGHT)
|
||||
block_events = '\n'.join('- ' + format_event_string(e) for e in events)
|
||||
date_sentinel = datetime.datetime(1900, 1, 1, 1, 1, 1, tzinfo=contents.timezone)
|
||||
events.sort(key=lambda x: x.start_time or x.end_time or date_sentinel)
|
||||
block_events = '\n'.join('- ' + format_event_string(e, tz = contents.timezone) for e in events)
|
||||
|
||||
post = frontmatter.Post(
|
||||
content=FILE_FORMAT.format(
|
||||
|
@ -240,7 +243,7 @@ def find_events_list_block(ast) -> tuple[list, list[str], list]:
|
|||
return (blocks, [], [])
|
||||
|
||||
|
||||
def format_event_string(event: Event) -> str:
|
||||
def format_event_string(event: Event, tz: ZoneInfo) -> str:
|
||||
assert event is not None
|
||||
if (
|
||||
event.start_time is None
|
||||
|
@ -251,9 +254,9 @@ def format_event_string(event: Event) -> str:
|
|||
return event.comment
|
||||
|
||||
buf = []
|
||||
buf.append(f'{event.start_time:%H:%M}')
|
||||
buf.append(f'{event.start_time.astimezone(tz):%H:%M}')
|
||||
if event.end_time and event.end_time != event.start_time:
|
||||
buf.append(f'-{event.end_time:%H:%M}')
|
||||
buf.append(f'-{event.end_time.astimezone(tz):%H:%M}')
|
||||
buf.append(' | ')
|
||||
buf.append(event.verb)
|
||||
buf.append(' [[')
|
||||
|
@ -271,7 +274,9 @@ RE_LINK_WIKI = r'\[\[([^\]:/]*)\]\]'
|
|||
RE_TIME_FORMAT = RE_TIME + r'(?:\s*\-\s*' + RE_TIME + r')?'
|
||||
|
||||
|
||||
def parse_event_string(event_str: str) -> Event:
|
||||
def parse_event_string(event_str: str, date: datetime.date, timezone: ZoneInfo) -> Event:
|
||||
"""Parses event string for the given date.
|
||||
"""
|
||||
if m := re.match(
|
||||
r'^\s*'
|
||||
+ RE_TIME_FORMAT
|
||||
|
@ -282,10 +287,9 @@ def parse_event_string(event_str: str) -> Event:
|
|||
+ r'\.?\s*(.*)$',
|
||||
event_str,
|
||||
):
|
||||
start = datetime.time.fromisoformat(m.group(1))
|
||||
end = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start
|
||||
return Event(start, end, m.group(3), m.group(4), m.group(5))
|
||||
if m := re.match(
|
||||
start_time = datetime.time.fromisoformat(m.group(1))
|
||||
end_time = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start_time
|
||||
elif m := re.match(
|
||||
r'^\s*'
|
||||
+ RE_TIME_FORMAT
|
||||
+ r'[ :\|-]*'
|
||||
|
@ -295,8 +299,13 @@ def parse_event_string(event_str: str) -> Event:
|
|||
+ r'\.?\s*(.*)$',
|
||||
event_str,
|
||||
):
|
||||
start = datetime.time.fromisoformat(m.group(1))
|
||||
end = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start
|
||||
return Event(start, end, m.group(3), m.group(4), m.group(5))
|
||||
start_time = datetime.time.fromisoformat(m.group(1))
|
||||
end_time = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start_time
|
||||
else:
|
||||
logger.info('Could not parse format: %s', event_str)
|
||||
return Event(None, None, None, None, event_str)
|
||||
|
||||
start = datetime.datetime.combine(date, start_time, timezone)
|
||||
end = datetime.datetime.combine(date, end_time, timezone)
|
||||
|
||||
return Event(start, end, m.group(3), m.group(4), m.group(5))
|
||||
|
|
Loading…
Reference in New Issue
Block a user