1
0

Use full datetimes events

This commit is contained in:
Jon Michael Aanes 2024-11-24 17:08:41 +01:00
parent 05c870402d
commit 78a3b3b767
Signed by: Jmaa
SSH Key Fingerprint: SHA256:Ab0GfHGCblESJx7JRE4fj4bFy/KRpeLhi41y4pF3sNA
2 changed files with 35 additions and 34 deletions

View File

@ -5,7 +5,7 @@ Sub-module for importing time-based data into Obsidian.
import dataclasses import dataclasses
import datetime import datetime
from collections.abc import Iterator from collections.abc import Iterator, Iterable
from logging import getLogger from logging import getLogger
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -119,7 +119,6 @@ class EventContent:
subject: str subject: str
comment: str comment: str
def import_activity_sample_csv( def import_activity_sample_csv(
vault: ObsidianVault, vault: ObsidianVault,
rows: Rows, rows: Rows,
@ -141,16 +140,9 @@ def import_activity_sample_csv(
def map_to_event(sample: RealizedActivitySample) -> Event: def map_to_event(sample: RealizedActivitySample) -> Event:
content = content_mapper(sample) content = content_mapper(sample)
expected_tz = datetime.timezone(
datetime.timedelta(hours=2),
) # TODO: Determine this in a more intelligent manner
return Event( return Event(
sample.start_at.astimezone(expected_tz) sample.start_at,
.replace(second=0, microsecond=0) sample.end_at,
.time(),
sample.end_at.astimezone(expected_tz)
.replace(second=0, microsecond=0)
.time(),
verb=content.verb, verb=content.verb,
subject=escape_for_obsidian_link(content.subject), subject=escape_for_obsidian_link(content.subject),
comment=content.comment, comment=content.comment,

View File

@ -6,6 +6,8 @@ from decimal import Decimal
from logging import getLogger from logging import getLogger
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from zoneinfo import ZoneInfo
import enforce_typing
import frontmatter import frontmatter
import marko import marko
@ -16,10 +18,11 @@ logger = getLogger(__name__)
StatisticKey = str StatisticKey = str
@enforce_typing.enforce_types
@dataclasses.dataclass(frozen=True, order=True) @dataclasses.dataclass(frozen=True, order=True)
class Event: class Event:
start_time: datetime.time | None start_time: datetime.datetime | None
end_time: datetime.time | None end_time: datetime.datetime | None
verb: str | None verb: str | None
subject: str | None subject: str | None
comment: str comment: str
@ -29,13 +32,13 @@ class Event:
assert ':' not in self.subject assert ':' not in self.subject
assert '/' not in self.subject assert '/' not in self.subject
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class FileContents: class FileContents:
frontmatter: dict[str, Any] frontmatter: dict[str, Any]
blocks_pre_events: list blocks_pre_events: list
events: frozenset[Event] events: frozenset[Event]
blocks_post_events: list blocks_post_events: list
timezone: ZoneInfo
@dataclasses.dataclass(frozen=False) @dataclasses.dataclass(frozen=False)
@ -54,9 +57,6 @@ FILE_FORMAT = """
{blocks_post_events} {blocks_post_events}
""" """
MIDNIGHT = datetime.time(0, 0, 0)
class ObsidianVault: class ObsidianVault:
def __init__( def __init__(
self, self,
@ -136,6 +136,8 @@ class ObsidianVault:
return contents.events return contents.events
def _load_date_contents(self, date: datetime.date) -> FileContents | None: 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) file_path = self._date_file_path(date)
text = self._load_file_text(file_path) or self._load_file_text( text = self._load_file_text(file_path) or self._load_file_text(
self._daily_template_path(), self._daily_template_path(),
@ -147,9 +149,9 @@ class ObsidianVault:
ast = MARKDOWN_PARSER.parse(str(file_frontmatter)) ast = MARKDOWN_PARSER.parse(str(file_frontmatter))
(pre_events, list_block_items, post_events) = find_events_list_block(ast) (pre_events, list_block_items, post_events) = find_events_list_block(ast)
events = frozenset( 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: def _save_date_contents(self, date: datetime.date, contents: FileContents) -> None:
blocks_pre_events = ''.join( blocks_pre_events = ''.join(
@ -162,8 +164,9 @@ class ObsidianVault:
events = list(contents.events) events = list(contents.events)
events.sort(key=lambda x: x.subject or '') events.sort(key=lambda x: x.subject or '')
events.sort(key=lambda x: x.verb or '') events.sort(key=lambda x: x.verb or '')
events.sort(key=lambda x: x.start_time or x.end_time or MIDNIGHT) date_sentinel = datetime.datetime(1900, 1, 1, 1, 1, 1, tzinfo=contents.timezone)
block_events = '\n'.join('- ' + format_event_string(e) for e in events) 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( post = frontmatter.Post(
content=FILE_FORMAT.format( content=FILE_FORMAT.format(
@ -240,7 +243,7 @@ def find_events_list_block(ast) -> tuple[list, list[str], list]:
return (blocks, [], []) return (blocks, [], [])
def format_event_string(event: Event) -> str: def format_event_string(event: Event, tz: ZoneInfo) -> str:
assert event is not None assert event is not None
if ( if (
event.start_time is None event.start_time is None
@ -251,9 +254,9 @@ def format_event_string(event: Event) -> str:
return event.comment return event.comment
buf = [] 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: 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(' | ')
buf.append(event.verb) buf.append(event.verb)
buf.append(' [[') buf.append(' [[')
@ -271,7 +274,9 @@ RE_LINK_WIKI = r'\[\[([^\]:/]*)\]\]'
RE_TIME_FORMAT = RE_TIME + r'(?:\s*\-\s*' + RE_TIME + 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( if m := re.match(
r'^\s*' r'^\s*'
+ RE_TIME_FORMAT + RE_TIME_FORMAT
@ -282,10 +287,9 @@ def parse_event_string(event_str: str) -> Event:
+ r'\.?\s*(.*)$', + r'\.?\s*(.*)$',
event_str, event_str,
): ):
start = datetime.time.fromisoformat(m.group(1)) start_time = datetime.time.fromisoformat(m.group(1))
end = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start end_time = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start_time
return Event(start, end, m.group(3), m.group(4), m.group(5)) elif m := re.match(
if m := re.match(
r'^\s*' r'^\s*'
+ RE_TIME_FORMAT + RE_TIME_FORMAT
+ r'[ :\|-]*' + r'[ :\|-]*'
@ -295,8 +299,13 @@ def parse_event_string(event_str: str) -> Event:
+ r'\.?\s*(.*)$', + r'\.?\s*(.*)$',
event_str, event_str,
): ):
start = datetime.time.fromisoformat(m.group(1)) start_time = datetime.time.fromisoformat(m.group(1))
end = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start end_time = datetime.time.fromisoformat(m.group(2)) if m.group(2) else start_time
return Event(start, end, m.group(3), m.group(4), m.group(5)) else:
logger.info('Could not parse format: %s', event_str) logger.info('Could not parse format: %s', event_str)
return Event(None, None, None, None, 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))