Files
bambuddy/backend
maziggy f855d8dcca Pin the PostgreSQL session to UTC so defaulted timestamps are UTC (#2855)
On a UTC+3 install every AMS humidity reading and every archive was
    stamped three hours ahead of when it happened. Bambuddy stores naive
    timestamps that hold UTC and the frontend's parseUTCDate reads an
    offsetless timestamp as UTC, so the display added the offset to a value
    that was already local.

    The Python side has honoured that contract since #504. The reporter's
    timestamps were not written by Python. Around ninety-six columns take
    their value from server_default=func.now() and the migration DDL carries
    another forty-nine on DEFAULT CURRENT_TIMESTAMP -- the database fills
    those, and on PostgreSQL now() is a timestamptz, so storing it into a
    timestamp without time zone casts it through the session TimeZone. A
    Postgres container started with TZ=Europe/Istanbul bakes that zone into
    postgresql.conf at initdb, and every defaulted column then receives local
    wall-clock. recorded_at is the clearest case: nothing in the codebase
    ever assigns it, so its value is entirely whatever the database decided.

    Connections now carry timezone=UTC, which makes the cast a no-op whatever
    the server is set to. Measured through the real engine factory against a
    live PostgreSQL, a session on the reporter's configuration stored +10800s
    and the fixed one +0s. Pinning the session was preferred over a hundred
    and forty-five individual edits partly for its size but mostly because
    half of those sites are raw DDL that no model-level change can reach.

    SQLite needed nothing and gets nothing: its CURRENT_TIMESTAMP is UTC by
    definition and it has no session timezone to get wrong, which is why this
    survived two years of timezone fixes without showing itself. That also
    makes it the reference -- the change moves Postgres onto SQLite's
    behaviour rather than introducing a third convention -- so the SQLite
    behaviour is now pinned by a test instead of being assumed. asyncpg is
    the documented driver and takes the setting in its startup packet; any
    other Postgres driver gets the same setting the libpq way, so a psycopg
    URL does not fail at connect on a keyword asyncpg alone accepts.

    Rows already written are deliberately left alone. The inverse cast is
    computable and DST-correct, but it cannot be applied safely: created_at
    is assigned explicitly on some paths and defaulted on others, an install
    that began on SQLite holds correct and shifted rows side by side, and
    nothing distinguishes them after the fact. Timestamps are right from the
    upgrade forward and history keeps the times it was given.

    One related mismatch goes with it, because fixing the database side alone
    would have made it start lying on exactly the installs this repairs. The
    support package's oldest_pending_age_seconds subtracted a naive local
    clock from a naive UTC column, with a comment claiming it was UTC; on the
    reporter's install the two errors cancelled. It reported a job queued
    five minutes ago as three hours old east of Greenwich and a negative age
    west of it. The two AMS and printer-sensor retention cutoffs move to the
    same utcnow_naive helper -- correct in value already, but deprecated in
    3.12 and emitting warnings on every sweep.
2026-08-17 15:46:44 +02:00
..