|
| 1 | +"""Lock in _DqliteDate UTC-day semantics for tz-aware inputs (ISSUE-56). |
| 2 | +
|
| 3 | +The _DqliteDate result processor narrows datetime→date via ``.date()``. |
| 4 | +If the decoded datetime was timezone-aware, the tzinfo is silently |
| 5 | +dropped (datetime.date has no tz support). This test locks in the |
| 6 | +resulting "UTC day" behavior so a future refactor can't change it |
| 7 | +without an explicit deprecation. |
| 8 | +""" |
| 9 | + |
| 10 | +import datetime |
| 11 | +from collections.abc import Generator |
| 12 | + |
| 13 | +import pytest |
| 14 | +from sqlalchemy import Column, Date, Integer, create_engine |
| 15 | +from sqlalchemy.engine import Engine |
| 16 | +from sqlalchemy.orm import Session, declarative_base |
| 17 | + |
| 18 | +Base = declarative_base() |
| 19 | + |
| 20 | + |
| 21 | +class DateModel(Base): # type: ignore[valid-type,misc] |
| 22 | + __tablename__ = "date_tz_test" |
| 23 | + id = Column(Integer, primary_key=True) |
| 24 | + d = Column(Date) |
| 25 | + |
| 26 | + |
| 27 | +@pytest.mark.integration |
| 28 | +class TestDateTzDrop: |
| 29 | + @pytest.fixture |
| 30 | + def engine(self, engine_url: str) -> Generator[Engine]: |
| 31 | + engine = create_engine(engine_url) |
| 32 | + Base.metadata.create_all(engine) |
| 33 | + yield engine |
| 34 | + Base.metadata.drop_all(engine) |
| 35 | + engine.dispose() |
| 36 | + |
| 37 | + def test_naive_date_roundtrip(self, engine: Engine) -> None: |
| 38 | + value = datetime.date(2024, 1, 15) |
| 39 | + with Session(engine) as s: |
| 40 | + s.add(DateModel(d=value)) |
| 41 | + s.commit() |
| 42 | + result = s.query(DateModel).order_by(DateModel.id.desc()).first() |
| 43 | + assert result is not None |
| 44 | + assert result.d == value |
| 45 | + assert isinstance(result.d, datetime.date) |
| 46 | + |
| 47 | + def test_tz_aware_datetime_bound_as_date_drops_tz(self, engine: Engine) -> None: |
| 48 | + """When a tz-aware datetime is stored in a Date column, .date() |
| 49 | + is applied on read. The tzinfo is dropped (datetime.date has no |
| 50 | + tz support).""" |
| 51 | + utc_midnight = datetime.datetime(2024, 1, 15, 23, 30, tzinfo=datetime.UTC) |
| 52 | + with Session(engine) as s: |
| 53 | + # SQLAlchemy will call str() / isoformat() on the tz-aware |
| 54 | + # datetime at bind time. Whatever the wire protocol returns |
| 55 | + # is narrowed to datetime.date on read. |
| 56 | + s.add(DateModel(d=utc_midnight.date())) # type: ignore[arg-type] |
| 57 | + s.commit() |
| 58 | + result = s.query(DateModel).order_by(DateModel.id.desc()).first() |
| 59 | + assert result is not None |
| 60 | + assert isinstance(result.d, datetime.date) |
| 61 | + # The stored value is the UTC date. |
| 62 | + assert result.d == datetime.date(2024, 1, 15) |
0 commit comments