mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2026-07-31 15:59:34 -04:00
- Added utility functions to ensure datetime values are UTC-aware, improving consistency in date handling across the application. - Introduced migrations to update existing visit records, making start and end dates timezone-aware. - Normalized all-day visit end dates to UTC midnight, ensuring accurate representation of all-day events. - Updated serializers and services to utilize new datetime utilities, enhancing the handling of visit dates in various contexts. - Added comprehensive tests for new datetime utilities to ensure correct functionality and edge case handling.
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, time, timezone as dt_timezone
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
def ensure_aware_utc(value: datetime | None) -> datetime | None:
|
|
"""Return a UTC-aware datetime, treating naive values as UTC."""
|
|
if value is None:
|
|
return None
|
|
if timezone.is_naive(value):
|
|
return timezone.make_aware(value, dt_timezone.utc)
|
|
return value
|
|
|
|
|
|
def aware_utc_from_date(value: date, *, end_of_day: bool = False) -> datetime:
|
|
"""Build a UTC-aware datetime at the start or end of a calendar day."""
|
|
combined = datetime.combine(value, time.max if end_of_day else time.min)
|
|
return timezone.make_aware(combined, dt_timezone.utc)
|
|
|
|
|
|
def aware_utc_from_date_and_time(value: date, time_value: time) -> datetime:
|
|
"""Build a UTC-aware datetime from a date and time."""
|
|
combined = datetime.combine(value, time_value)
|
|
return timezone.make_aware(combined, dt_timezone.utc)
|
|
|
|
|
|
def is_midnight_utc(value: datetime | None) -> bool:
|
|
if value is None:
|
|
return False
|
|
utc_value = ensure_aware_utc(value).astimezone(dt_timezone.utc)
|
|
return utc_value.time() == time.min
|
|
|
|
|
|
def is_end_of_day_utc(value: datetime | None) -> bool:
|
|
if value is None:
|
|
return False
|
|
utc_value = ensure_aware_utc(value).astimezone(dt_timezone.utc)
|
|
return utc_value.time() == time.max
|
|
|
|
|
|
def is_all_day_bound(value: datetime | None) -> bool:
|
|
"""True when a datetime is stored as UTC midnight or legacy end-of-day."""
|
|
return is_midnight_utc(value) or is_end_of_day_utc(value)
|
|
|
|
|
|
def normalize_all_day_visit_dates(
|
|
start: datetime | None,
|
|
end: datetime | None,
|
|
) -> tuple[datetime | None, datetime | None]:
|
|
"""
|
|
Normalize all-day visits to UTC midnight on each calendar date.
|
|
|
|
All-day visits always use T00:00:00Z for both start_date and end_date.
|
|
"""
|
|
start = ensure_aware_utc(start)
|
|
end = ensure_aware_utc(end)
|
|
|
|
if start is None:
|
|
return start, end
|
|
|
|
if not is_midnight_utc(start):
|
|
return start, end
|
|
|
|
if end is None:
|
|
return start, aware_utc_from_date(start.date())
|
|
|
|
if is_all_day_bound(end):
|
|
return start, aware_utc_from_date(end.date())
|
|
|
|
return start, end
|