fix: 포트 충돌 회피 — note_bridge 8098, intent_service 8099
Jellyfin(8096), OrbStack(8097) 포트 충돌으로 변경. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
251
.venv/lib/python3.9/site-packages/recurring_ical_events/util.py
Normal file
251
.venv/lib/python3.9/site-packages/recurring_ical_events/util.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""Utility functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING, Callable, Optional, Sequence
|
||||
|
||||
from recurring_ical_events.errors import PeriodEndBeforeStart
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from recurring_ical_events.adapters.component import ComponentAdapter
|
||||
from recurring_ical_events.types import RecurrenceIDs, Time, Timestamp
|
||||
|
||||
|
||||
def timestamp(dt: datetime.datetime) -> Timestamp:
|
||||
"""Return the time stamp of a datetime"""
|
||||
return dt.timestamp()
|
||||
|
||||
|
||||
def convert_to_date(date: Time) -> datetime.date:
|
||||
"""Converts a date or datetime to a date"""
|
||||
return datetime.date(date.year, date.month, date.day)
|
||||
|
||||
|
||||
def is_pytz(tzinfo: datetime.tzinfo | Time):
|
||||
"""Whether the time zone requires localize() and normalize().
|
||||
|
||||
pytz requires these funtions to be used in order to correctly use the
|
||||
time zones after operations.
|
||||
"""
|
||||
return hasattr(tzinfo, "localize")
|
||||
|
||||
|
||||
def normalize_pytz(time: Time) -> Time:
|
||||
"""We have to normalize the time after a calculation if we use pytz."""
|
||||
if is_pytz_dt(time):
|
||||
return time.tzinfo.normalize(time)
|
||||
return time
|
||||
|
||||
|
||||
def is_date(time: Time) -> bool:
|
||||
"""Whether this is a date and not a datetime."""
|
||||
return isinstance(time, datetime.date) and not isinstance(time, datetime.datetime)
|
||||
|
||||
|
||||
def is_datetime(time: Time) -> bool:
|
||||
"""Whether this is a datetime and not a date."""
|
||||
return isinstance(time, datetime.datetime)
|
||||
|
||||
|
||||
def has_timezone(time: Time) -> bool:
|
||||
"""Whether this date/datetime has a timezone."""
|
||||
return is_datetime(time) and time.tzinfo is not None
|
||||
|
||||
|
||||
def convert_to_datetime(
|
||||
date: Time, tzinfo: Optional[datetime.tzinfo]
|
||||
) -> datetime.datetime:
|
||||
"""Converts a date to a datetime.
|
||||
|
||||
Dates are converted to datetimes with tzinfo.
|
||||
Datetimes loose their timezone if tzinfo is None.
|
||||
Datetimes receive tzinfo as a timezone if they do not have a timezone.
|
||||
Datetimes retain their timezone if they have one already (tzinfo is not None).
|
||||
"""
|
||||
if is_date(date):
|
||||
date = datetime.datetime(date.year, date.month, date.day) # noqa: DTZ001
|
||||
if isinstance(date, datetime.datetime):
|
||||
if date.tzinfo is None:
|
||||
if tzinfo is not None:
|
||||
if is_pytz(tzinfo):
|
||||
return tzinfo.localize(date)
|
||||
return date.replace(tzinfo=tzinfo)
|
||||
elif tzinfo is None:
|
||||
return normalize_pytz(date).replace(tzinfo=None)
|
||||
return date
|
||||
return date
|
||||
|
||||
|
||||
def make_comparable(dates: Sequence[Time]) -> list[Time]:
|
||||
"""Make an list or tuple of dates comparable.
|
||||
|
||||
Returns an list.
|
||||
"""
|
||||
tzinfo = None
|
||||
all_dates = True
|
||||
for date in dates:
|
||||
if not is_date(date):
|
||||
all_dates = False
|
||||
if has_timezone(date):
|
||||
tzinfo = date.tzinfo
|
||||
break
|
||||
if all_dates:
|
||||
return dates
|
||||
return [convert_to_datetime(date, tzinfo) for date in dates]
|
||||
|
||||
|
||||
def time_span_contains_event(
|
||||
span_start: Time,
|
||||
span_stop: Time,
|
||||
event_start: Time,
|
||||
event_stop: Time,
|
||||
comparable: bool = False, # noqa: FBT001
|
||||
) -> bool:
|
||||
"""Return whether an event should is included within a time span.
|
||||
|
||||
- span_start and span_stop define the time span
|
||||
- event_start and event_stop define the event time
|
||||
- comparable indicates whether the dates can be compared.
|
||||
You can set it to True if you are sure you have timezones and
|
||||
date/datetime correctly or used make_comparable() before.
|
||||
|
||||
Note that the stops are exlusive but the starts are inclusive.
|
||||
|
||||
This is an essential function of the module. It should be tested in
|
||||
test/test_time_span_contains_event.py.
|
||||
|
||||
This raises a PeriodEndBeforeStart exception if a start is after an end.
|
||||
"""
|
||||
if not comparable:
|
||||
span_start, span_stop, event_start, event_stop = make_comparable(
|
||||
(span_start, span_stop, event_start, event_stop)
|
||||
)
|
||||
if event_start > event_stop:
|
||||
raise PeriodEndBeforeStart(
|
||||
(
|
||||
"the event must start before it ends"
|
||||
f"(start: {event_start} end: {event_stop})"
|
||||
),
|
||||
event_start,
|
||||
event_stop,
|
||||
)
|
||||
if span_start > span_stop:
|
||||
raise PeriodEndBeforeStart(
|
||||
(
|
||||
"the time span must start before it ends"
|
||||
f"(start: {span_start} end: {span_stop})"
|
||||
),
|
||||
span_start,
|
||||
span_stop,
|
||||
)
|
||||
if event_start == event_stop:
|
||||
if span_start == span_stop:
|
||||
return event_start == span_start
|
||||
return span_start <= event_start < span_stop
|
||||
if span_start == span_stop:
|
||||
return event_start <= span_start < event_stop
|
||||
return event_start < span_stop and span_start < event_stop
|
||||
|
||||
|
||||
def compare_greater(date1: Time, date2: Time) -> bool:
|
||||
"""Compare two dates if date1 > date2 and make them comparable before."""
|
||||
date1, date2 = make_comparable((date1, date2))
|
||||
return date1 > date2
|
||||
|
||||
|
||||
def cmp(date1: Time, date2: Time) -> int:
|
||||
"""Compare two dates, like cmp().
|
||||
|
||||
Returns
|
||||
-------
|
||||
-1 if date1 < date2
|
||||
0 if date1 = date2
|
||||
1 if date1 > date2
|
||||
|
||||
"""
|
||||
# credits: https://www.geeksforgeeks.org/python-cmp-function/
|
||||
# see https://stackoverflow.com/a/22490617/1320237
|
||||
date1, date2 = make_comparable((date1, date2))
|
||||
return (date1 > date2) - (date1 < date2)
|
||||
|
||||
|
||||
def is_pytz_dt(time: Time) -> bool:
|
||||
"""Whether the time requires localize() and normalize().
|
||||
|
||||
pytz requires these funtions to be used in order to correctly use the
|
||||
time zones after operations.
|
||||
"""
|
||||
return isinstance(time, datetime.datetime) and is_pytz(time.tzinfo)
|
||||
|
||||
|
||||
def cached_property(func: Callable) -> property:
|
||||
"""Cache the property value for speed up."""
|
||||
name = f"_cached_{func.__name__}"
|
||||
not_found = object()
|
||||
|
||||
@property
|
||||
@wraps(func)
|
||||
def cached_property(self: object):
|
||||
value = self.__dict__.get(name, not_found)
|
||||
if value is not_found:
|
||||
self.__dict__[name] = value = func(self)
|
||||
return value
|
||||
|
||||
return cached_property
|
||||
|
||||
|
||||
def to_recurrence_ids(time: Time) -> RecurrenceIDs:
|
||||
"""Convert the time to a recurrence id so it can be hashed and recognized.
|
||||
|
||||
The first value should be used to identify a component as it is a datetime in UTC.
|
||||
The other values can be used to look the component up.
|
||||
"""
|
||||
# We are inside the Series calculation with this and want to identify
|
||||
# a date. It is fair to assume that the timezones are the same now.
|
||||
if not isinstance(time, datetime.datetime):
|
||||
return (convert_to_datetime(time, None),)
|
||||
if time.tzinfo is None:
|
||||
return (time,)
|
||||
return (
|
||||
time.astimezone(datetime.timezone.utc).replace(tzinfo=None),
|
||||
time.replace(tzinfo=None),
|
||||
)
|
||||
|
||||
|
||||
def with_highest_sequence(
|
||||
adapter1: ComponentAdapter | None, adapter2: ComponentAdapter | None
|
||||
):
|
||||
"""Return the one with the highest sequence."""
|
||||
return max(
|
||||
adapter1,
|
||||
adapter2,
|
||||
key=lambda adapter: -1e10 if adapter is None else adapter.sequence,
|
||||
)
|
||||
|
||||
|
||||
def get_any(dictionary: dict, keys: Sequence[object], default: object = None):
|
||||
"""Get any item from the keys and return it."""
|
||||
result = default
|
||||
for key in keys:
|
||||
result = dictionary.get(key, result)
|
||||
return result
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PeriodEndBeforeStart",
|
||||
"cmp",
|
||||
"convert_to_datetime",
|
||||
"get_any",
|
||||
"has_timezone",
|
||||
"is_date",
|
||||
"is_datetime",
|
||||
"is_pytz",
|
||||
"is_pytz_dt",
|
||||
"make_comparable",
|
||||
"normalize_pytz",
|
||||
"time_span_contains_event",
|
||||
"to_recurrence_ids",
|
||||
"with_highest_sequence",
|
||||
]
|
||||
Reference in New Issue
Block a user