Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/omnifocus_operator/repository/hybrid/hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@


def _get_local_tz() -> ZoneInfo:
"""Get system timezone as ZoneInfo from /etc/localtime symlink (macOS)."""
"""Get system timezone as ZoneInfo from /etc/localtime symlink (macOS).

/etc/localtime resolves through /var/db/timezone/zoneinfo, but fully
resolving symlinks can land on /usr/share/zoneinfo.default instead of
/usr/share/zoneinfo depending on macOS build, so match either variant.
"""
tz_path = pathlib.Path("/etc/localtime").resolve()
tz_name = str(tz_path).split("zoneinfo/")[-1]
tz_name = re.sub(r".*/zoneinfo(?:\.default)?/", "", str(tz_path))
return ZoneInfo(tz_name)


Expand Down
31 changes: 30 additions & 1 deletion tests/test_hybrid_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from __future__ import annotations

import asyncio
import pathlib
import plistlib
import sqlite3
import time
Expand All @@ -32,7 +33,11 @@
from omnifocus_operator.contracts.use_cases.list.tasks import ListTasksRepoQuery
from omnifocus_operator.models.enums import Availability, FolderAvailability, TagAvailability
from omnifocus_operator.models.snapshot import AllEntities
from omnifocus_operator.repository.hybrid.hybrid import _FRESHNESS_TIMEOUT, HybridRepository
from omnifocus_operator.repository.hybrid.hybrid import (
_FRESHNESS_TIMEOUT,
HybridRepository,
_get_local_tz,
)
from tests.doubles import InMemoryBridge, StubBridge

# Core Foundation epoch: Jan 1, 2001 00:00:00 UTC
Expand Down Expand Up @@ -1924,6 +1929,30 @@ async def test_project_reads_plain_text_note(self, hybrid_repo: HybridRepository
# ============================================================================


class TestGetLocalTz:
"""_get_local_tz() derives an IANA ZoneInfo name from the /etc/localtime symlink."""

@pytest.mark.parametrize(
"resolved_path",
[
"/usr/share/zoneinfo/Australia/Sydney",
"/usr/share/zoneinfo.default/Australia/Sydney",
"/var/db/timezone/zoneinfo/Australia/Sydney",
],
)
def test_resolves_tz_name_across_symlink_variants(self, resolved_path: str) -> None:
"""Regression: some macOS builds resolve /etc/localtime through
/usr/share/zoneinfo.default rather than /usr/share/zoneinfo, which used to
break naive `.split("zoneinfo/")` parsing and raise
`ZoneInfo keys may not be absolute paths`.
"""
with patch(
"pathlib.Path.resolve",
return_value=pathlib.PosixPath(resolved_path),
):
assert _get_local_tz() == ZoneInfo("Australia/Sydney")


class TestLocalDatetimeParsing:
"""dateDue, dateToStart, datePlanned should be parsed as local time with DST."""

Expand Down